[TOC]
### **1、簡介**
[Laravel](http://laravelacademy.org/tags/laravel "View all posts in Laravel")?自帶的?[Eloquent](http://laravelacademy.org/tags/eloquent "View all posts in Eloquent")?[ORM](http://laravelacademy.org/tags/orm "View all posts in ORM")?提供了一個美觀、簡單的與[數據庫](http://laravelacademy.org/tags/%e6%95%b0%e6%8d%ae%e5%ba%93 "View all posts in 數據庫")打交道的?ActiveRecord?實現,每張數據表都對應一個與該表進行交互的“[模型](http://laravelacademy.org/tags/%e6%a8%a1%e5%9e%8b "View all posts in 模型")”,模型允許你在表中進行數據[查詢](http://laravelacademy.org/tags/%e6%9f%a5%e8%af%a2 "View all posts in 查詢"),以及[插入](http://laravelacademy.org/tags/%e6%8f%92%e5%85%a5 "View all posts in 插入")、[更新](http://laravelacademy.org/tags/%e6%9b%b4%e6%96%b0 "View all posts in 更新")、[刪除](http://laravelacademy.org/tags/%e5%88%a0%e9%99%a4 "View all posts in 刪除")等操作。
在開始之前,確保在`config/database.php`文件中配置好了數據庫連接。更多關于數據庫配置的信息,請查看[文檔](http://laravelacademy.org/post/2942.html#ipt_kb_toc_2942_1)。
### **2、定義模型**
作為開始,讓我們創建一個 Eloquent 模型,模型通常位于`app`目錄下,你也可以將其放在其他可以被`composer.json`文件自動加載的地方。所有Eloquent模型都繼承自?`Illuminate\Database\Eloquent\Model`類。
創建模型實例最簡單的辦法就是使用 Artisan 命令`make:model`:
~~~
php artisan make:model User
~~~
如果你想要在生成模型時生成[數據庫遷移](http://laravelacademy.org/post/2965.html),可以使用`--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{
//
}
~~~
**表名**
注意我們并沒有告訴 Eloquent 我們的`Flight`模型使用哪張表。默認規則是模型類名的復數作為與其對應的表名,除非在模型類中明確指定了其它名稱。所以,在本例中,Eloquent 認為`Flight`模型存儲記錄在`flights`表中。你也可以在模型中定義`table`屬性來指定自定義的表名:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 關聯到模型的數據表
*
* @var string
*/
protected $table = 'my_flights';
}
~~~
**主鍵**
Eloquent 默認每張表的主鍵名為`id`,你可以在模型類中定義一個`$primaryKey`屬性來覆蓋該約定。
**時間戳**
默認情況下,Eloquent 期望`created_at`和`updated_at`已經存在于數據表中,如果你不想要這些 Laravel 自動管理的列,在模型類中設置`$timestamps`屬性為`false`:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 表明模型是否應該被打上時間戳
*
* @var bool
*/
public $timestamps = false;
}
~~~
如果你需要自定義時間戳格式,設置模型中的`$dateFormat`屬性。該屬性決定日期被如何存儲到數據庫中,以及模型被序列化為數組或 JSON 時日期的格式:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 模型日期列的存儲格式
*
* @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';
}
~~~
### **3、獲取多個模型**
創建完模型及其關聯的數據表后,就要準備從數據庫中獲取數據。將Eloquent模型看作功能強大的[查詢構建器](http://laravelacademy.org/post/2956.html),你可以使用它來流暢的查詢與其關聯的數據表。例如:
~~~
<?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 模型實例,可以通過訪問其相應的屬性來訪問模型的列值。例如,讓我們循環查詢返回的每一個`Flight`實例并輸出`name`的值:
~~~
foreach ($flights as $flight) {
echo $flight->name;
}
~~~
#### **添加額外約束**
Eloquent 的`all`方法返回模型表的所有結果,由于每一個Eloquent模型都是一個[查詢構建器](http://laravelacademy.org/post/2956.html),你還可以添加約束條件到查詢,然后使用`get`方法獲取對應結果:
~~~
$flights = App\Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
~~~
> 注意:由于 Eloquent 模型本質上就是查詢構建器,你可以在Eloquent查詢中使用查詢構建器的所有方法。
#### **集合**
對 Eloquent 中獲取多個結果的方法(比如`all`和`get`)而言,其返回值是`Illuminate\Database\Eloquent\Collection`的一個實例,`Collection`類提供了多個有用的函數來處理Eloquent結果。當然,你可以像操作數組一樣簡單循環這個集合:
~~~
foreach ($flights as $flight) {
echo $flight->name;
}
~~~
#### **組塊結果集**
如果你需要處理成千上萬個 Eloquent 結果,可以使用`chunk`命令。`chunk`方法會獲取一個“組塊”的 Eloquent 模型,并將其填充到給定閉包進行處理。使用`chunk`方法能夠在處理大量數據集合時有效減少內存消耗:
~~~
Flight::chunk(200, function ($flights) {
foreach ($flights as $flight) {
//
}
});
~~~
傳遞給該方法的第一個參數是你想要獲取的“組塊”數目,閉包作為第二個參數被調用用于處理每個從數據庫獲取的區塊數據。
### **4、獲取單個模型/聚合**
當然,除了從給定表中獲取所有記錄之外,還可以使用`find`和`first`獲取單個記錄。這些方法返回單個模型實例而不是返回模型集合:
~~~
// 通過主鍵獲取模型...
$flight = App\Flight::find(1);
// 獲取匹配查詢條件的第一個模型...
$flight = App\Flight::where('active', 1)->first();
~~~
**Not Found 異常**
有時候你可能想要在模型找不到的時候拋出異常,這在路由或控制器中非常有用,`findOrFail`和`firstOrFail`方法會獲取查詢到的第一個結果。然而,如果沒有任何查詢結果,`Illuminate\Database\Eloquent\ModelNotFoundException`異常將會被拋出:
~~~
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();
~~~
如果異常沒有被捕獲,那么HTTP?404?響應將會被發送給用戶,所以在使用這些方法的時候沒有必要對返回404響應編寫明確的檢查:
~~~
Route::get('/api/flights/{id}', function ($id) {
return App\Flight::findOrFail($id);
});
~~~
#### **獲取聚合**
當然,你還可以使用查詢構建器聚合方法,例如`count`、`sum`、`max`,以及其它查詢構建器提供的[聚合方法](http://laravelacademy.org/post/2956.html#ipt_kb_toc_2956_6)。這些方法返回計算后的結果而不是整個模型實例:
~~~
$count?=?App\Flight::where('active',?1)->count();
$max?=?App\Flight::where('active',?1)->max('price');
~~~
### **5、插入/更新模型**
#### **基本插入**
想要在數據庫中插入新的記錄,只需創建一個新的模型實例,設置模型的屬性,然后調用`save`方法:
~~~
<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class FlightController extends Controller{
/**
* 創建一個新的航班實例
*
* @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`模型實例的那么屬性,當我們調用`save`方法時,一條記錄將會被插入數據庫。`created_at`和`updated_at`時間戳在`save`方法被調用時會自動被設置,所以沒必要手動設置它們。
#### **基本更新**
`save`方法還可以用于更新數據庫中已存在的模型。要更新一個模型,應該先獲取它,設置你想要更新的屬性,然后調用`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`方法要求以數組形式傳遞鍵值對參數,代表著數據表中應該被更新的列。
#### **[批量賦值](http://laravelacademy.org/tags/%e6%89%b9%e9%87%8f%e8%b5%8b%e5%80%bc "View all posts in 批量賦值")**
還可以使用`create`方法保存一個新的模型。該方法返回被插入的模型實例。但是,在此之前,你需要指定模型的`fillable`或`guarded`屬性,因為所有Eloquent模型都通過批量賦值(Mass Assignment)進行保護。
當用戶通過 HTTP 請求傳遞一個不被期望的參數值時就會出現安全隱患,然后該參數以不被期望的方式修改數據庫中的列值。例如,惡意用戶通過 HTTP 請求發送一個`is_admin`參數,然后該參數映射到模型的`create`方法,從而允許用戶將自己變成管理員。
所以,你應該在模型中定義哪些屬性是可以進行賦值的,使用模型上的`$fillable`屬性即可實現。例如,我們設置`Flight`模型上的`name`屬性可以被賦值:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 可以被批量賦值的屬性.
*
* @var array
*/
protected $fillable = ['name'];
}
~~~
設置完可以被賦值的屬性之后,我們就可以使用`create`方法在數據庫中插入一條新的記錄。`create`方法返回保存后的模型實例:
~~~
$flight = App\Flight::create(['name' => 'Flight 10']);
~~~
`$fillable`就像是可以被賦值屬性的“白名單”,還可以選擇使用`$guarded`。`$guarded`屬性包含你不想被賦值的屬性數組。所以不被包含在其中的屬性都是可以被賦值的,因此,$guarded方法就像“黑名單”。當然,你只能同時使用其中一個——而不是一起使用:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model{
/**
* 不能被批量賦值的屬性
*
* @var array
*/
protected $guarded = ['price'];
}
~~~
在這個例子中,除了`$price`之外的所有屬性都是可以被賦值的。
**其它創建方法**
還有其它兩種可以用來創建模型的方法:`firstOrCreate`和`firstOrNew`。`firstOrCreate`方法先嘗試通過給定列/值對在數據庫中查找記錄,如果沒有找到的話則通過給定屬性創建一個新的記錄。
`firstOrNew`方法和`firstOrCreate`方法一樣先嘗試在數據庫中查找匹配的記錄,如果沒有找到,則返回一個的模型實例。注意通過`firstOrNew`方法返回的模型實例并沒有持久化到數據庫中,你還需要調用`save`方法手動持久化:
~~~
//?通過屬性獲取航班,?如果不存在則創建...
$flight?=?App\Flight::firstOrCreate(['name'?=>?'Flight?10']);
//?通過屬性獲取航班,?如果不存在初始化一個新的實例...
$flight?=?App\Flight::firstOrNew(['name'?=>?'Flight?10']);
~~~
### **6、刪除模型**
要刪除一個模型,調用模型實例上的`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();
~~~
#### **[軟刪除](http://laravelacademy.org/tags/%e8%bd%af%e5%88%a0%e9%99%a4 "View all posts in 軟刪除")**
除了從數據庫刪除記錄外,Eloquent還可以對模型進行“軟刪除”。當模型被軟刪除后,它們并沒有真的從數據庫刪除,而是在模型上設置一個`deleted_at`屬性并插入數據庫,如果模型有一個非空`deleted_at`值,那么該模型已經被軟刪除了。要啟用模型的軟刪除功能,可以使用模型上的`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;
/**
* 應該被調整為日期的屬性
*
* @var array
*/
protected $dates = ['deleted_at'];
}
~~~
當然,應該添加`deleted_at`列到數據表。Laravel Schema構建器包含一個幫助函數來創建該列:
~~~
Schema::table('flights', function ($table) {
$table->softDeletes();
});
~~~
現在,當調用模型的`delete`方法時,`deleted_at`列將被設置為當前日期和時間,并且,當查詢一個使用軟刪除的模型時,被軟刪除的模型將會自動從查詢結果中排除。
判斷給定模型實例是否被軟刪除,可以使用`trashed`方法:
~~~
if ($flight->trashed()) {
//
}
~~~
#### **查詢被軟刪除的模型**
**包含軟刪除模型**
正如上面提到的,軟刪除模型將會自動從查詢結果中排除,但是,如果你想要軟刪除模型出現在查詢結果中,可以使用`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();
~~~
**恢復軟刪除模型**
有時候你希望恢復一個被軟刪除的模型,可以使用`restore`方法:
~~~
$flight->restore();
~~~
你還可以在查詢中使用`restore`方法來快速恢復多個模型:
~~~
App\Flight::withTrashed()
->where('airline_id', 1)
->restore();
~~~
和`withTrashed`方法一樣,`restore`方法也可以用于關聯查詢:
~~~
$flight->history()->restore();
~~~
**永久刪除模型**
有時候你真的需要從數據庫中刪除一個模型,可以使用`forceDelete`方法:
~~~
// 強制刪除單個模型實例...
$flight->forceDelete();
// 強制刪除所有關聯模型...
$flight->history()->forceDelete();
~~~
### **7、查詢[作用域](http://laravelacademy.org/tags/%e4%bd%9c%e7%94%a8%e5%9f%9f "View all posts in 作用域")**
#### **全局作用域**
全局作用域允許我們為給定模型的所有查詢添加條件約束。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);
? ? }
}
~~~
**應用全局作用域**
要將全局作用域分配給模型,需要重寫給定模型的?`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);
});
? ? }
}
~~~
我們還可以通過以下方式移除全局作用:
~~~
User::withoutGlobalScope('age')->get();
~~~
**移除全局作用域**
如果想要在給定查詢中移除指定全局作用域,可以使用?`withoutGlobalScope`:
~~~
User::withoutGlobalScope(AgeScope::class)->get();
~~~
如果你想要移除某幾個或全部全局作用域,可以使用?`withoutGlobalScopes`?方法:
~~~
User::withoutGlobalScopes()->get();
User::withoutGlobalScopes([FirstScope::class,?SecondScope::class])->get();
~~~
#### **本地作用域**
本地作用域允許我們定義通用的約束集合以便在應用中復用。例如,你可能經常需要獲取最受歡迎的用戶,要定義這樣的一個作用域,只需簡單在對應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);
}
}
~~~
**使用查詢作用域**
作用域被定義好了之后,就可以在查詢模型的時候調用作用域方法,但調用時不需要加上`scope`前綴,你甚至可以在同時調用多個作用域,例如:
~~~
$users?=?App\User::popular()->active()->orderBy('created_at')->get();
~~~
**動態作用域**
有時候你可能想要定義一個可以接收參數的作用域,你只需要將額外的參數添加到你的作用域即可。作用域參數應該被定義在`$query`參數之后:
~~~
<?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);
}
}
~~~
現在,你可以在調用作用域時傳遞參數了:
~~~
$users = App\User::ofType('admin')->get();
~~~
### **8、事件**
Eloquent模型可以觸發事件,允許你在模型生命周期中的多個時間點調用如下這些方法:`creating`,?`created`,?`updating`,?`updated`,?`saving`,?`saved`,`deleting`,?`deleted`,?`restoring`,?`restored`。事件允許你在一個指定模型類每次保存或更新的時候執行代碼。
#### **基本使用**
一個新模型被首次保存的時候,`creating`和`created`事件會被觸發。如果一個模型已經在數據庫中存在并調用`save`方法,`updating/updated`事件會被觸發,無論是創建還是更新,saving/saved事件都會被調用。
舉個例子,我們在[服務提供者](http://laravelacademy.org/post/2900.html)中定義一個Eloquent事件監聽器,在事件監聽器中,我們會調用給定模型的`isValid`方法,如果模型無效會返回`false`。如果從Eloquent事件監聽器中返回`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()
{
//
}
}
~~~
- 序言
- 發行版本說明
- 升級指南
- 貢獻代碼
- 開始
- 安裝
- 配置
- Laravel Homestead
- 基礎
- HTTP 路由
- HTTP 中間件
- HTTP 控制器
- HTTP 請求
- HTTP 響應
- 視圖
- Blade 模板引擎
- 架構
- 一次請求的生命周期
- 應用目錄結構
- 服務提供者
- 服務容器
- 門面(Facades)
- 數據庫
- 起步
- 查詢構建器
- 遷移
- 填充數據
- Eloquent ORM
- 起步
- 關聯關系
- 集合
- 訪問器&修改器
- 序列化
- 服務
- 用戶認證
- 用戶授權
- Artisan Console
- 訂閱支付實現:Laravel Cashier
- 緩存
- 集合
- 集成前端資源:Laravel Elixir
- 加密
- 錯誤&日志
- 事件
- 文件系統/云存儲
- 哈希
- 輔助函數
- 本地化
- 郵件
- 包開發
- 分頁
- Redis
- 隊列
- Session
- Envoy Task Runner
- 任務調度
- 測試
- 驗證
- 新手入門指南
- 簡單任務管理系統
- 帶用戶功能的任務管理系統