Thinkphp 第六章:模型數(shù)據(jù)處理

模型提供了比數(shù)據(jù)庫(kù)類更為強(qiáng)大的數(shù)據(jù)處理功能,本章講解了如何使用模型的各種數(shù)據(jù)自動(dòng)處理機(jī)制來(lái)簡(jiǎn)化開(kāi)發(fā),包括模型數(shù)據(jù)的轉(zhuǎn)換和輸出抬闷,是模型的核心和必須掌握的部分,學(xué)習(xí)內(nèi)容主要包含:

獲取器和修改器

獲取器和修改器是5.0模型的核心功能之一刹淌,也是數(shù)據(jù)處理的關(guān)鍵饶氏,它們的配合完成了模型數(shù)據(jù)的輸入和輸出(自動(dòng))處理讥耗,但獲取器和修改器并非一一對(duì)應(yīng)。

獲取器

獲取器的作用是對(duì)模型的數(shù)據(jù)對(duì)象的(原始)數(shù)據(jù)做出自動(dòng)處理疹启。一個(gè)獲取器對(duì)應(yīng)模型的一個(gè)特殊方法古程,方法格式為:

getFieldNameAttr

FieldName為數(shù)據(jù)表字段的駝峰轉(zhuǎn)換,定義了獲取器之后會(huì)在下列情況自動(dòng)觸發(fā):

  • 模型的數(shù)據(jù)對(duì)象取值操作($model->field_name)喊崖;
  • 模型的序列化輸出操作($model->toArray())挣磨;
  • 顯式調(diào)用getAttr方法($this->getAttr('field_name'));

獲取器的場(chǎng)景包括:

  • 時(shí)間日期字段的格式化輸出荤懂;
  • 集合或枚舉類型的輸出茁裙;
  • 數(shù)字狀態(tài)字段的輸出;
  • 組合字段的輸出节仿;

例如晤锥,User模型有一個(gè)時(shí)間戳類型的birthday屬性,那么如果使用

$user = User::get(1);
// 輸出 1234567890
echo $user->birthday;

輸出的是一個(gè)數(shù)字時(shí)間戳廊宪,并不是標(biāo)準(zhǔn)日期字符串格式矾瘾,一般的處理是,我們手動(dòng)處理箭启,例如:

$user = User::get(1);
// 輸出 2009-02-14
echo date('Y-m-d', $user->birthday);

這只是舉個(gè)例子壕翩,因?yàn)閿?shù)據(jù)的輸出處理可能非常的復(fù)雜,涉及到多個(gè)字段的不同處理方式傅寡。

定義模型的讀取器可以簡(jiǎn)化此類操作放妈,例如:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected function getBirthdayAttr($value)
    {
        return date('Y-m-d', $value);
    }
}

getBirthdayAttr就是一個(gè)獲取器方法,定義后再次測(cè)試下面的示例荐操,結(jié)果就不同了芜抒。

$user = User::get(1);
// 輸出 2009-02-14
echo $user->birthday;

定義了獲取器之后,如果要獲取原始數(shù)據(jù)的值怎么辦呢托启?框架提供了getData方法挽绩,用法如下:

$user = User::get(1);
// 輸出 2009-02-14
echo $user->birthday;
// 輸出 1234567890
echo $user->getData('birthday');

如果要獲取數(shù)據(jù)表的create_time字段,獲取器方法定義方式為:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{

    protected function getBirthdayAttr($value)
    {
        return date('Y-m-d', $value);
    }

    protected function getCreateTimeAttr($value)
    {
        return date('Y-m-d H:i:s', $value);
    }    
}

獲取create_time字段值的時(shí)候驾中,使用:

$user = User::get(1);
// 輸出 2009-02-14
echo $user->create_time;
// 或者
echo $user->createTime;

獲取模型的對(duì)象屬性的時(shí)候駝峰法和小寫命名方式都可以取到值。

如果你的獲取器方法需要根據(jù)其它字段的值來(lái)組合模聋,可以給獲取器方法添加第二個(gè)參數(shù)肩民,如下:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{

    protected function getBirthdayAttr($value)
    {
        return date('Y-m-d', $value);
    }

    protected function getCreateTimeAttr($value)
    {
        return date('Y-m-d H:i:s', $value);
    }    

    protected function getUserTitleAttr($value,$data)
    {
        return $data['name'] . ':' . $data['nickname'];
    }
}

