Eloquent: 起步
簡介
Laravel 的 Eloquent ORM 提供了一種漂亮簡潔的關系映射的模型來與數據庫進行交互。所有的數據庫表都有相應的模型,這些模型被用來與表進行交互。模型允許你直接查詢數據庫表中的數據,及插入新的記錄到數據表中。
在開始之前,你需要確保完成了 config/database.php
配置文件中的數據庫配置。對于更多的配置數據庫相關的信息,請參考 文檔。
定義模型
在開始之前,讓我們先來創建一個 Eloquent 模型。模型通常存放在 app
目錄下,但是你也可以自由的放置在任何地方,只要它能夠根據你的 composer.json
文件的指導進行自動的加載。所有的 Eloquent 模型都繼承自 Illuminate\Database\Eloquent\Model
類。
最簡單的創建一個模型類的方式就是使用 make:model
Artisan 命令:
php artisan make:model User
如果你希望在你生成模型的同時生成一份數據庫遷移,你可以使用 --migration
或者 -m
選項:
php artisan make:model User --migration
php artisan make:model User -m
Eloquent 模型約定
現在,讓我們來看一個 Flight
模型的示例,我們將通過它獲取和存儲數據庫中 flights
表的數據:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
//
}
表名稱
你需要注意我們上面的代碼中并沒有指出 Flight
模型使用哪個數據庫的表。如果你沒有明確的指出模型所對應的表,那么 Eloquent 將使用類的蛇形命名的復數形式來使用相應的數據表。所以,在這個例子中,Eloquent 將會假定 Flight
模型存儲的記錄被放在 flights
表中。你可以使用 table
屬性來指定表名:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'my_flights';
}
主鍵
Eloquent 也會假定所有表的主鍵列名為 id
。你也可使用 $primaryKey
屬性來手動的指定表的主鍵。
另外,Eloquent 也會假定主鍵是一個自增的整型值,這意味著,在默認情況下主鍵會被自動的轉為 int
。如果你希望使用非自增或者非數字的主鍵,那么你需要在模型中將公開的 $incrementing
屬性設置為 false
。
時間戳
默認的,Eloquent 期望模型表中存在 created_at
和 updated_at
列,如果你不希望 Eloquent 自主的管理這兩列,你可以在模型中設置 $timestamps
屬性為 false
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
如果你需要自定義時間戳的格式,你可以設置 $dateFormat
屬性。這個屬性用來決定日期屬性存儲在數據庫中的格式,以及模型在進行序列化為數組或者 JSON 時的顯示樣式:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The storage format of the model's date columns.
*
* @var string
*/
protected $dateFormat = 'U';
}
數據庫連接
默認的,所有的 Eloquent 模型都會使用應用配置的默認的數據庫連接。如果你希望模型使用不同的數據庫連接,你可以使用 $connection
屬性:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The connection name for the model.
*
* @var string
*/
protected $connection = 'connection-name';
}
檢索多個模型
一旦你創建了模型和其相應的數據表,你就為從數據庫中檢索數據做好了準備。你可以把 Eloquent 模型作為一個強大的查詢生成器來使用,它允許通過模型流暢的查詢相應的表中的數據。比如:
<?php
namespace App\Http\Controllers;
use App\Flight;
use App\Http\Controllers\Controller;
class FlightController extends Controller
{
/**
* Show a list of all available flights.
*
* @return Response
*/
public function index()
{
$flights = Flight::all();
return view('flight.index', ['flights' => $flights]);
}
}
訪問列的值
如果你獲得了 Eloquent 模型的實例,那么你可以通過模型中相應的列名的屬性來訪問表中列的值。比如,讓我們循環查詢的結果,并通過獲取的 Flight
實例來獲得 name
列的值:
foreach ($flights as $flight) {
echo $flight->name;
}
添加額外的條件
Eloquent 的 all
方法會返回模型表中所有的記錄。由于所有的 Eloquent 模型都可以作為查詢生成器來進行服務,所以你可以在這些查詢中增加額外的條件,然后使用 get
方法來檢索結果:
$flights = App\Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
注意:由于 Eloquent 模型也是查詢生成器,所以你可以回顧一下查詢生成器中所有可用的方法,因為它們同樣可以在 Eloquent 中進行使用。
集合
對于像 all
和 get
這樣的 Eloquent 方法會返回多個結果,這將返回一個 Illuminate\Database\Eloquent\Collection
實例。Collection
類提供了各種有用的方法與 Eloquent 結果進行交互。當然,你可以簡單的對集合進行循環,因為他實現了 ArrayAccess 接口:
foreach ($flights as $flight) {
echo $flight->name;
}
對結果進行分塊
如果你需要處理數千條 Eloquent 記錄,那么你可以使用 chunk
方法。chunk
方法會從 Eloquent 模型中檢索出一小塊記錄,然后將其傳遞給 Closure
進行處理。使用 chunk
方法在處理大量結果集時可有有效的降低內存的消耗:
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
傳遞到方法的第一個參數是你要設置進行分塊的大小。而傳遞到第二個參數的閉包將會在從數據庫檢索出每塊內容時進行調用。
檢索單個模型 / 統計
當然,除了可以從表中檢索出所有的記錄之外,你也可以使用 find
和 first
方法來從數據庫中檢索出單條的數據。這將會返回一個單獨的模型實例:
// Retrieve a model by its primary key...
$flight = App\Flight::find(1);
// Retrieve the first model matching the query constraints...
$flight = App\Flight::where('active', 1)->first();
你也可以使用 find
方法時傳遞一個包含主鍵所組成的數組,這將返回所有匹配到記錄的集合:
$flights = App\Flight::find([1, 2, 3]);
未發現時的異常
有時候,你可能希望在沒有找到匹配的模型時拋出一個異常。這對路由或者控制器來說尤其有用。findOrFail
和 firstOrFail
方法可以從查詢中檢索出首個匹配的結果,但是,如果結果中沒有匹配的項,那么會拋出一個 Illuminate\Database\Eloquent\ModelNotFoundException
異常:
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();
如果異常沒有被捕獲,那么會直接傳遞給用戶一個 404
HTTP 響應。所以當你在使用這些方法時是沒有必要編寫明確的檢查并返回 404
響應的:
Route::get('/api/flights/{id}', function ($id) {
return App\Flight::findOrFail($id);
});
檢索統計
當然,你可以使用 count
,sum
,max
和其他 查詢生成器 所提供的的 聚合函數。這些方法會返回適當的數值,而不是完整的模型實例:
$count = App\Flight::where('active', 1)->count();
$max = App\Flight::where('active', 1)->max('price');
插入 & 更新模型
基礎插入
想要在數據庫中插入新的記錄,只需要簡單的創建模型實例,然后設置模型的屬性,再調用 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();
你也可以針對查詢匹配的多個模型進行更新操作。比如,所有 active
并且 destination
為 San Diego
的航班將會被標記為延遲:
App\Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
update
方法接收一個想要更新的列的鍵值對數組。
批量賦值
你也可以使用 create
方法來在一行中存儲一個新的模型。一個新增的模型實例將會從方法中返回。事實上,在開始做這些之前,你需要先指定模型的 fillable
或者 guarded
屬性,它們是針對批量賦值時的保護措施。
當用戶通過 HTTP 請求傳遞一些意外的參數時,這可能會造成批量賦值時一些問題的出現,或許參數中存在一些你并不想改變的列值的參數。比如,一個惡意的用戶可能會在請求中嵌入一個 is_admin
參數,然后這些參數映射到你的 create
方法中,這就使用戶將自己升級為了超級管理員。
所以,在開始之前,你需要先定義哪些模型屬性是可以進行批量分配的。你可以使用模型 $fillable
屬性,讓我們在 Flight
模型中添加允許 name
屬性的批量賦值:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Dlight extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}
一旦我們使一個屬性可以進行批量賦值。那么我們可以使用 create
方法直接的添加記錄到數據中。create
方法會返回存儲后的模型實例:
$flight = App\Flight::create(['name' => 'Flight 10']);
而 $fillable
就像是為批量賦值提供了一種白名單的機制。你也可以選擇使用 $guarded
。$guarded
屬性應該包含一個你不想要進行批量賦值的屬性所組成的數組。所有不在這個數組中的屬性都將可以進行批量賦值。所以,$guarded
就像一個黑名單的功能。當然,你應該只使用 $fillable
或者 $guarded
中的一個,而不是全部:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = ['price'];
}
在上面的例子中,除了 price
,所有的屬性都可以進行批量賦值。
其他創建方法
這里也有其他兩種方法可以使用批量賦值進行模型的創建:firstOrCreate
和 firstOrNew
。firstOrCreate
方法會嘗試根據給定的列的鍵值對從數據庫定位相關的模型。如果相關的模型沒有找到,那么會根據給定的屬性在數據庫中新增一條記錄。
firstOrNew
方法和 firstOrCreate
方法一樣,但是它在模型沒找到時只會返回一個新的模型實例,并不會在數據庫中新增一條記錄。你需要手動的使用 save
方法來將其存儲到數據庫中:
// Retrieve the flight by the attributes, or create it if it doesn't exists...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
刪除模型
你可以在模型實例中使用 delete
方法來刪除模型:
$flight = App\Flight::find(1);
$flight->delete();
根據鍵刪除存在的模型
在上面的例子中,我們先從數據庫中檢索出相關的模型,然后才調用 delete
方法來進行刪除。事實上,如果你知道了模型的主鍵,那么你完全可以不用去檢索到它。你可以直接使用 destroy
方法來進行刪除:
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);
通過查詢刪除模型
當然,你也可以通過查詢刪除一個模型集。在這個例子中,我們將刪除所有標記為未啟用的航班:
$deletedRows = App\Flight::where('active', 0)->delete();
軟刪除
除了從數據庫中真實的刪除數據之外,Eloquent 也可以進行軟刪除操作。當模型是一個軟刪除模型時,它們并不會真正的從數據庫中清除記錄。實際上,它們會將模型的 deleted_at
屬性進行設置,并且將其更新到數據庫中。如果模型中的 deleted_at
的值不是 NULL
,那么它就被標記為軟刪除了。如果你需要啟用軟刪除模型,你需要在模型中引入 Illuminate\Database\Eloquent\SoftDeletes
trait,并且在模型的 $dates
屬性中添加 deleted_at
列:
<?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::table('flights', function ($table) {
$table->softDeletes();
});
現在,當我們調用 delete
方法時,deleted_at
列會被設置為當前時間。并且,當查詢啟用軟刪除的模型時,已經被軟刪除的模型將自動從結果中剔除。
你可以使用 trashed
方法來判斷所給定的模型是否已經被軟刪除:
if ($flight->trashed()) {
//
}
查詢軟刪除的模型
包含軟刪除的模型
就如上面我們所提到的,被軟刪除的模型將自動的從結果中進行剔除。事實上,你可以使用 withTrashed
方法來強制結果中顯示已經被軟刪除的模型:
$flights = App\Flight::withTrashed()
->where('account_id', 1)
->get();
whitTrashed
方法也可以在關聯查詢中進行使用:
$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
方法也可以在關聯查詢中使用:
$flight->history()->restore();
永久的刪除模型
有時候你可能希望從數據庫中直接刪除這個模型。你可以使用 forceDelete
方法來將模型從數據庫中永久的刪除:
// Force deleting a single model instance...
$flight->forceDelete();
// Force deleting all related modls...
$flight->history()->forceDelete();
查詢區間
全局區間
全局區間允許你在給定的模型上的所有查詢進行約束的添加。Laravel 自己的軟刪除功能就是利用了全局區間來從數據庫中拉取未刪除的數據。編寫自己的全局區間可以方便的對給定模型的所有查詢進行約束。
編寫全局區間
編寫一個全局區間非常簡單,你只需要定義一個實現了 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)
{
return $builder->where('age', '>', 200);
}
}
Laravel 沒有為區間預置存放的目錄,所以你可以自由的創建自己的 Scopes
目錄來進行管理。
應用全局區間
你需要在給定的模型中復寫 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()
{
paraent::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);
});
}
}
傳遞到 addGlobalScope()
方法的首個參數將作為區間的唯一標識,你可以通過標識將其排除:
User::withoutGlobalScope('age')->get();
刪除全局區間
如果你希望從給定的查詢中移除全局區間,你可以使用 withoutGlobalScope
方法:
User::withoutGlobalScope(AgeScope::class)->get();
如果你希望刪除多個或者全部的全局區間,你可以使用這么使用 withoutGlobalScopes
方法:
User::withoutGlobalScopes()->get();
User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get();
當前區間
當前區間允許你在模型中定義一些可以復用的常用的約束。比如,你可能經常需要檢索一些受歡迎的用戶。你可以簡單的在模型方法中前置 scope
命名來定義一個區間.
區間應該總是返回一個查詢生成器的實例:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include popular users.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* Scope a query to only include active users.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* Scope a query to only include active users.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
利用區間查詢
一旦區間進行了定義,你可以在模型進行查詢時調用區間方法,事實上,你在調用區間方法時并不需要包含 scope
前綴。你甚至可以鏈式的調用其它的區間:
$users = App\User::popular()->active()->orderBy('created_at')->get();
動態區間
有時候你可能希望定義一個可以接收參數的區間。在開始之前,你僅僅只需要在你的區間中添加一些額外的參數。區間參數應該在 $query
參數之后進行定義:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
現在,你可以在調用區間時傳遞一些參數了:
$users = App\User::ofType('admin')->get();
事件
Eloquent 模型可以觸發多種事件,這允許你在模型的生命周期的各個關鍵點進行 hook 操作。你可以使用下面的方法進行 Hook:creating
,created
,updating
,updated
,saving
,saved
,deleting
,deleted
,restoring
,restored
。事件允許你輕松的在模型進行存儲或更新操作時進行執行額外的操作。
基礎用法
當一個新的模型首次進行存儲操作時,會觸發 creating
和 created
事件。如果模型已經存在于數據庫中,并且調用 save
方法,那么 updating
/ updated
事件將會被觸發。事實上,在這兩種情況下,saving
和 saved
事件都會被觸發。
舉個示例,讓我們在服務提供者中定義一個 Eloquent 事件監聽器。在我們的事件監聽器中,我們將在給定的模型中調用 isValid
方法,當模型并沒有通過驗證時將返回 false
。如果從 Eloquent 事件監聽器中返回 false
,那么將取消 save
/ update
操作:
<?php
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
User::creating(function ($user) {
if (! $user->isValid()) {
return false;
}
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
}