hyperf/doc/zh-tw/db/model.md
marun b6b4141e30 feat(ModelCommand,ModelOptions)
给gen:model增加 --camel-case参数 将生成的property转成驼峰
也可在commands.gen:model 中设置camel_case为true
默认转换驼峰
2020-03-02 15:52:12 +08:00

16 KiB
Raw Blame History

模型

模型元件衍生於 Eloquent ORM,相關操作均可參考 Eloquent ORM 的文件。

建立模型

Hyperf 提供了建立模型的命令,您可以很方便的根據資料表建立對應模型。命令通過 AST 生成模型,所以當您增加了某些方法後,也可以使用指令碼方便的重置模型。

1.1.0 + 版本:

$ php bin/hyperf.php gen:model table_name

1.0.* 版本:

$ php bin/hyperf.php db:model table_name

可選引數如下:

引數 型別 預設值 備註
--pool string default 連線池,指令碼會根據當前連線池配置建立
--path string app/Model 模型路徑
--force-casts bool false 是否強制重置 casts 引數
--prefix string 空字串 表字首
--inheritance string Model 父類
--uses string App\Model\Model 配合 inheritance 使用
--refresh-fillable bool false 是否重新整理 fillable 引數
--table-mapping array [] 為表名 -> 模型增加對映關係 比如 ['users:Account']
--ignore-tables array [] 不需要生成模型的表名 比如 ['users']
--with-comments bool false 是否增加欄位註釋
--camel-case bool false 將欄位轉成駝峰式

對應配置也可以配置到 databases.{pool}.commands.gen:model 中,如下

<?php

declare(strict_types=1);

return [
    'default' => [
        // 忽略其他配置
        'commands' => [
            'gen:model' => [
                'path' => 'app/Model',
                'force_casts' => true,
                'inheritance' => 'Model',
                'uses' => '',
                'refresh_fillable' => true,
                'table_mapping' => [],
                // 注意這裡都是下劃線
                'with_comments' => true,
                'camel_case'    => true,
            ],
        ],
    ],
];

建立的模型如下

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

/**
 * @property $id
 * @property $name
 * @property $gender
 * @property $created_at
 * @property $updated_at
 */
class User extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'user';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['id', 'name', 'gender', 'created_at', 'updated_at'];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = ['id' => 'integer', 'gender' => 'integer'];
}

模型成員變數

引數 型別 預設值 備註
connection string default 資料庫連線
table string 資料表名稱
primaryKey string id 模型主鍵
keyType string int 主鍵型別
fillable array [] 允許被批量賦值的屬性
casts string 資料格式化配置
timestamps bool true 是否自動維護時間戳
incrementing bool true 是否自增主鍵

資料表名稱

如果我們沒有指定模型對應的 table它將使用類的複數形式「蛇形命名」來作為表名。因此在這種情況下Hyperf 將假設 User 模型儲存的是 users 資料表中的資料。你可以通過在模型上定義 table 屬性來指定自定義資料表:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $table = 'user';
}

主鍵

Hyperf 會假設每個資料表都有一個名為 id 的主鍵列。你可以定義一個受保護的 $primaryKey 屬性來重寫約定。

此外Hyperf 假設主鍵是一個自增的整數值,這意味著預設情況下主鍵會自動轉換為 int 型別。如果您希望使用非遞增或非數字的主鍵則需要設定公共的 $incrementing 屬性設定為 false。如果你的主鍵不是一個整數你需要將模型上受保護的 $keyType 屬性設定為 string。

時間戳

預設情況下Hyperf 預期你的資料表中存在 created_atupdated_at 。如果你不想讓 Hyperf 自動管理這兩個列, 請將模型中的 $timestamps 屬性設定為 false

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    public $timestamps = false;
}

如果需要自定義時間戳的格式,在你的模型中設定 $dateFormat 屬性。這個屬性決定日期屬性在資料庫的儲存方式,以及模型序列化為陣列或者 JSON 的格式:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $dateFormat = 'U';
}

如果您需要不希望保持 datetime 格式的儲存,或者希望對時間做進一步的處理,您可以通過在模型內重寫 fromDateTime($value) 方法實現。

如果你需要自定義儲存時間戳的欄位名,可以在模型中設定 CREATED_ATUPDATED_AT 常量的值來實現,其中一個為 null,則表明不希望 ORM 處理該欄位:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    const CREATED_AT = 'creation_date';

    const UPDATED_AT = 'last_update';
}