獲取器方法的第二個(gè)參數(shù)表示當(dāng)前數(shù)據(jù)對(duì)象的所有數(shù)據(jù),數(shù)據(jù)表并不存在user_title字段链方,其實(shí)獲取器讀取的數(shù)據(jù)對(duì)象屬性和字段是無(wú)關(guān)的持痰。

$user = User::get(1);
// 輸出 thinkphp:流年
echo $user->user_title;

修改器

和獲取器相反,修改器的主要作用是對(duì)模型設(shè)置的數(shù)據(jù)對(duì)象值進(jìn)行處理祟蚀。修改器方法的命名規(guī)范為:

setFieldNameAttr

修改器的使用場(chǎng)景和讀取器類似:

  • 時(shí)間日期字段的轉(zhuǎn)換寫入工窍;
  • 集合或枚舉類型的寫入割卖;
  • 數(shù)字狀態(tài)字段的寫入;
  • 某個(gè)字段涉及其它字段的條件或者組合寫入患雏;

定義了修改器之后會(huì)在下列情況下觸發(fā):

  • 模型對(duì)象賦值鹏溯;
  • 調(diào)用模型的data方法,并且第二個(gè)參數(shù)傳入true淹仑;
  • 調(diào)用模型的save方法丙挽,并且傳入數(shù)據(jù);
  • 顯式調(diào)用模型的setAttr方法匀借;
  • 定義了該字段的自動(dòng)完成颜阐;

還是用之前的例子,比如說(shuō)你對(duì)User模型的birthday屬性設(shè)置了一個(gè)2017-1-1的日期字符串吓肋,但是數(shù)據(jù)表的字段類型是時(shí)間戳類型的凳怨,普通的方式是:

$user = User::get(1);
$user->birthday = strtotime('2017-1-1');
$user->save();

如果使用修改器定義,首先定義

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected function getBirthdayAttr($value)
    {
        return date('Y-m-d', $value);
    }

    protected function setBirthdayAttr($value)
    {
        return strtotime($value);
    }    
}

通常讀取器和修改器都是配套定義的是鬼,現(xiàn)在我們不需要對(duì)日期數(shù)據(jù)進(jìn)行處理了肤舞,直接使用:

$user = User::get(1);
$user->birthday = '2017-1-1';
// 實(shí)際寫入數(shù)據(jù)表的值是
$user->save();

同樣胸梆,如果你需要在修改器中使用其它屬性的值畸冲,可以添加第二個(gè)參數(shù)盏浇,例如:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{

    protected function setUserTokenAttr($value, $data)
    {
        return md5($data['name'] . $data['birthday']);
    }
}

這里傳入的data可能已經(jīng)經(jīng)過(guò)了其它的修改器操作铣减,并非原始的數(shù)據(jù)攒发。

定義后的設(shè)置代碼如下:

$user             = User::get(1);
$user->birthday   = '2017-1-1';
$user->user_token = null;
$user->save();

由于已經(jīng)定義了user_token字段的修改器宛官,而改修改器并沒(méi)有根據(jù)當(dāng)前字段的值來(lái)處理帚豪,因此設(shè)置任何值都不影響空免,這里設(shè)置為null紫皇。和讀取器不同慰安,修改器的屬性必須是數(shù)據(jù)表中存在的字段,否則修改器的值僅僅能作為數(shù)據(jù)輔助作用聪铺。

下面的寫法是錯(cuò)誤的(會(huì)拋出字段不存在的異常)

$user            = User::get(1);
$user->birthday  = '2017-1-1';
$user->userToken = null;
$user->save();

讀取器是可以使用$user->userToken來(lái)獲取化焕,為了避免類似問(wèn)題的困惑,我們的建議是:

  • 數(shù)據(jù)表字段統(tǒng)一使用小寫+下劃線命名铃剔;
  • 方法中統(tǒng)一使用駝峰法命名撒桨;
  • 模型的對(duì)象屬性統(tǒng)一使用小寫+下劃線命名;

但實(shí)際應(yīng)用開(kāi)發(fā)過(guò)程中键兜,修改器的定義往往少于讀取器的定義數(shù)量凤类。同時(shí)也要避免多次修改的問(wèn)題。

自動(dòng)時(shí)間字段

