快速開始
webman模型 基于 Eloquent ORM 。每個(gè)數(shù)據(jù)庫表都有一個(gè)對(duì)應(yīng)的「模型」用來與該表交互。你可以通過模型查詢數(shù)據(jù)表中的數(shù)據(jù),以及在數(shù)據(jù)表中插入新記錄。
在開始之前,請(qǐng)確保配置了 config/database.php
中配置數(shù)據(jù)庫連接。
注意:Eloquent ORM 要支持模型觀察者需要額外導(dǎo)入
composer require "illuminate/events"
例子
示例
<?php
namespace app\model;
use support\Model;
class User extends Model
{
/**
* 與模型關(guān)聯(lián)的表名
*
* @var string
*/
protected $table = 'user';
/**
* 重定義主鍵,默認(rèn)是id
*
* @var string
*/
protected $primaryKey = 'uid';
/**
* 指示是否自動(dòng)維護(hù)時(shí)間戳
*
* @var bool
*/
public $timestamps = false;
}
表名
你可以通過在模型上定義 table 屬性來指定自定義數(shù)據(jù)表:
class User extends Model
{
/**
* 與模型關(guān)聯(lián)的表名
*
* @var string
*/
protected $table = 'user';
}
主鍵
Eloquent 也會(huì)假設(shè)每個(gè)數(shù)據(jù)表都有一個(gè)名為 id 的主鍵列。你可以定義一個(gè)受保護(hù)的 $primaryKey 屬性來重寫約定。
class User extends Model
{
/**
* 重定義主鍵,默認(rèn)是id
*
* @var string
*/
protected $primaryKey = 'uid';
}
Eloquent 假設(shè)主鍵是一個(gè)自增的整數(shù)值,這意味著默認(rèn)情況下主鍵會(huì)自動(dòng)轉(zhuǎn)換為 int 類型。如果您希望使用非遞增或非數(shù)字的主鍵則需要設(shè)置公共的 $incrementing 屬性設(shè)置為 false
class User extends Model
{
/**
* 指示模型主鍵是否遞增
*
* @var bool
*/
public $incrementing = false;
}
如果你的主鍵不是一個(gè)整數(shù),你需要將模型上受保護(hù)的 $keyType 屬性設(shè)置為 string:
class User extends Model
{
/**
* 自動(dòng)遞增ID的“類型”。
*
* @var string
*/
protected $keyType = 'string';
}
時(shí)間戳
默認(rèn)情況下,Eloquent 預(yù)期你的數(shù)據(jù)表中存在 created_at 和 updated_at 。如果你不想讓 Eloquent 自動(dòng)管理這兩個(gè)列, 請(qǐng)將模型中的 $timestamps 屬性設(shè)置為 false:
class User extends Model
{
/**
* 指示是否自動(dòng)維護(hù)時(shí)間戳
*
* @var bool
*/
public $timestamps = false;
}
如果需要自定義時(shí)間戳的格式,在你的模型中設(shè)置 $dateFormat 屬性。這個(gè)屬性決定日期屬性在數(shù)據(jù)庫的存儲(chǔ)方式,以及模型序列化為數(shù)組或者 JSON 的格式:
class User extends Model
{
/**
* 時(shí)間戳存儲(chǔ)格式
*
* @var string
*/
protected $dateFormat = 'U';
}
如果你需要自定義存儲(chǔ)時(shí)間戳的字段名,可以在模型中設(shè)置 CREATED_AT 和 UPDATED_AT 常量的值來實(shí)現(xiàn):
class User extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'last_update';
}
數(shù)據(jù)庫連接
認(rèn)情況下,Eloquent 模型將使用你的應(yīng)用程序配置的默認(rèn)數(shù)據(jù)庫連接。如果你想為模型指定一個(gè)不同的連接,設(shè)置 $connection 屬性:
class User extends Model
{
/**
* 模型的連接名稱
*
* @var string
*/
protected $connection = 'connection-name';
}
默認(rèn)屬性值
如果要為模型的某些屬性定義默認(rèn)值,可以在模型上定義 $attributes 屬性:
class User extends Model
{
/**
* 模型的默認(rèn)屬性值。
*
* @var array
*/
protected $attributes = [
'delayed' => false,
];
}
模型檢索
創(chuàng)建模型和 它關(guān)聯(lián)的數(shù)據(jù)庫表 后,你就可以從數(shù)據(jù)庫中查詢數(shù)據(jù)了。將每個(gè) Eloquent 模型想象成一個(gè)強(qiáng)大的 查詢構(gòu)造器 ,你可以用它更快速的查詢與其相關(guān)聯(lián)的數(shù)據(jù)表。例如:
$users = app\model\User::all();
foreach ($users as $user) {
echo $user->name;
}
提示:因?yàn)?Eloquent 模型也是查詢構(gòu)造器,所以你也應(yīng)當(dāng)閱讀 查詢構(gòu)造器 可用的所有方法。你可以在 Eloquent 查詢中使用這些方法。
附加約束
Eloquent 的 all 方法會(huì)返回模型中所有的結(jié)果。由于每個(gè) Eloquent 模型都充當(dāng)一個(gè) 查詢構(gòu)造器,所以你也可以添加查詢條件,然后使用 get 方法獲取查詢結(jié)果:
$users = app\model\User::where('name', 'like', '%tom')
->orderBy('uid', 'desc')
->limit(10)
->get();
重新加載模型
你可以使用 fresh 和 refresh 方法重新加載模型。 fresh 方法會(huì)重新從數(shù)據(jù)庫中檢索模型?,F(xiàn)有的模型實(shí)例不受影響:
$user = app\model\User::where('name', 'tom')->first();
$fresh_user = $user->fresh();
refresh 方法使用數(shù)據(jù)庫中的新數(shù)據(jù)重新賦值現(xiàn)有模型。此外,已經(jīng)加載的關(guān)系會(huì)被重新加載:
$user = app\model\User::where('name', 'tom')->first();
$user->name = 'jerry';
$user = $user->fresh();
$user->name; // "tom"
集合
Eloquent 的 all 和 get 方法可以查詢到多個(gè)結(jié)果,返回一個(gè) Illuminate\Database\Eloquent\Collection
實(shí)例。Collection
類提供了大量的輔助函數(shù)來處理 Eloquent 結(jié)果:
$users = $users->reject(function ($user) {
return $user->disabled;
});
使用游標(biāo)
cursor 方法允許你使用游標(biāo)遍歷數(shù)據(jù)庫,它只執(zhí)行一次查詢。處理大量的數(shù)據(jù)時(shí), cursor 方法可以大大減少內(nèi)存的使用量:
foreach (app\model\User::where('sex', 1')->cursor() as $user) {
//
}
cursor 返回 Illuminate\Support\LazyCollection
實(shí)例。 Lazy collections 允許你使用 Laravel 集合中大多數(shù)集合方法,而且每次只會(huì)加載單個(gè)模型到內(nèi)存中:
$users = app\model\User::cursor()->filter(function ($user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
Selects 子查詢
Eloquent 提供了高級(jí)子查詢支持,你可以用單條查詢語句從相關(guān)表中提取信息。舉個(gè)例子,假設(shè)我們有一個(gè)目的地表 destinations 和一個(gè)到目的地的航班表 flights。flights 表包含一個(gè) arrival_at 字段,表示航班何時(shí)到達(dá)目的地。
使用子查詢功能提供的 select 和 addSelect 方法,我們可以用單條語句查詢?nèi)磕康牡?destinations,以及抵達(dá)各目的地最后一班飛機(jī)的名稱:
use app\model\Destination;
use app\model\Flight;
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderBy('arrived_at', 'desc')
->limit(1)
])->get();
根據(jù)子查詢進(jìn)行排序
此外,查詢構(gòu)建器的 orderBy 函數(shù)也支持子查詢。我們可以使用此功能根據(jù)最后一班航班到達(dá)目的地的時(shí)間對(duì)所有目的地排序。 同樣,這可以只對(duì)數(shù)據(jù)庫執(zhí)行單個(gè)查詢:
return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderBy('arrived_at', 'desc')
->limit(1)
)->get();
檢索單個(gè)模型 / 集合
除了從指定的數(shù)據(jù)表檢索所有記錄外,你可以使用 find、 first 或 firstWhere 方法來檢索單條記錄。這些方法返回單個(gè)模型實(shí)例,而不是返回模型集合:
// 通過主鍵查找一個(gè)模型...
$flight = app\model\Flight::find(1);
// 查找符合查詢條件的首個(gè)模型...
$flight = app\model\Flight::where('active', 1)->first();
// 查找符合查詢條件的首個(gè)模型的快速實(shí)現(xiàn)...
$flight = app\model\Flight::firstWhere('active', 1);
你也可以使用主鍵數(shù)組作為參數(shù)調(diào)用 find 方法,它將返回匹配記錄的集合:
$flights = app\model\Flight::find([1, 2, 3]);
有時(shí)你可能希望在查找首個(gè)結(jié)果但找不到值時(shí)執(zhí)行其他動(dòng)作。firstOr 方法將會(huì)在查找到結(jié)果時(shí)返回首個(gè)結(jié)果,如果沒有結(jié)果,將會(huì)執(zhí)行給定的回調(diào)?;卣{(diào)的返回值將會(huì)作為 firstOr 方法的返回值:
$model = app\model\Flight::where('legs', '>', 100)->firstOr(function () {
// ...
});
firstOr 方法同樣接受欄位數(shù)組來查詢:
$model = app\modle\Flight::where('legs', '>', 100)
->firstOr(['id', 'legs'], function () {
// ...
});
「未找到」異常
有時(shí)你希望在未找到模型時(shí)拋出異常。這在控制器和路由中非常有用。 findOrFail 和 firstOrFail 方法會(huì)檢索查詢的第一個(gè)結(jié)果,如果未找到,將拋出 Illuminate\Database\Eloquent\ModelNotFoundException 異常:
$model = app\modle\Flight::findOrFail(1);
$model = app\modle\Flight::where('legs', '>', 100)->firstOrFail();
檢索集合
你還可以使用 查詢構(gòu)造器 提供的 count、 sum 和 max 方法,和其他的集合函數(shù) 來操作集合。這些方法只會(huì)返回適當(dāng)?shù)臉?biāo)量值而不是一個(gè)模型實(shí)例:
$count = app\modle\Flight::where('active', 1)->count();
$max = app\modle\Flight::where('active', 1)->max('price');
插入
要往數(shù)據(jù)庫新增一條記錄,先創(chuàng)建新模型實(shí)例,給實(shí)例設(shè)置屬性,然后調(diào)用 save 方法:
<?php
namespace app\controller;
use app\model\User;
use support\Request;
use support\Response;
class FooController
{
/**
* 在用戶表中添加一條新的記錄
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 驗(yàn)證請(qǐng)求
$user = new User;
$user->name = $request->get('name');
$user->save();
}
}
created_at 和 updated_at 時(shí)間戳將會(huì)自動(dòng)設(shè)置(模型中$timestamps屬性為true時(shí)),不需要手動(dòng)賦值。
更新
save 方法也可以用來更新數(shù)據(jù)庫已經(jīng)存在的模型。更新模型,你需要先檢索出來,設(shè)置要更新的屬性,然后調(diào)用 save 方法。同樣, updated_at 時(shí)間戳?xí)詣?dòng)更新,所以也不需要手動(dòng)賦值:
$user = app\model\User::find(1);
$user->name = 'jerry';
$user->save();
批量更新
app\model\User::where('uid', '>', 10)
->update(['name' => 'tom'']);
檢查屬性變化
Eloquent 提供了 isDirty, isClean 和 wasChanged 方法,以檢查模型的內(nèi)部狀態(tài)并確定其屬性從最初加載時(shí)如何變化。
isDirty 方法確定自加載模型以來是否已更改任何屬性。 您可以傳遞特定的屬性名稱來確定特定的屬性是否變臟。isClean 方法與 isDirty 相反,它也接受可選的屬性參數(shù):
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->save();
$user->isDirty(); // false
$user->isClean(); // true
wasChanged 方法確定在當(dāng)前請(qǐng)求周期內(nèi)最后一次保存模型時(shí)是否更改了任何屬性。 你還可以傳遞屬性名稱以查看特定屬性是否已更改:
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->save();
$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged('first_name'); // false
批量賦值
你也可以使用 create 方法來保存新模型。 此方法會(huì)返回模型實(shí)例。 不過,在使用之前,你需要在模型上指定 fillable 或 guarded 屬性,因?yàn)樗械?Eloquent 模型都默認(rèn)不可進(jìn)行批量賦值。
當(dāng)用戶通過請(qǐng)求傳入意外的 HTTP 參數(shù),并且該參數(shù)更改了數(shù)據(jù)庫中你不需要更改的字段時(shí),就會(huì)發(fā)生批量賦值漏洞。 比如:惡意用戶可能會(huì)通過 HTTP 請(qǐng)求傳入 is_admin 參數(shù),然后將其傳給 create 方法,此操作能讓用戶將自己升級(jí)成管理員。
所以,在開始之前,你應(yīng)該定義好模型上的哪些屬性是可以被批量賦值的。你可以通過模型上的 $fillable 屬性來實(shí)現(xiàn)。 例如:讓 Flight 模型的 name 屬性可以被批量賦值:
<?php
namespace app\model;
use support\Model;
class Flight extends Model
{
/**
* 可以被批量賦值的屬性。
*
* @var array
*/
protected $fillable = ['name'];
}
一旦我們設(shè)置好了可以批量賦值的屬性,就可以通過 create 方法插入新數(shù)據(jù)到數(shù)據(jù)庫中了。 create 方法將返回保存的模型實(shí)例:
$flight = app\modle\Flight::create(['name' => 'Flight 10']);
如果你已經(jīng)有一個(gè)模型實(shí)例,你可以傳遞一個(gè)數(shù)組給 fill 方法來賦值:
$flight->fill(['name' => 'Flight 22']);
$fillable 可以看作批量賦值的「白名單」, 你也可以使用 $guarded 屬性來實(shí)現(xiàn)。 $guarded 屬性包含的是不允許批量賦值的數(shù)組。也就是說, $guarded 從功能上將更像是一個(gè)「黑名單」。注意:你只能使用 $fillable 或 $guarded 二者中的一個(gè),不可同時(shí)使用。下面這個(gè)例子中,price 屬性之外的所有屬性都可以進(jìn)行批量賦值:
<?php
namespace app\model;
use support\Model;
class Flight extends Model
{
/**
* 不可批量賦值的屬性。
*
* @var array
*/
protected $guarded = ['price'];
}
如果你想讓所有屬性都可以批量賦值, 你可以將 $guarded 定義成一個(gè)空數(shù)組:
/**
* 不可批量賦值的屬性。
*
* @var array
*/
protected $guarded = [];
其他創(chuàng)建方法
firstOrCreate/ firstOrNew
這里有兩個(gè)你可能用來批量賦值的方法: firstOrCreate 和 firstOrNew。 firstOrCreate 方法會(huì)通過給定的鍵 / 值對(duì)來匹配數(shù)據(jù)庫中的數(shù)據(jù)。如果在數(shù)據(jù)庫中找不到模型,則將插入一條記錄,其中包含第一個(gè)參數(shù)的屬性以及可選的第二個(gè)參數(shù)的屬性。
firstOrNew 方法像 firstOrCreate 方法一樣嘗試通過給定的屬性查找數(shù)據(jù)庫中的記錄。不過,如果 firstOrNew 方法找不到對(duì)應(yīng)的模型,會(huì)返回一個(gè)新的模型實(shí)例。注意 firstOrNew 返回的模型實(shí)例尚未保存到數(shù)據(jù)庫中,你需要手動(dòng)調(diào)用 save 方法來保存:
// 通過 name 檢索航班,不存在則創(chuàng)建...
$flight = app\modle\Flight::firstOrCreate(['name' => 'Flight 10']);
// 通過 name 檢索航班,或使用 name 和 delayed 屬性和 arrival_time 屬性創(chuàng)建...
$flight = app\modle\Flight::firstOrCreate(
['name' => 'Flight 10'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// 通過 name 檢索航班,不存在則創(chuàng)建一個(gè)實(shí)例...
$flight = app\modle\Flight::firstOrNew(['name' => 'Flight 10']);
// 通過 name 檢索航班,或使用 name 和 delayed 屬性和 arrival_time 屬性創(chuàng)建一個(gè)模型實(shí)例...
$flight = app\modle\Flight::firstOrNew(
['name' => 'Flight 10'],
['delayed' => 1, 'arrival_time' => '11:30']
);
你還可能遇到希望更新現(xiàn)有模型或在不存在的情況下則創(chuàng)建新的模型的情景。 updateOrCreate 方法來一步實(shí)現(xiàn)。 類似于 firstOrCreate 方法,updateOrCreate 持久化模型,因此無需調(diào)用 save():
// 如果有從奧克蘭到圣地亞哥的航班,則價(jià)格定為99美元。
// 如果沒匹配到存在的模型,則創(chuàng)建一個(gè)。
$flight = app\modle\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
刪除模型
可以在模型實(shí)例上調(diào)用 delete 方法來刪除實(shí)例:
$flight = app\modle\Flight::find(1);
$flight->delete();
通過主鍵刪除模型
app\modle\Flight::destroy(1);
app\modle\Flight::destroy(1, 2, 3);
app\modle\Flight::destroy([1, 2, 3]);
app\modle\Flight::destroy(collect([1, 2, 3]));
通過查詢刪除模型
$deletedRows = app\modle\Flight::where('active', 0)->delete();
復(fù)制模型
您可以使用 replicate 方法復(fù)制一個(gè)新的未保存到數(shù)據(jù)庫的實(shí)例, 當(dāng)模型實(shí)例共享許多相同的屬性時(shí),這個(gè)方法非常好用。
$shipping = App\Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
$billing->save();
模型比較
有時(shí)可能需要判斷兩個(gè)模型是否「相同」。is 方法可以用來快速校驗(yàn)兩個(gè)模型是否擁有相同的主鍵、表和數(shù)據(jù)庫連接:
if ($post->is($anotherPost)) {
//
}
模型觀察者
注意:Eloquent ORM 要支持模型觀察者需要額外導(dǎo)入composer require "illuminate/events"
<?php
namespace app\model;
use support\Model;
use app\observer\UserObserver;
class User extends Model
{
public static function boot()
{
parent::boot();
static::observe(UserObserver::class);
}
}