資料庫連線

預設情況下Hyperf 模型將使用你的應用程式配置的預設資料庫連線 default。如果你想為模型指定一個不同的連線,設定 $connection 屬性:當然,connection-name 作為 key,必須在 databases.php 配置檔案中存在。

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $connection = 'connection-name';
}

預設屬性值

如果要為模型的某些屬性定義預設值,可以在模型上定義 $attributes 屬性:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $attributes = [
        'delayed' => false,
    ];
}

模型查詢

<?php
use App\Model\User;

/** @var User $user */
$user = User::query()->where('id', 1)->first();
$user->name = 'Hyperf';
$user->save();

重新載入模型

你可以使用 freshrefresh 方法重新載入模型。 fresh 方法會重新從資料庫中檢索模型。現有的模型例項不受影響:

<?php
use App\Model\User;

/** @var User $user */
$user = User::query()->find(1);

$freshUser = $user->fresh();

refresh 方法使用資料庫中的新資料重新賦值現有模型。此外,已經載入的關係會被重新載入:

<?php
use App\Model\User;

/** @var User $user */
$user = User::query()->where('name','Hyperf')->first();

$user->name = 'Hyperf2';

$user->refresh();

echo $user->name; // Hyperf

集合

對於模型中的 allget 方法可以查詢多個結果,返回一個 Hyperf\Database\Model\Collection 例項。 Collection 類提供了很多輔助函式來處理查詢結果:

$users = $users->reject(function ($user) {
    // 排除所有已刪除的使用者
    return $user->deleted;
});

檢索單個模型

除了從指定的資料表檢索所有記錄外,你可以使用 findfirst 方法來檢索單條記錄。這些方法返回單個模型例項,而不是返回模型集合:

<?php
use App\Model\User;

$user = User::query()->where('id', 1)->first();

$user = User::query()->find(1);

檢索多個模型

當然 find 的方法不止支援單個模型。

<?php
use App\Model\User;

$users = User::query()->find([1, 2, 3]);

『未找到』異常

有時你希望在未找到模型時丟擲異常,這在控制器和路由中非常有用。
findOrFailfirstOrFail 方法會檢索查詢的第一個結果,如果未找到,將丟擲 Hyperf\Database\Model\ModelNotFoundException 異常:

<?php
use App\Model\User;

$model = User::findOrFail(1);
$model = User::where('age', '>', 18)->firstOrFail();

聚合函式

你還可以使用 查詢構造器 提供的 countsum, max, 和其他的聚合函式。這些方法只會返回適當的標量值而不是一個模型例項:

<?php
use App\Model\User;

$count = User::query()->where('gender', 1)->count();

插入 & 更新模型

插入

要往資料庫新增一條記錄,先建立新模型例項,給例項設定屬性,然後呼叫 save 方法:

use App\Model\User;

/** @var User $user */
$user = new User();

$user->name = 'Hyperf';

$user->save();

在這個示例中,我們賦值給了 App\Model\User 模型例項的 name 屬性。當呼叫 save 方法時,將會插入一條新記錄。 created_atupdated_at 時間戳將會自動設定,不需要手動賦值。

更新

save 方法也可以用來更新資料庫已經存在的模型。更新模型,你需要先檢索出來,設定要更新的屬性,然後呼叫 save 方法。同樣, updated_at 時間戳會自動更新,所以也不需要手動賦值:

use App\Model\User;

/** @var User $user */
$user = User::query()->find(1);

$user->name = 'Hi Hyperf';

$user->save();

批量更新

也可以更新匹配查詢條件的多個模型。在這個示例中,所有的 gender1 的使用者,修改 gender_show 為 男性:

use App\Model\User;

User::query()->where('gender', 1)->update(['gender_show' => '男性']);

批量更新時, 更新的模型不會觸發 saved 和 updated 事件。因為在批量更新時,從不會去檢索模型。

批量賦值

你也可以使用 create 方法來儲存新模型,此方法會返回模型例項。不過,在使用之前,你需要在模型上指定 fillableguarded 屬性,因為所有的模型都預設不可進行批量賦值。

當用戶通過 HTTP 請求傳入一個意外的引數,並且該引數更改了資料庫中你不需要更改的欄位時。比如:惡意使用者可能會通過 HTTP 請求傳入 is_admin 引數,然後將其傳給 create 方法,此操作能讓使用者將自己升級成管理員。