由于數(shù)據(jù)表的時(shí)間字段是一個(gè)非常普遍的需求普气,因此框架做了一些強(qiáng)化支持谜疤,無(wú)需定義獲取器和修改器就能完成時(shí)間日期類型字段的自動(dòng)處理。

默認(rèn)情況下自動(dòng)寫入時(shí)間戳字段功能是關(guān)閉的,可以在模型里面定義

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    // 開(kāi)啟時(shí)間字段自動(dòng)寫入
    protected $autoWriteTimestamp = true; 
}

開(kāi)啟時(shí)間字段(這里的時(shí)間字段支持整型夷磕、時(shí)間戳和日期類型)自動(dòng)寫入后履肃,會(huì)默認(rèn)自動(dòng)寫入兩個(gè)時(shí)間字段:create_time(創(chuàng)建時(shí)間,新增數(shù)據(jù)的時(shí)候自動(dòng)寫入)和update_time(更新時(shí)間坐桩,新增和更新的時(shí)候都會(huì)自動(dòng)寫入)尺棋,并且以整型類型寫入數(shù)據(jù)庫(kù)。

如果你的時(shí)間字段名不是默認(rèn)字段撕攒,則需要添加屬性設(shè)置陡鹃。

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    // 開(kāi)啟時(shí)間字段自動(dòng)寫入
    protected $autoWriteTimestamp = true; 
    // 定義時(shí)間字段名
    protected $createTime = 'create_at';
    protected $updateTime = 'update_at';    
}

如果你的時(shí)間字段類型不是整型,也可以單獨(dú)設(shè)置如下:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    // 開(kāi)啟時(shí)間字段自動(dòng)寫入 并設(shè)置字段類型為datetime
    protected $autoWriteTimestamp = 'datetime'; 
}

autoWriteTimestamp屬性支持設(shè)置的時(shí)間字段類型包括:整型(設(shè)置為true的時(shí)候使用該類型)抖坪、時(shí)間(datetime)和時(shí)間戳(timestamp)萍鲸。

現(xiàn)在來(lái)看一個(gè)例子說(shuō)明自動(dòng)時(shí)間字段的寫入

// 新增用戶數(shù)據(jù)
$user       = new User;
$user->name = 'thinkphp';
// 會(huì)自動(dòng)寫入create_time和update_time字段
$user->save();
echo $user->create_time;
echo $user->update_time;
// 更新用戶數(shù)據(jù)
$user->name = 'topthink';
// 會(huì)自動(dòng)更新update_time字段
$user->save();
echo $user->create_time;
echo $user->update_time;

create_timeupdate_time字段的值不需要進(jìn)行設(shè)置,系統(tǒng)會(huì)自動(dòng)寫入擦俐。如果你手動(dòng)進(jìn)行設(shè)置的話脊阴,則不會(huì)觸發(fā)自動(dòng)寫入機(jī)制(也就是說(shuō)不會(huì)進(jìn)行時(shí)間字段的格式轉(zhuǎn)換),你需要按照實(shí)際的字段類型設(shè)置蚯瞧。

如果你的時(shí)間字段類型為整型嘿期,自動(dòng)寫入的時(shí)間字段會(huì)在獲取的時(shí)候自動(dòng)轉(zhuǎn)換為dateFormat屬性設(shè)置的時(shí)間格式,所以不需要再次對(duì)時(shí)間字段進(jìn)行格式化輸出埋合,以免出錯(cuò)备徐。如果不希望自動(dòng)格式化,可以設(shè)置數(shù)據(jù)庫(kù)配置參數(shù)datetime_format 的值為false甚颂。(時(shí)間類型字段則無(wú)需更改設(shè)置)

我們來(lái)改變下時(shí)間字段的輸出格式蜜猾,

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    // 開(kāi)啟時(shí)間字段自動(dòng)寫入 并設(shè)置字段類型為datetime
    protected $autoWriteTimestamp = 'datetime'; 
    protected $dateFormat = 'Y/m/d H:i:s';
}

再來(lái)看輸出的值是否有變化

// 新增用戶數(shù)據(jù)
$user       = new User;
$user->name = 'thinkphp';
// 會(huì)自動(dòng)寫入create_time和update_time字段
$user->save();
echo $user->create_time;
echo $user->update_time;
// 更新用戶數(shù)據(jù)
$user->name = 'topthink';
// 會(huì)自動(dòng)更新update_time字段
$user->save();
echo $user->create_time;
echo $user->update_time;

