Laravel 自帶的 Eloquent ORM 提供了一個美觀、簡單的與數(shù)據(jù)庫打交道的 ActiveRecord 實現(xiàn),每張數(shù)據(jù)表都對應一個與該表進行交互的“模型”,模型允許你在表中進行數(shù)據(jù)查詢,以及插入、更新、刪除等操作。
在開始之前,確保在 config/database.php
文件中配置好了數(shù)據(jù)庫連接。更多關(guān)于數(shù)據(jù)庫配置的信息,請查看文檔。
作為開始,讓我們創(chuàng)建一個 Eloquent 模型,模型通常位于 app
目錄下,你也可以將其放在其他可以被 composer.json
文件自動加載的地方。所有 Eloquent 模型都繼承自 Illuminate\Database\Eloquent\Model
類。
創(chuàng)建模型實例最簡單的辦法就是使用 Artisan 命令 make:model
:
php artisan make:model User
如果你想要在生成模型時生成數(shù)據(jù)庫遷移,可以使用--migration
或-m
選項:
php artisan make:model User --migration
php artisan make:model User -m
現(xiàn)在,讓我們來看一個 Flight
模型類例子,我們將用該類獲取和存取數(shù)據(jù)表 flights
中的信息:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
//
}
注意我們并沒有告訴 Eloquent 我們的 Flight
模型使用哪張表。默認規(guī)則是模型類名的復數(shù)作為與其對應的表名,除非在模型類中明確指定了其它名稱。所以,在本例中,Eloquent 認為 Flight
模型存儲記錄在 flights
表中。你也可以在模型中定義 table
屬性來指定自定義的表名:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 關(guān)聯(lián)到模型的數(shù)據(jù)表
*
* @var string
*/
protected $table = 'my_flights';
}
Eloquent 默認每張表的主鍵名為 id
,你可以在模型類中定義一個$primaryKey
屬性來覆蓋該約定。
默認情況下,Eloquent 期望 created_at
和 updated_at
已經(jīng)存在于數(shù)據(jù)表中,如果你不想要這些 Laravel 自動管理的列,在模型類中設置$timestamps
屬性為 false
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 表明模型是否應該被打上時間戳
*
* @var bool
*/
public $timestamps = false;
}
如果你需要自定義時間戳格式,設置模型中的$dateFormat
屬性。該屬性決定日期被如何存儲到數(shù)據(jù)庫中,以及模型被序列化為數(shù)組或 JSON 時日期的格式:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 模型日期列的存儲格式
*
* @var string
*/
protected $dateFormat = 'U';
}
創(chuàng)建完模型及其關(guān)聯(lián)的數(shù)據(jù)表后,就要準備從數(shù)據(jù)庫中獲取數(shù)據(jù)。將 Eloquent 模型看功能強大的查詢構(gòu)建器,你可以使用它來流暢的查詢與其關(guān)聯(lián)的數(shù)據(jù)表。例如:
<?php
namespace App\Http\Controllers;
use App\Flight;
use App\Http\Controllers\Controller;
class FlightController extends Controller{
/**
* 顯示所有有效航班列表
*
* @return Response
*/
public function index()
{
$flights = Flight::all();
return view('flight.index', ['flights' => $flights]);
}
}
如果你有一個 Eloquent 模型實例,可以通過訪問其相應的屬性來訪問模型的列值。例如,讓我們循環(huán)查詢返回的每一個 Flight
實例并輸出 name
的值:
foreach ($flights as $flight) {
echo $flight->name;
}
Eloquent 的 all
方法返回模型表的所有結(jié)果,由于每一個 Eloquent 模型都是一個查詢構(gòu)建器,你還可以添加約束條件到查詢,然后使用 get 方法獲取對應結(jié)果:
$flights = App\Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
注意:由于 Eloquent 模型本質(zhì)上就是查詢構(gòu)建器,你可以在 Eloquent 查詢中使用查詢構(gòu)建器的所有方法。
對 Eloquent 中獲取多個結(jié)果的方法(比如 all
和get
)而言,其返回值是 Illuminate\Database\Eloquent\Collection
的一個實例,Collection
類提供了多個有用的函數(shù)來處理 Eloquent 結(jié)果。當然,你可以像操作數(shù)組一樣簡單循環(huán)這個集合:
foreach ($flights as $flight) {
echo $flight->name;
}
如果你需要處理成千上萬個 Eloquent 結(jié)果,可以使用 chunk
命令。chunk
方法會獲取一個“組塊”的 Eloquent 模型,并將其填充到給定閉包進行處理。使用 chunk
方法能夠在處理大量數(shù)據(jù)集合時有效減少內(nèi)存消耗:
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
傳遞給該方法的第一個參數(shù)是你想要獲取的“組塊”數(shù)目,閉包作為第二個參數(shù)被調(diào)用用于處理每個從數(shù)據(jù)庫獲取的區(qū)塊數(shù)據(jù)。
當然,除了從給定表中獲取所有記錄之外,還可以使用 find
和 first
獲取單個記錄。這些方法返回單個模型實例而不是返回模型集合:
// 通過主鍵獲取模型...
$flight = App\Flight::find(1);
// 獲取匹配查詢條件的第一個模型...
$flight = App\Flight::where('active', 1)->first();
Not Found 異常
有時候你可能想要在模型找不到的時候拋出異常,這在路由或控制器中非常有用,findOrFail
和 firstOrFail
方法會獲取查詢到的第一個結(jié)果。然而,如果沒有任何查詢結(jié)果,Illuminate\Database\Eloquent\ModelNotFoundException
異常將會被拋出:
$model = App\Flight::findOrFail(1);$model = App\Flight::where('legs', '>', 100)->firstOrFail();
如果異常沒有被捕獲,那么 HTTP 404 響應將會被發(fā)送給用戶,所以在使用這些方法的時候沒有必要對返回 404 響應編寫明確的檢查:
Route::get('/api/flights/{id}', function ($id) {
return App\Flight::findOrFail($id);
});
當然,你還可以使用查詢構(gòu)建器聚合方法,例如 count
、sum
、max
,以及其它查詢構(gòu)建器提供的聚合方法。這些方法返回計算后的結(jié)果而不是整個模型實例:
$count = App\Flight::where('active', 1)->count();
$max = App\Flight::where('active', 1)->max('price');
想要在數(shù)據(jù)庫中插入新的記錄,只需創(chuàng)建一個新的模型實例,設置模型的屬性,然后調(diào)用 save
方法:
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller{
/**
* 創(chuàng)建一個新的航班實例
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
}
在這個例子中,我們只是簡單分配 HTTP 請求中的 name
參數(shù)值給 App\Flight
模型實例的那么屬性,當我們調(diào)用 save
方法時,一條記錄將會被插入數(shù)據(jù)庫。created_at
和 updated_at
時間戳在 save
方法被調(diào)用時會自動被設置,所以沒必要手動設置它們。
save
方法還可以用于更新數(shù)據(jù)庫中已存在的模型。要更新一個模型,應該先獲取它,設置你想要更新的屬性,然后調(diào)用 save
方法。同樣,updated_at
時間戳會被自動更新,所以沒必要手動設置其值:
$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();
更新操作還可以同時修改給定查詢提供的多個模型實例,在本例中,所有有效且 destination=San Diego
的航班都被標記為延遲:
App\Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update
方法要求以數(shù)組形式傳遞鍵值對參數(shù),代表著數(shù)據(jù)表中應該被更新的列。
還可以使用 create
方法保存一個新的模型。該方法返回被插入的模型實例。但是,在此之前,你需要指定模型的 fillable
或 guarded
屬性,因為所有 Eloquent 模型都通過 mass-assignment
進行保護。
當用戶通過 HTTP 請求傳遞一個不被期望的參數(shù)值時就會出現(xiàn) mass-assignment
隱患,然后該參數(shù)以不被期望的方式修改數(shù)據(jù)庫中的列值。例如,惡意用戶通過 HTTP 請求發(fā)送一個 is_admin
參數(shù),然后該參數(shù)映射到模型的 create
方法,從而允許用戶將自己變成管理員。
所以,作為開始,你應該定義模型屬性中哪些是可以進行賦值的,使用模型上的$fillable
屬性即可實現(xiàn)。例如,我們設置 Flight
模型上的 name
屬性可以被賦值:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 可以被批量賦值的屬性.
*
* @var array
*/
protected $fillable = ['name'];
}
設置完可以被賦值的屬性之后,我們就可以使用 create
方法在數(shù)據(jù)庫中插入一條新的記錄。create
方法返回保存后的模型實例:
$flight = App\Flight::create(['name' => 'Flight 10']);
$fillable
就像是可以被賦值屬性的“白名單”,還可以選擇使用$guarded
。$guarded
屬性包含你不想被賦值的屬性數(shù)組。所以不被包含在其中的屬性都是可以被賦值的,因此,$guarded 方法就像|“黑名單”。當然,你只能同時使用其中一個——而不是一起使用:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 不能被批量賦值的屬性
*
* @var array
*/
protected $guarded = ['price'];
}
在這個例子中,除了$price
之外的所有屬性都是可以被賦值的。
還有其它兩種可以用來創(chuàng)建模型的方法:firstOrCreate
和 firstOrNew
。firstOrCreate
方法先嘗試通過給定列/值對在數(shù)據(jù)庫中查找記錄,如果沒有找到的話則通過給定屬性創(chuàng)建一個新的記錄。
firstOrNew
方法和 firstOrCreate
方法一樣先嘗試在數(shù)據(jù)庫中查找匹配的記錄,如果沒有找到,則返回一個的模型實例。注意通過 firstOrNew
方法返回的模型實例并沒有持久化到數(shù)據(jù)庫中,你還需要調(diào)用 save
方法手動持久化:
// 通過屬性獲取航班, 如果不存在則創(chuàng)建...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// 通過屬性獲取航班, 如果不存在初始化一個新的實例...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
要刪除一個模型,調(diào)用模型實例上的 delete
方法:
$flight = App\Flight::find(1);
$flight->delete();
在上面的例子中,我們在調(diào)用 delete
方法之前從數(shù)據(jù)庫中獲取該模型,然而,如果你知道模型的主鍵的話,可以直接刪除而不需要獲取它:
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);
當然,你還可以通過查詢刪除多個模型,在本例中,我們刪除所有被標記為無效的航班:
$deletedRows = App\Flight::where('active', 0)->delete();
除了從數(shù)據(jù)庫刪除記錄外,Eloquent 還可以對模型進行“軟刪除”。當模型被軟刪除后,它們并沒有真的從數(shù)據(jù)庫刪除,而是在模型上設置一個 deleted_at
屬性并插入數(shù)據(jù)庫,如果模型有一個非空 deleted_at
值,那么該模型已經(jīng)被軟刪除了。要啟用模型的軟刪除功能,可以使用模型上的 Illuminate\Database\Eloquent\SoftDeletestrait
并添加 deleted_at
列到$dates
屬性:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model{
use SoftDeletes;
/**
* 應該被調(diào)整為日期的屬性
*
* @var array
*/
protected $dates = ['deleted_at'];
}
當然,應該添加 deleted_at
列到數(shù)據(jù)表。Laravel 查詢構(gòu)建器包含一個幫助函數(shù)來創(chuàng)建該列:
Schema::table('flights', function ($table) {
$table->softDeletes();
});
現(xiàn)在,當調(diào)用模型的delete
方法時,deleted_at
列將被設置為當前日期和時間,并且,當查詢一個使用軟刪除的模型時,被軟刪除的模型將會自動從查詢結(jié)果中排除。
判斷給定模型實例是否被軟刪除,可以使用 trashed
方法:
if ($flight->trashed()) {
//
}
正如上面提到的,軟刪除模型將會自動從查詢結(jié)果中排除,但是,如果你想要軟刪除模型出現(xiàn)在查詢結(jié)果中,可以使用 withTrashed
方法:
$flights = App\Flight::withTrashed()
->where('account_id', 1)
->get();
withTrashed
方法也可以用于關(guān)聯(lián)查詢中:
$flight->history()->withTrashed()->get();
onlyTrashed
方法之獲取軟刪除模型:
$flights = App\Flight::onlyTrashed()
->where('airline_id', 1)
->get();
有時候你希望恢復一個被軟刪除的模型,可以使用 restore
方法:
$flight->restore();
你還可以在查詢中使用 restore
方法來快速恢復多個模型:
App\Flight::withTrashed()
->where('airline_id', 1)
->restore();
和 withTrashed
方法一樣,restore
方法也可以用于關(guān)系查詢:
$flight->history()->restore();
有時候你真的需要從數(shù)據(jù)庫中刪除一個模型,可以使用 forceDelete
方法:
// 強制刪除單個模型實例...
$flight->forceDelete();
// 強制刪除所有關(guān)聯(lián)模型...
$flight->history()->forceDelete();
作用域允許你定義一個查詢條件的通用集合,這樣就可以在應用中方便地復用。例如,你需要頻繁獲取最受歡迎的用戶,要定義一個作用域,只需要簡單的在 Eloquent 模型方法前加上一個 scope
前綴:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 只包含活躍用戶的查詢作用域
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* 只包含激活用戶的查詢作用域
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
作用域被定義好了之后,就可以在查詢模型的時候調(diào)用作用域方法,但調(diào)用時不需要加上 scope
前綴,你甚至可以在同時調(diào)用多個作用域,例如:
$users = App\User::popular()->women()->orderBy('created_at')->get();
有時候你可能想要定義一個可以接收參數(shù)的作用域,你只需要將額外的參數(shù)添加到你的作用域即可。作用域參數(shù)應該被定義在$query
參數(shù)之后:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
/**
* 只包含給用類型用戶的查詢作用域
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
現(xiàn)在,你可以在調(diào)用作用域時傳遞參數(shù)了:
$users = App\User::ofType('admin')->get();
Eloquent 模型可以觸發(fā)事件,允許你在模型生命周期中的多個時間點調(diào)用如下這些方法:creating
, created
, updating
, updated
, saving
, saved
,deleting
, deleted
, restoring
, restored
。事件允許你在一個指定模型類每次保存或更新的時候執(zhí)行代碼。
一個新模型被首次保存的時候,creating
和 created
事件會被觸發(fā)。如果一個模型已經(jīng)在數(shù)據(jù)庫中存在并調(diào)用 save/方法
,updating/updated 事件會被觸發(fā)
。
舉個例子,我們在服務提供者中定義一個 Eloquent 事件監(jiān)聽器,在事件監(jiān)聽器中,我們會調(diào)用給定模型的 isValid
方法,如果模型無效會返回 false
。如果從 Eloquent 事件監(jiān)聽器中返回 false
則取消 save/update
操作:
<?php
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 啟動所有應用服務
*
* @return void
*/
public function boot()
{
User::creating(function ($user) {
if ( ! $user->isValid()) {
return false;
}
});
}
/**
* 注冊服務提供者.
*
* @return void
*/
public function register()
{
//
}
}