所以,在開始之前,你應該定義好模型上的哪些屬性是可以被批量賦值的。你可以通過模型上的 $fillable 屬性來實現。 例如:讓 User 模型的 name 屬性可以被批量賦值:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $fillable = ['name'];
}

一旦我們設定好了可以批量賦值的屬性,就可以通過 create 方法插入新資料到資料庫中了。 create 方法將返回儲存的模型例項:

use App\Model\User;

$user = User::create(['name' => 'Hyperf']);

如果你已經有一個模型例項,你可以傳遞一個數組給 fill 方法來賦值:

$user->fill(['name' => 'Hyperf']);

保護屬性

$fillable 可以看作批量賦值的「白名單」, 你也可以使用 $guarded 屬性來實現。 $guarded 屬性包含的是不允許批量賦值的陣列。也就是說, $guarded 從功能上將更像是一個「黑名單」。注意:你只能使用 $fillable$guarded 二者中的一個,不可同時使用。下面這個例子中,除了 gender_show 屬性,其他的屬性都可以批量賦值:

<?php

declare(strict_types=1);

namespace App\Model;

use Hyperf\DbConnection\Model\Model;

class User extends Model
{
    protected $guarded = ['gender_show'];
}

其他建立方法

firstOrCreate / firstOrNew

這裡有兩個你可能用來批量賦值的方法: firstOrCreatefirstOrNew

firstOrCreate 方法會通過給定的 列 / 值 來匹配資料庫中的資料。如果在資料庫中找不到對應的模型, 則會從第一個引數的屬性乃至第二個引數的屬性中建立一條記錄插入到資料庫。

firstOrNew 方法像 firstOrCreate 方法一樣嘗試通過給定的屬性查詢資料庫中的記錄。不同的是,如果 firstOrNew 方法找不到對應的模型,會返回一個新的模型例項。注意 firstOrNew 返回的模型例項尚未儲存到資料庫中,你需要手動呼叫 save 方法來儲存:

<?php
use App\Model\User;

// 通過 name 來查詢使用者,不存在則建立...
$user = User::firstOrCreate(['name' => 'Hyperf']);

// 通過 name 查詢使用者,不存在則使用 name 和 gender, age 屬性建立...
$user = User::firstOrCreate(
    ['name' => 'Hyperf'],
    ['gender' => 1, 'age' => 20]
);

//  通過 name 查詢使用者,不存在則建立一個例項...
$user = User::firstOrNew(['name' => 'Hyperf']);

// 通過 name 查詢使用者,不存在則使用 name 和 gender, age 屬性建立一個例項...
$user = User::firstOrNew(
    ['name' => 'Hyperf'],
    ['gender' => 1, 'age' => 20]
);

刪除模型

可以在模型例項上呼叫 delete 方法來刪除例項:

use App\Model\User;

$user = User::query()->find(1);

$user->delete();

通過查詢刪除模型

您可通過在查詢上呼叫 delete 方法來刪除模型資料,在這個例子中,我們將刪除所有 gender1 的使用者。與批量更新一樣,批量刪除不會為刪除的模型啟動任何模型事件:

use App\Model\User;

// 注意使用 delete 方法時必須建立在某些查詢條件基礎之上才能安全刪除資料,不存在 where 條件,會導致刪除整個資料表
User::query()->where('gender', 1)->delete(); 

通過主鍵直接刪除資料

在上面的例子中,在呼叫 delete 之前需要先去資料庫中查詢對應的模型。事實上,如果你知道了模型的主鍵,您可以直接通過 destroy 靜態方法來刪除模型資料,而不用先去資料庫中查詢。 destroy 方法除了接受單個主鍵作為引數之外,還接受多個主鍵,或者使用陣列,集合來儲存多個主鍵:

use App\Model\User;

User::destroy(1);

User::destroy([1,2,3]);

軟刪除

除了真實刪除資料庫記錄,Hyperf 也可以「軟刪除」模型。軟刪除的模型並不是真的從資料庫中刪除了。事實上,是在模型上設定了 deleted_at 屬性並將其值寫入資料庫。如果 deleted_at 值非空,代表這個模型已被軟刪除。如果要開啟模型軟刪除功能,你需要在模型上使用 Hyperf\Database\Model\SoftDeletes trait

SoftDeletes trait 會自動將 deleted_at 屬性轉換成 DateTime / Carbon 例項

<?php

namespace App\Model;

use Hyperf\Database\Model\Model;
use Hyperf\Database\Model\SoftDeletes;

class User extends Model
{
    use SoftDeletes;
}