上面的設(shè)置都是針對(duì)單個(gè)模型的,如果需要設(shè)置全局使用振诬,可以在數(shù)據(jù)庫(kù)配置文件中設(shè)置下面的參數(shù):

// 開(kāi)啟自動(dòng)寫入時(shí)間字段 支持設(shè)置字段類型(同前)
'auto_timestamp' => true,
// 時(shí)間字段取出后的時(shí)間格式
'datetime_format' => 'Y-m-d H:i:s',

如果全局設(shè)置開(kāi)啟時(shí)間字段自動(dòng)寫入后蹭睡,部分模型可以單獨(dú)關(guān)閉,例如:

<?php

namespace app\index\model;

use think\Model;

class Data extends Model
{
    // 關(guān)閉時(shí)間字段自動(dòng)寫入
    protected $autoWriteTimestamp = false; 
}

甚至說(shuō)部分模型的時(shí)間字段名和類型可以單獨(dú)設(shè)置

<?php

namespace app\index\model;

use think\Model;

class Data extends Model
{
    // 關(guān)閉時(shí)間字段自動(dòng)寫入
    protected $autoWriteTimestamp = 'datetime'; 
    // 定義時(shí)間字段名
    protected $createTime = 'create_at';
    protected $updateTime = 'update_at';      
}

在系統(tǒng)自動(dòng)時(shí)間字段之外的其它時(shí)間字段赶么,如果需要自動(dòng)格式輸出肩豁,可以設(shè)置類型轉(zhuǎn)換,這個(gè)下一節(jié)就會(huì)講到辫呻。

數(shù)據(jù)類型轉(zhuǎn)換

自動(dòng)時(shí)間字段寫入只支持創(chuàng)建時(shí)間和更新時(shí)間的自動(dòng)寫入和格式化讀取清钥,如果你的模型有其它時(shí)間字段的話,則可以通過(guò)設(shè)置類型轉(zhuǎn)換來(lái)完成放闺,例如User模型的birthday字段也使用了時(shí)間類型循捺。

前面我們已經(jīng)了解了定義修改器和讀取器的方式來(lái)處理birthday字段,更簡(jiǎn)單的辦法就是上面這種設(shè)置類型轉(zhuǎn)換雄人,免去定義修改器和讀取器的麻煩。

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $type = [
        'birthday'  =>  'datetime:Y/m/d',
    ];   
}

type屬性用于定義類型轉(zhuǎn)換(支持相當(dāng)多的類型,后面會(huì)提及)础钠,'datetime:Y/m/d'表示使用datetime類型恰力,輸出格式為Y/m/d,下面是一段代碼示例旗吁。

$user = User::get(1);
// 輸出 2009/02/14
echo $user->birthday;
$user->birthday = '2017-1-1';
$user->save();
// 輸出 2017/1/1
echo $user->birthday;

類型轉(zhuǎn)換支持的類型設(shè)置包括:

integer

設(shè)置為integer(整型)后踩萎,該字段寫入和輸出的時(shí)候都會(huì)自動(dòng)轉(zhuǎn)換為整型。

float

該字段的值寫入和輸出的時(shí)候自動(dòng)轉(zhuǎn)換為浮點(diǎn)型很钓。

boolean

該字段的值寫入和輸出的時(shí)候自動(dòng)轉(zhuǎn)換為布爾型香府。

array

如果設(shè)置為強(qiáng)制轉(zhuǎn)換為array類型,系統(tǒng)會(huì)自動(dòng)把數(shù)組編碼為json格式字符串寫入數(shù)據(jù)庫(kù)码倦,取出來(lái)的時(shí)候會(huì)自動(dòng)解碼企孩。

object

該字段的值在寫入的時(shí)候會(huì)自動(dòng)編碼為json字符串,輸出的時(shí)候會(huì)自動(dòng)轉(zhuǎn)換為stdclass對(duì)象袁稽。

serialize

指定為序列化類型的話勿璃,數(shù)據(jù)會(huì)自動(dòng)序列化寫入,并且在讀取的時(shí)候自動(dòng)反序列化推汽。

json

