hyperf/docs/zh-hk/db/model.md
2021-03-08 09:09:58 +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 Hyperf\DbConnection\Model\Model 配合 inheritance 使用
--refresh-fillable bool false 是否刷新 fillable 參數
--table-mapping array [] 為表名 -> 模型增加映射關係 比如 ['users:Account']
--ignore-tables array [] 不需要生成模型的表名 比如 ['users']
--with-comments bool false 是否增加字段註釋
--property-case int 0 字段類型 0 蛇形 1 駝峯

當使用 --property-case 將字段類型轉化為駝峯時,還需要手動在模型中加入 Hyperf\Database\Model\Concerns\CamelCase

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

中劃線都需要轉化為下劃線

<?php

declare(strict_types=1);

use Hyperf\Database\Commands\ModelOption;

return [
    'default' => [
        // 忽略其他配置
        'commands' => [
            'gen:model' => [
                'path' => 'app/Model',
                'force_casts' => true,
                'inheritance' => 'Model',
                'uses' => '',
                'refresh_fillable' => true,
                'table_mapping' => [],
                'with_comments' => true,
                'property_case' => ModelOption::PROPERTY_SNAKE_CASE,
            ],
        ],
    ],
];

創建的模型如下

<?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' => '男性']);

批量更新時, 更新的模型不會觸發 savedupdated 事件。因為在批量更新時,並沒有實例化模型。同時,也不會執行相應的 casts,例如數據庫中 json 格式,在 Model 類中 casts 字段標記為 array,若是用批量更新,則插入時不會自動將 array 轉換為 json 字符串格式。

批量賦值

你也可以使用 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;
}