接 上篇 。
插入 & 更新 Model
插入
向資料庫插入一條資料,可以採用這樣的方式:建立一個模型例項、為例項設定屬性,然後呼叫 save
方法:
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
/**
* Create a new flight instance.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
在上面的例子裡,我們把從 HTTP 請求裡接受到的 name
引數賦值給了 App\Flight
模型例項物件的 name
屬性。當 save
方法呼叫後,一條資料就被插入資料庫了,而且 created_at
和 updated_at
欄位也會被自動更新。
更新
save
方法還可以用來更新 已存在於資料庫中的資料。在更新模型資料前,先要獲得模型例項物件,然後設定要更新的欄位內容,就可以呼叫 save
方法更新資料了, updated_at
時間戳欄位會被自動更新:
$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();
批量更新
update
方法可以用在批量更新資料庫記錄。在下面的例子裡,所有以 San Diego
作為 destination
的、並且是 active
的航班,都要延遲起飛了:
App\Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update
方法裡列出的欄位就是要更新的欄位。
當通過這種方式批量更新資料時,不會觸發 saved
和 updated
事件。因為資料是直接在資料庫更新的,沒有通過取得、然後再更新的方式。
批量賦值
也可以用 create
方法儲存模型例項物件,該方法會返回被插入的模型例項物件。但有前提的,前提是要在 Model 裡設定 fillable
或者 guarded
屬性。因為預設 Eloquent Model 是不允許批量賦值的。
批量賦值漏洞發生在使用者通過 HTTP 請求傳遞了非預期的欄位引數,並且那個欄位引數對應到你的資料庫裡的那個欄位,是你不想讓使用者更改的。例如,一個惡毒的使用者可能通過 HTTP 請求傳遞了 is_admin
引數,而你呢,是使用 create
方法來建立使用者的,這就相當於使用者把自己改為超級管理員了,這很危險。
$fillable
屬性
所以,你需要在 Model 中設定哪些欄位是允許批量賦值的。可以通過設定 Model 上的 $fillable
屬性實現。例如,在 Flight
Model 上將 name
屬性設定為可批量賦值的:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}
設定好後,我們就可以用 create
方法向資料庫插入資料了。 create
方法返回儲存的 Model 例項物件:
$flight = App\Flight::create(['name' => 'Flight 10']);
如果已有了一個 Model 例項,通過 fill
方法的(陣列型別的)引數可以填充 Model 資料:
$flight->fill(['name' => 'Flight 22']);
$guarded
屬性
$fillable
屬性相當於批量賦值的「白名單」,而 $guarded
屬性呢就相當於一個「黑名單」。在一個 Model 裡,不可以同時設定 $fillable
和 $guarded
屬性。在下面的例子中,除了 price
欄位,其他都是可批量賦值的欄位:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['price'];
}
如果要讓所有的屬性都是可以批量賦值的,那麼將 $guarded
屬性設定為空陣列即可:
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
其他的建立方法
firstOrCreate
/ firstOrNew
這兒還有兩個使用了批量賦值建立 Model 的方法:firstOrCreate
和 firstOrNew
。firstOrCreate
方法用給到的欄位鍵值去資料庫查詢是否有匹配的記錄,沒有匹配記錄的話,就會將給出的欄位鍵值資料插入到資料庫表中。
firstOrNew
方法與 firstOrCreate
類似,也會去資料庫查詢是否有匹配的記錄,不同的是,如果沒有匹配記錄的話,就會建立並返回一個新的 Model 例項。需要注意的是,通過 firstOrNew
方法返回的 Model 例項不會插入到資料庫中,如果需要插入到資料庫,就需要額外呼叫 save
方法:
// Retrieve flight by name, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve flight by name, or create it with the name and delayed attributes...
$flight = App\Flight::firstOrCreate(
['name' => 'Flight 10'], ['delayed' => 1]
);
// Retrieve by name, or instantiate...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
// Retrieve by name, or instantiate with the name and delayed attributes...
$flight = App\Flight::firstOrNew(
['name' => 'Flight 10'], ['delayed' => 1]
);
updateOrCreate
有時你也會遇到這樣的情況:更新一個 Model 時,如果 Model 不存在就建立它。用 updateOrCreate
一步就可以做到。類似 firstOrCreate
方法,updateOrCreate
會持久化 Model,所以無需手動呼叫 save()
:
// If there's a flight from Oakland to San Diego, set the price to $99.
// If no matching model exists, create one.
$flight = App\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99]
);
刪除 Model
在 Model 例項上呼叫 delete
方法刪除它:
$flight = App\Flight::find(1);
$flight->delete();
通過主鍵刪除
在已知主鍵的情況下,可以直接刪除,而無需先獲得。這時,使用 destory
方法:
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);
刪除一組 Model
當然,也可以在一組 Model 上執行 delete 語句。 在下面的例子中,我們會刪除所有 inactive 的航班。類似批量更新,批量刪除不會觸發模型事件 deleting
和 deleted
。
$deletedRows = App\Flight::where('active', 0)->delete();
軟刪除
“軟刪除”並不是把資料記錄從資料庫中真的刪除,而是在 deleted_at
時間戳欄位上設定了值,當這個欄位的值不是 null
時,我們認為這條記錄就是被刪除了。 為了讓一個 Model 支援軟刪除,需要讓 Model 引入 Illuminate\Database\Eloquent\SoftDeletes
這個 trait,並且將 deleted_at
欄位加入到 $dates
屬性中:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model
{
use SoftDeletes;
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['deleted_at'];
}
當然,在資料庫表裡也要新增 deleted_at
這個欄位。Laravel 的 schema 構造器中提供了建立這個欄位的方法:
Schema::table('flights', function ($table) {
$table->softDeletes();
});
現在,當你呼叫 delete
方法刪除 Model 時,delete_at
欄位就會被設定為當前執行刪除操作時的時間,當用查詢獲得資料時,被軟刪除的資料記錄會被自動排除在外,不顯示。
判斷一個 Model 例項是否被刪除,使用 trashed
方法:
if ($flight->trashed()) {
//
}
查詢軟刪除 Model
包括軟刪除資料
之前提到過,查詢時,被軟刪除的資料記錄會被自動排除在外。你也可以強制查詢結果裡包含軟刪除資料,需要在查詢上用到 withTrashed
方法:
$flights = App\Flight::withTrashed()
->where('account_id', 1)
->get();
withTrashed
方法也可以用在關聯查詢上:
$flight->history()->withTrashed()->get();
只獲取軟刪除資料
onlyTrashed
方法只返回軟刪除資料:
$flights = App\Flight::onlyTrashed()
->where('airline_id', 1)
->get();
還原軟刪除 Model
還原軟刪除 Model,需要使用 restore
方法:
$flight->restore();
也可以使用 restore
方法還原多條資料,不過就像其他的“批量”操作,不會觸發任何模型事件:
App\Flight::withTrashed()
->where('airline_id', 1)
->restore();
類似 withTrashed
方法,restore
方法也可以用在關聯方法上:
$flight->history()->restore();
徹底刪除 Model
如果是真的要把記錄從資料庫刪除,要用到 forceDelete
方法:
// Force deleting a single model instance...
$flight->forceDelete();
// Force deleting all related models...
$flight->history()->forceDelete();
查詢範圍
全域性查詢範圍
全域性查詢範圍是給一個 Model 的所有查詢新增約束條件。Laravel 自身的軟刪除功能就是利用了全域性查詢範圍,只從資料庫中獲得“未刪除”的 Model。自定義全域性查詢範圍可以更方便、快捷地給指定 Model 的所有查詢新增特定約束。
寫全域性查詢範圍
查詢範圍類需要實現 Illuminate\Database\Eloquent\Scope
介面,這個介面裡需要實現一個方法:apply
。apply
方法可以給查詢新增必要的 where
約束條件:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('age', '>', 200);
}
}
注意,如果在全域性查詢範圍類裡的查詢要使用 select 子句,你應該用 addSelect
方法而不是 select
方法。這將防止無意中替換現有查詢中的 select 子句。
使用全域性約束
想在指定 Model 上使用全域性查詢範圍,可以通過重寫 Model 的 boot
方法實現,在方法內部,再呼叫 addGlobalScope
方法:
<?php
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
}
新增查詢範圍後,執行 User::all()
,產生的 SQL 語句如下:
select * from `users` where `age` > 200
匿名全域性查詢範圍
Eloquent 也允許使用閉包的方式定義全域性查詢範圍,這對於不想用單獨類的簡單查詢範圍特別有用。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function (Builder $builder) {
$builder->where('age', '>', 200);
});
}
}
刪除全域性查詢範圍
如果對於一個給定的查詢,不需要使用全域性查詢範圍,那就要用 withoutGlobalScope
方法了,該方法接受全域性查詢範圍類作為它的唯一引數:
User::withoutGlobalScope(AgeScope::class)->get();
如果要刪除幾個或者全部的全域性查詢範圍,就需要用 withoutGlobalScopes
方法了:
// Remove all of the global scopes...
User::withoutGlobalScopes()->get();
// Remove some of the global scopes...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
本地查詢範圍
本地查詢範圍就是在 Model 中定義的,以方法形式呈現,方法名以 scope
開頭。Scope 方法應該總是返回一個查詢構造器例項:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include popular users.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* Scope a query to only include active users.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
使用本地查詢範圍
Scope 方法定義好後,在查詢 Model 的時候就可以使用了。但是使用的時候,就不需要加上 scope
字首了。也可以鏈式呼叫不同的 Scope 方法,例如:
$users = App\User::popular()->active()->orderBy('created_at')->get();
動態查詢範圍
有時,需要定義一個接受引數的 Scope 方法。這也很簡單,在 $query
之後,新增我們希望接受的引數即可:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
現在,你就可以在呼叫 Scope 方法的時候傳遞引數過去就行了:
$users = App\User::ofType('admin')->get();
事件
Eloquent Model 在整個生命週期內,提供了幾個不同的事件,方便你在對應的關鍵點上新增處理邏輯:creating
、created
、updating
、updated
、saving
、saved
、deleting
、deleted
、restoring
和 restored
。這些事件允許你每次在資料庫中儲存或更新特定的 Model 類時輕鬆執行程式碼。
當一個新的 Model 第一次儲存的時候,會觸發 creating
和 created
事件。當同一個 Model 已存在於資料庫中,並且呼叫了 save
方法,那麼就會觸發 updating
/ updated
事件。而且這兩種情況,都會觸發 saving
/ saved
事件,事件的呼叫順序依次如下:
第一次儲存:saving → creating → created → saved
使用 `save()` 更新已有資料:saving → updating → updated → saved
在你的 Eloquent Model 中定義一個 $events
屬性為 Eloquent Model 在整個生命週期的事件點上指定事件處理邏輯的一個對映表,這裡需要用到 事件類。
<?php
namespace App;
use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The event map for the model.
*
* @var array
*/
protected $events = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
觀察者
如果在一個 Model 中需要監聽的事件很多,那麼這時就可以用“觀察者”將所有監聽邏輯組織在一個類中。觀察者類中方法名對應事件名。每個事件接受 Model 例項作為他們的唯一引數。Laravel 沒有為觀察者預設建立資料夾,所以你可能要為你的觀察者建立存放它們的資料夾了。在下面的例子中,觀察者放在了 app
目錄下的 Observers
資料夾下:
<?php
namespace App\Observers;
use App\User;
class UserObserver
{
/**
* Listen to the User created event.
*
* @param User $user
* @return void
*/
public function created(User $user)
{
//
}
/**
* Listen to the User deleting event.
*
* @param User $user
* @return void
*/
public function deleting(User $user)
{
//
}
}
是在服務提供者的 boot
方法裡註冊觀察者的,使用的是 Model 的 observer
方法。在下面的例子裡,我們將觀察者註冊在了 AppServiceProvider
中:
<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
}