指定為json類型的話补疑,數(shù)據(jù)會(huì)自動(dòng)json_encode寫入,并且在讀取的時(shí)候自動(dòng)json_decode處理歹撒。

timestamp

指定為時(shí)間戳字段類型(注意并不是數(shù)據(jù)庫(kù)的timestamp類型莲组,事實(shí)上是int類型)的話,該字段的值在寫入時(shí)候會(huì)自動(dòng)使用strtotime生成對(duì)應(yīng)的時(shí)間戳暖夭,輸出的時(shí)候會(huì)自動(dòng)轉(zhuǎn)換為dateFormat屬性定義的時(shí)間字符串格式锹杈,默認(rèn)的格式為Y-m-d H:i:s,如果希望改變其他格式鳞尔,可以定義如下:

class User extends Model 
{
    protected $dateFormat = 'Y/m/d';
    protected $type = [
        'status'    =>  'integer',
        'score'     =>  'float',
        'birthday'  =>  'timestamp',
    ];
}

或者在類型轉(zhuǎn)換定義的時(shí)候使用:

class User extends Model 
{
    protected $type = [
        'status'    =>  'integer',
        'score'     =>  'float',
        'birthday'  =>  'timestamp:Y/m/d',
    ];
}

然后就可以

$user = User::find(1);
echo $user->birthday; // 2015/5/1

datetime

timestamp類似嬉橙,區(qū)別在于寫入和讀取數(shù)據(jù)的時(shí)候都會(huì)自動(dòng)處理成時(shí)間字符串Y-m-d H:i:s的格式。

PHP5.6版本以下寥假,數(shù)據(jù)庫(kù)查詢的字段返回?cái)?shù)據(jù)類型都是字符串的市框,在做API開(kāi)發(fā)的時(shí)候最好是使用類型轉(zhuǎn)換強(qiáng)制處理下,PHP5.6版本開(kāi)始糕韧,PDO查詢的返回?cái)?shù)據(jù)的字段類型都是實(shí)際的字段類型格式枫振。

數(shù)據(jù)自動(dòng)完成

前面提到的修改器是在設(shè)置數(shù)據(jù)的時(shí)候自動(dòng)觸發(fā),有時(shí)候我們希望數(shù)據(jù)修改是自動(dòng)觸發(fā)的萤彩,類似于自動(dòng)時(shí)間字段一樣(只能自動(dòng)完成固定時(shí)間字段)粪滤,這就是本節(jié)要講解的數(shù)據(jù)自動(dòng)完成的概念。

數(shù)據(jù)自動(dòng)完成是依賴修改器的(和3.2版本區(qū)別很大)雀扶,不支持使用函數(shù)或者其它回調(diào)來(lái)自動(dòng)完成(但可以支持固定值)杖小,足見(jiàn)5.0版本是推崇使用修改器肆汹。

系統(tǒng)支持autoinsertupdate三個(gè)屬性予权,可以分別設(shè)置寫入昂勉、新增和更新的時(shí)候需要進(jìn)行自動(dòng)完成的字段列表(auto屬性包含新增和更新操作),下面是一個(gè)示例:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $auto   = ['active_time'];
    protected $insert = ['reg_ip', 'status' => 1];
    protected $update = []; 
}

定義了數(shù)據(jù)自動(dòng)完成后不需要手動(dòng)設(shè)置屬性扫腺,一旦使用手動(dòng)設(shè)置的話岗照,自動(dòng)完成就會(huì)忽略,以避免產(chǎn)生多次處理的數(shù)據(jù)混亂笆环。

數(shù)據(jù)轉(zhuǎn)換和輸出

數(shù)組轉(zhuǎn)換

模型的查詢返回?cái)?shù)據(jù)都是模型的對(duì)象實(shí)例攒至,其實(shí)模型對(duì)象在數(shù)據(jù)存取方面和數(shù)組幾乎沒(méi)什么差異,不信的話躁劣,你可以測(cè)試下下面的一段代碼:

$user = User::get(1);
echo $user->name;
echo $user['name'];
$user->name = 'test';
$user->save();
echo $user->name;
$user['name'] = 'test2';
$user->save();
echo $user['name'];

即使在模板中輸出迫吐,也可以正常使用:

{$user.name}
{$name.email}

答案是由于模型類think\Model實(shí)現(xiàn)了ArrayAccess接口,所以對(duì)象可以采用數(shù)組的方式訪問(wèn)习绢。

如果是數(shù)據(jù)集查詢渠抹,也是和數(shù)組一樣foreach遍歷后使用

$list = User::all();
foreach ($list as $user) {
    echo $user->name . ':' . $user['name'];
}
// 當(dāng)然如果你需要也可以直接
echo $list[0]->name . ':' . $list[0]['name'];

所以,看起來(lái)模型查詢返回的對(duì)象對(duì)使用上并沒(méi)有任何的影響闪萄。

如果因?yàn)槟撤N原因梧却,必須要轉(zhuǎn)換為數(shù)組的話,可以使用模型類提供的toArray方法败去,用法如下:

$user = User::get(1);
if($user) {
    $data = $user->toArray();
}

如果是數(shù)據(jù)集查詢的話有兩種情況放航,由于默認(rèn)的數(shù)據(jù)集返回結(jié)果的類型是一個(gè)數(shù)組,因此無(wú)法調(diào)用toArray方法圆裕,必須先轉(zhuǎn)成數(shù)據(jù)集對(duì)象然后再使用toArray方法广鳍,系統(tǒng)提供了一個(gè)collection助手函數(shù)實(shí)現(xiàn)數(shù)據(jù)集對(duì)象的轉(zhuǎn)換,代碼如下:

$list = User::all();
if($list) {
    $list = collection($list)->toArray();
}

如果設(shè)置了模型的數(shù)據(jù)集返回類型的話吓妆,則可以簡(jiǎn)化使用

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $resultSetType = 'collection';
}

關(guān)鍵代碼是在模型中添加下面一行定義

protected $resultSetType = 'collection';

設(shè)置后赊时,模型所有的數(shù)據(jù)集查詢返回結(jié)果的類型都是think\model\Collection對(duì)象實(shí)例。

下面的事情就簡(jiǎn)單了行拢,直接使用:

$list = User::all();
$list = $list->toArray();

當(dāng)然toArray方法不僅僅只是轉(zhuǎn)換一個(gè)數(shù)組這么簡(jiǎn)單祖秒,我們可以在轉(zhuǎn)換數(shù)據(jù)的時(shí)候進(jìn)行個(gè)別字段的隱藏和追加,涉及到四個(gè)方法:

方法 說(shuō)明
hidden 設(shè)置隱藏的屬性
visible 設(shè)置輸出的屬性
append 追加額外的(獲取器)屬性
appendRelationAttr 追加額外的關(guān)聯(lián)屬性

這幾個(gè)方法都是針對(duì)模型對(duì)象示例的(數(shù)據(jù)集對(duì)象無(wú)法使用)舟奠,前三個(gè)方法的參數(shù)都是數(shù)組竭缝,而且默認(rèn)會(huì)和模型類的hiddenvisible以及append屬性合并沼瘫。下面是一個(gè)代碼示例:

$user = User::get(1);
$data = $user->hidden(['id'])->toArray();
dump($data);
$data = $user->visible(['name', 'email'])->toArray();
dump($data);
$data = $user->append(['status_text'])->toArray();
dump($data);

hiddenvisible方法設(shè)置的是當(dāng)前模型對(duì)應(yīng)的數(shù)據(jù)表中的字段列表中需要隱藏或者顯示的屬性抬纸,如果不在數(shù)據(jù)表字段列表中,但設(shè)置過(guò)獲取器的話耿戚,可以通過(guò)append方法追加湿故。如果不在字段列表中阿趁,也沒(méi)有設(shè)置過(guò)任何的獲取器,則只能手動(dòng)賦值晓锻,例如:

$user        = User::get(1);
$user->hello = 'thinkphp';
$data        = $user->toArray();
dump($data);

如果已經(jīng)在模型中設(shè)置了hidden歌焦、visible或者append屬性,但臨時(shí)不需要輸出砚哆,可以在方法中傳入第二個(gè)參數(shù)true設(shè)置為覆蓋而不是合并。

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $hidden  = ['id'];
    protected $visible = ['name', 'email'];
    protected $append  = ['level'];

    protected function getLevelAttr($value, $data)
    {
        $score = $data['score'];
        if ($score < 100) {
            $level = 1;
        } elseif ($score < 500) {
            $level = 2;
        } elseif ($score < 2000) {
            $level = 3;
        } elseif ($score < 5000) {
            $level = 4;
        } else {
            $level = 5;
        }
        return $level;
    }
}

下面是測(cè)試代碼:

$user = User::get(1);
$data = $user->hidden(['email'], true)->toArray();
dump($data);
$data = $user->visible(['name'], true)->toArray();
dump($data);
$data = $user->append(['level'], true)->toArray();
dump($data);

appendRelationAttr方法是用于追加關(guān)聯(lián)對(duì)象中的某個(gè)屬性到當(dāng)前模型對(duì)象屬性中屑墨,暫且不表躁锁,會(huì)在關(guān)聯(lián)和聚合章節(jié)中給你詳細(xì)講解用法。

如果是數(shù)據(jù)集查詢的話卵史,只能通過(guò)在模型類中設(shè)置屬性的方式來(lái)設(shè)置需要輸出的屬性战转,同樣是上面的User模型類,用下面的代碼測(cè)試:

$list = User::all();
$data = $list->toArray();
dump($data);

JSON序列化

除了轉(zhuǎn)換為數(shù)組數(shù)據(jù)外以躯,還支持對(duì)模型對(duì)象進(jìn)行JSON序列化槐秧,序列化方法為toJson,使用方法和toArray類似忧设,并且調(diào)用toJson序列化之前同樣支持hidden刁标、visibleappendappendRelationAttr方法址晕。

模型事件

雖然說(shuō)模型的數(shù)據(jù)自動(dòng)完成和修改器可以很方便的進(jìn)行數(shù)據(jù)處理膀懈,但也有自身的不足,而模型事件功能則提供了更靈活和強(qiáng)大的模型數(shù)據(jù)處理機(jī)制谨垃。

模型事件可以看成是模型層的鉤子和行為启搂,只不過(guò)鉤子的位置主要針對(duì)模型數(shù)據(jù)的寫入操作,包含下面這些:

鉤子 對(duì)應(yīng)操作 快捷注冊(cè)方法
before_insert 新增前 beforeInsert
after_insert 新增后 afterInsert
before_update 更新前 beforeUpdate
after_update 更新后 afterUpdate
before_write 寫入前 beforeWrite
after_write 寫入后 afterWrite
before_delete 刪除前 beforeDelete
after_delete 刪除后 afterDelete

before_writeafter_write表示無(wú)論是新增還是更新都會(huì)執(zhí)行的鉤子刘陶。

要使用模型事件功能胳赌,就必須先給模型注冊(cè)事件,我們建議在模型類的init方法中統(tǒng)一注冊(cè)模型事件(init靜態(tài)方法會(huì)在實(shí)例化模型的時(shí)候調(diào)用匙隔,并且僅會(huì)執(zhí)行一次)疑苫,下面是一個(gè)例子,在寫入用戶數(shù)據(jù)的時(shí)候牡直。

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected static function init()
    {
        User::event('before_insert', function ($user) {
            $user->reg_ip = request()->ip();
        });
        User::event('before_write', function ($user) {
            $user->name = strtolower($user->name);
        });
    }
}

為了更簡(jiǎn)單的定義缀匕,系統(tǒng)提供了快捷方法,上面的模型事件注冊(cè)可以改為:

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected static function init()
    {
        User::beforeInsert(function ($user) {
            $user->reg_ip = request()->ip();
        });
        User::beforeWrite(function ($user) {
            $user->name = strtolower($user->name);
        });
    }
}

在事件方法中可以對(duì)當(dāng)前模型實(shí)例做任何的操作碰逸,所以理論上來(lái)說(shuō)乡小,修改器和自動(dòng)完成可以做的事情,模型的事件方法都可以完成饵史,而且具備修改器和自動(dòng)完成沒(méi)有的優(yōu)勢(shì)满钟,包括:

  • 批量完成(修改器只能針對(duì)某個(gè)字段進(jìn)行修改)胜榔;
  • 支持判斷并自動(dòng)終止模型數(shù)據(jù)寫入操作;
  • 便于統(tǒng)一管理模型數(shù)據(jù)操作湃番;

不要對(duì)一個(gè)模型數(shù)據(jù)同時(shí)使用修改器和模型事件

注冊(cè)的回調(diào)方法的第一個(gè)參數(shù)是當(dāng)前的模型對(duì)象實(shí)例夭织,并且所有before開(kāi)頭的事件方法(包括before_writebefore_insert吠撮、 before_update 尊惰、before_delete)如果返回false,則寫入操作不會(huì)執(zhí)行泥兰,并且也不再執(zhí)行該鉤子位置的后續(xù)事件操作弄屡。

<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function isValid()
    {

    }

    protected static function init()
    {
        User::event('before_insert', function ($user) {
            $user->reg_ip = request()->ip();
        });
        User::event('before_write', function ($user) {
            return $user->isValid();
        });
    }
}

支持給一個(gè)位置注冊(cè)多個(gè)回調(diào)方法,例如:

        User::event('before_insert', function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });
        // 注冊(cè)回調(diào)到beforeInsert函數(shù)
        User::event('before_insert', 'beforeInsert');

當(dāng)同一個(gè)鉤子注冊(cè)了多個(gè)模型事件的話鞋诗,在特殊的情況下膀捷,你可以傳入第二個(gè)參數(shù)為true覆蓋之前注冊(cè)的其它模型事件。

        User::event('before_insert', function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });
        // 注冊(cè)回調(diào)到beforeInsert函數(shù) 并覆蓋前面的
        User::event('before_insert', 'beforeInsert',true);

數(shù)據(jù)分批處理

模型也可以支持對(duì)返回的數(shù)據(jù)分批處理削彬,并且由于返回的是模型對(duì)象全庸,更方便進(jìn)行業(yè)務(wù)操作,例如:

$count = 0;
User::chunk(100, function ($users) use ($event,$count) {
    foreach ($users as $user) {
        // 用戶活動(dòng)報(bào)名
        if ($user->age > 18) {
            $user->sign($event);
            $count++;
            if ($count >= 300) {
                // 超過(guò)300則不再接受報(bào)名
                return false;
            }
        }
    }
});

總結(jié)

數(shù)據(jù)寫入的自動(dòng)處理規(guī)則及優(yōu)先順序如下:

  1. 時(shí)間字段自動(dòng)格式化寫入
  2. 修改器
  3. 類型轉(zhuǎn)換
  4. 模型事件

數(shù)據(jù)讀取的自動(dòng)處理規(guī)則及優(yōu)先順序如下:

  1. 獲取器
  2. 類型轉(zhuǎn)換
  3. 時(shí)間字段自動(dòng)格式化輸出

通過(guò)本章的學(xué)習(xí)融痛,我們基本掌握了模型數(shù)據(jù)的處理和轉(zhuǎn)換壶笼、輸出,下一章我們來(lái)學(xué)習(xí)模型的一些高級(jí)用法酌心。

上一篇:第五章:模型和對(duì)象
下一篇:第七章:模型高級(jí)用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拌消,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子安券,更是在濱河造成了極大的恐慌墩崩,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侯勉,死亡現(xiàn)場(chǎng)離奇詭異鹦筹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)址貌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門铐拐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人练对,你說(shuō)我怎么就攤上這事遍蟋。” “怎么了螟凭?”我有些...
    開(kāi)封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵虚青,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我螺男,道長(zhǎng)棒厘,這世上最難降的妖魔是什么纵穿? 我笑而不...
    開(kāi)封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮奢人,結(jié)果婚禮上谓媒,老公的妹妹穿的比我還像新娘。我一直安慰自己何乎,他們只是感情好句惯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著支救,像睡著了一般宗弯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搂妻,一...
    開(kāi)封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音辕棚,去河邊找鬼欲主。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逝嚎,可吹牛的內(nèi)容都是我干的扁瓢。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼补君,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼引几!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挽铁,我...
    開(kāi)封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伟桅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叽掘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體楣铁,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年更扁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盖腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浓镜,死狀恐怖溃列,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膛薛,我是刑警寧澤听隐,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站相叁,受9級(jí)特大地震影響遵绰,放射性物質(zhì)發(fā)生泄漏辽幌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一椿访、第九天 我趴在偏房一處隱蔽的房頂上張望乌企。 院中可真熱鬧,春花似錦成玫、人聲如沸加酵。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猪腕。三九已至,卻和暖如春钦勘,著一層夾襖步出監(jiān)牢的瞬間陋葡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工彻采, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腐缤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓肛响,卻偏偏與公主長(zhǎng)得像岭粤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子特笋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容