Eloquent ORM
- 介绍
- 基本用法
- Mass Assignment
- 新增,修改,删除
- 软删除( Soft Deleting )
- 时间戳记
- 范围查询
- 全局范围
- 关联
- 关联查询
- 预载入( Eager Loading )
- 新增关联模型
- 更新上层模型时间戳
- 操作枢纽表
- Collections
- 获取器和修改器
- 日期转换器
- 模型事件
- 模型观察者
- 转换数组 / JSON
介绍
Laravel 的 Eloquent ORM 提供了漂亮、简洁的 ActiveRecord 实现来和数据库的互动。 每个数据库表会和一个对应的「模型」互动。
在开始之前,记得把 app/config/database.php
里的数据库连接配置好。
基本用法
我们先从建立一个 Eloquent 模型开始。模型通常放在 app/models
目录下,但是您可以将它们放在任何地方,只要能通过 composer.json
被自动载入。
定义一个 Eloquent 模型
class User extends Eloquent {}
注意我们并没有告诉 Eloquent User
模型会使用哪个数据库表。若没有特别指定,系统会默认自动对应名称为「类名称的小写复数形态」的数据库表。所以,在上面的例子中, Eloquent 会假设 User
将把数据存在 users
数据库表。可以在类里定义 table
属性自定义要对应的数据库表。
class User extends Eloquent {
protected $table = 'my_users';
}
注意: Eloquent 也会假设每个数据库表都有一个字段名称为
id
的主键。您可以在类里定义primaryKey
属性来重写。同样的,您也可以定义connection
属性,指定模型连接到专属的数据库连接。
定义好模型之后,您就可以从数据库表新增及获取数据了。注意在默认情况下,在数据库表里需要有 updated_at
和 created_at
两个字段。如果您不想设定或自动更新这两个字段,则将类里的 $timestamps
属性设为 false
即可。
取出所有模型数据
$users = User::all();
根据主键取出一条数据
$user = User::find(1);
var_dump($user->name);
提示: 所有查询构造器里的方法,查询 Eloquent 模型时也可以使用。
根据主键取出一条数据或抛出异常
有时, 您可能想要在找不到模型数据时抛出异常,以捕捉异常并让 App::error
处理并显示 404 页面。
$model = User::findOrFail(1);
$model = User::where('votes', '>', 100)->firstOrFail();
要注册错误处理,可以监听 ModelNotFoundException
use Illuminate\Database\Eloquent\ModelNotFoundException;
App::error(function(ModelNotFoundException $e)
{
return Response::make('Not Found', 404);
});
Eloquent 模型结合查询语法
$users = User::where('votes', '>', 100)->take(10)->get();
foreach ($users as $user)
{
var_dump($user->name);
}
Eloquent 聚合查询
当然,您也可以使用查询构造器的聚合查询方法。
$count = User::where('votes', '>', 100)->count();
如果没办法使用流畅的接口产生出查询语句,也可以使用 whereRaw
:
$users = User::whereRaw('age > ? and votes = 100', array(25))->get();
拆分查询
如果您要处理非常多(数千条)Eloquent 查询结果,使用 chunk
方法可以让您顺利工作而不会吃掉内存:
User::chunk(200, function($users)
{
foreach ($users as $user)
{
//
}
});
传到方法里的第一个参数表示每次「拆分」要取出的数据数量。第二个参数的闭合函数会在每次取出数据时被调用。
指定查询时连接数据库
您也可以指定在执行 Eloquent 查询时要使用哪个数据库连线。只要使用 on
方法:
$user = User::on('connection-name')->find(1);
Mass Assignment
在建立一个新的模型时,您把属性以数组的方式传入 create
方法,这些属性值会经由 mass-assignment 存成模型数据。这非常方便,然而,若盲目地将用户输入存到模型时,可能会造成严重的安全隐患。如果盲目的存入用户输入,用户可以随意的修改任何以及所有模型的属性。基于这个理由,所有 Eloquent 模型默认会防止 mass-assignment 。
在模型里设定 fillable
或 guarded
属性作为开始。
定义模型 Fillable 属性
fillable
属性指定了哪些字段支持 mass-assignable 。可以设定在类里或是建立实例后设定。
class User extends Eloquent {
protected $fillable = array('first_name', 'last_name', 'email');
}
在上面的例子里,只有三个属性 mass-assignable 。
定义模型 Guarded 属性
guarded
与 fillable
相反,是作为「黑名单」而不是「白名单」:
class User extends Eloquent {
protected $guarded = array('id', 'password');
}
注意: 使用
guarded
时,Input::get()
或任何用户可以控制的未过滤数据,永远不应该传入save
或update
方法,因为没有在「黑名单」内的字段可能被更新。
阻挡所有属性被 Mass Assignment
上面的例子中, id
和 password
属性不会被 mass assigned,而所有其他的属性则是 mass assignable。您也可以使用 guard 属性阻止所有属性被 mass assignment :
protected $guarded = array('*');
新增,更新,删除
要从模型新增一条数据到数据库,只要建立一个模型实例并调用 save
方法即可。
储存新的模型数据
$user = new User;
$user->name = 'John';
$user->save();
注意: 通常 Eloquent 模型主键值会自动递增。但是您若想自定义主键,将
incrementing
属性设成false
。
也可以使用 create
方法存入新的模型数据,新增完后会回传新增的模型实例。但是在新增前,需要先在模型类里设定好 fillable
或 guarded
属性,因为 Eloquent 默认会防止 mass-assignment 。
在新模型数据被储存或新增后,若模型有自动递增主键,可以从对象取得 id
属性值:
$insertedId = $user->id;
在模型里设定 Guarded 属性
class User extends Eloquent {
protected $guarded = array('id', 'account_id');
}
使用模型的 Create 方法
// 在数据库建立一条新的用户...
$user = User::create(array('name' => 'John'));
// 以属性找用户,若没有则新增并取得新的实例...
$user = User::firstOrCreate(array('name' => 'John'));
// 以属性找用户,若没有则建立新的实例...
$user = User::firstOrNew(array('name' => 'John'));
更新取出的模型
要更新模型,可以取出它,更改属性值,然后使用 save
方法:
$user = User::find(1);
$user->email = 'john@foo.com';
$user->save();
储存模型和关联数据
有时您可能不只想要储存模型本身,也想要储存关联的数据。您可以使用 push
方法达到目的:
$user->push();
您可以结合查询语句,批次更新模型:
$affectedRows = User::where('votes', '>', 100)->update(array('status' => 2));
注意: 若使用 Eloquent 查询构造器批次更新模型,则不会触发
模型事件
。
删除模型
要删除模型,只要使用实例调用 delete
方法:
$user = User::find(1);
$user->delete();
依主键值删除模型
User::destroy(1);
User::destroy(array(1, 2, 3));
User::destroy(1, 2, 3);
当然,您也可以结合查询语句批次删除模型:
$affectedRows = User::where('votes', '>', 100)->delete();
只更新模型的时间戳
如果您只想要更新模型的时间戳,您可以使用 touch
方法:
$user->touch();
软删除
通过软删除方式删除了一个模型后,模型中的数据并不是真的从数据库被移除。而是会设定 deleted_at
时间戳。要让模型使用软删除功能,只要在模型类里加入 SoftDeletingTrait
即可:
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class User extends Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
}
要加入 deleted_at
字段到数据库表,可以在迁移文件里使用 softDeletes
方法:
$table->softDeletes();
现在当您使用模型调用 delete
方法时, deleted_at
字段会被更新成现在的时间戳。在查询使用软删除功能的模型时,被「删除」的模型数据不会出现在查询结果里。
强制查询软删除数据
要强制让已被软删除的模型数据出现在查询结果里,在查询时使用 withTrashed
方法:
$users = User::withTrashed()->where('account_id', 1)->get();
withTrashed
也可以用在关联查询:
$user->posts()->withTrashed()->get();
如果您只想查询被软删除的模型数据,可以使用 onlyTrashed
方法:
$users = User::onlyTrashed()->where('account_id', 1)->get();
要把被软删除的模型数据恢复,使用 restore
方法:
$user->restore();
您也可以结合查询语句使用 restore
:
User::withTrashed()->where('account_id', 1)->restore();
如同 withTrashed
, restore
方法也可以用在关联对象:
$user->posts()->restore();
如果想要真的从模型数据库删除,使用 forceDelete
方法:
$user->forceDelete();
forceDelete
方法也可以用在关联对象:
$user->posts()->forceDelete();
要确认模型是否被软删除了,可以使用 trashed
方法:
if ($user->trashed())
{
//
}
时间戳
默认 Eloquent 会自动维护数据库表的 created_at
和 updated_at
字段。只要把这两个「时间戳」字段加到数据库表, Eloquent 就会处理剩下的工作。如果不想让 Eloquent 自动维护这些字段,把下面的属性加到模型类里:
关闭自动更新时间戳
class User extends Eloquent {
protected $table = 'users';
public $timestamps = false;
}
自定义时间戳格式
如果想要自定义时间戳格式,可以在模型类里重写 getDateFormat
方法:
class User extends Eloquent {
protected function getDateFormat()
{
return 'U';
}
}
范围查询
定义范围查询
范围查询可以让您轻松的重复利用模型的查询逻辑。要设定范围查询,只要定义有 scope
前缀的模型方法:
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
使用范围查询
$users = User::popular()->women()->orderBy('created_at')->get();
动态范围查询
有时您可能想要定义可接受参数的范围查询方法。只要把参数加到方法里:
class User extends Eloquent {
public function scopeOfType($query, $type)
{
return $query->whereType($type);
}
}
然后把参数值传到范围查询方法调用里:
$users = User::ofType('member')->get();
全局范围
Sometimes you may wish to define a scope that applies to all queries performed on a model. In essence, this is how Eloquent's own "soft delete" feature works. Global scopes are defined using a combination of PHP traits and an implementation
of Illuminate\Database\Eloquent\ScopeInterface
.
First, let's define a trait. For this example, we'll use the SoftDeletingTrait
that ships with Laravel:
trait SoftDeletingTrait {
/**
* Boot the soft deleting trait for a model.
*
* @return void
*/
public static function bootSoftDeletingTrait()
{
static::addGlobalScope(new SoftDeletingScope);
}
}
If an Eloquent model uses a trait that has a method matching the bootNameOfTrait
naming convention, that trait method will be called when the Eloquent model is booted, giving you an opportunity to register a global scope, or do
anything else you want. A scope must implement ScopeInterface
, which specifies two methods: apply
and remove
.
The apply
method receives an Illuminate\Database\Eloquent\Builder
query builder object, and is responsible for adding any additional where
clauses that the scope wishes to add. The remove
method also receives a Builder
object and is responsible for reversing the action taken by apply
. In other words, remove
should remove the where
clause (or any other clause) that was
added. So, for our SoftDeletingScope
, the methods look something like this:
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function apply(Builder $builder)
{
$model = $builder->getModel();
$builder->whereNull($model->getQualifiedDeletedAtColumn());
}
/**
* Remove the scope from the given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function remove(Builder $builder)
{
$column = $builder->getModel()->getQualifiedDeletedAtColumn();
$query = $builder->getQuery();
foreach ((array) $query->wheres as $key => $where)
{
// If the where clause is a soft delete date constraint, we will remove it from
// the query and reset the keys on the wheres. This allows this developer to
// include deleted model in a relationship result set that is lazy loaded.
if ($this->isSoftDeleteConstraint($where, $column))
{
unset($query->wheres[$key]);
$query->wheres = array_values($query->wheres);
}
}
}
关联
当然,您的数据库表很可能跟另一张表相关联。例如,一篇 blog 文章可能有很多评论,或是一张订单跟下单客户相关联。 Eloquent 让管理和处理这些关联变得很容易。 Laravel 有很多种关联种类:
一对一
定义一对一关联
一对一关联是很基本的关联。例如一个 User
模型会对应到一个 Phone
。 在 Eloquent 里可以像下面这样定义关联:
class User extends Eloquent {
public function phone()
{
return $this->hasOne('Phone');
}
}
传到 hasOne
方法里的第一个参数是关联模型的类名称。定义好关联之后,就可以使用 Eloquent 的动态属性取得关联对象:
$phone = User::find(1)->phone;
SQL 会执行如下语句:
select * from users where id = 1
select * from phones where user_id = 1
注意, Eloquent 假设对应的关联模型数据库表里,外键名称是基于模型名称。在这个例子里,默认 Phone
模型数据库表会以 user_id
作为外键。如果想要更改这个默认,可以传入第二个参数到 hasOne
方法里。更进一步,您可以传入第三个参数,指定关联的外键要对应到本身的哪个字段:
return $this->hasOne('Phone', 'foreign_key');
return $this->hasOne('Phone', 'foreign_key', 'local_key');
定义相对的关联
要在 Phone
模型里定义相对的关联,可以使用 belongsTo
方法:
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
在上面的例子里, Eloquent 默认会使用 phones
数据库表的 user_id
字段查询关联。如果想要自己指定外键字段,可以在 belongsTo
方法里传入第二个参数:
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User', 'local_key');
}
}
除此之外,也可以传入第三个参数指定要参照上层数据库表的哪个字段:
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User', 'local_key', 'parent_key');
}
}
一对多
一对多关联的例子如,一篇 Blog 文章可能「有很多」评论。可以像这样定义关联:
class Post extends Eloquent {
public function comments()
{
return $this->hasMany('Comment');
}
}
现在可以经由动态属性取得文章的评论:
$comments = Post::find(1)->comments;
如果需要增加更多条件限制,可以在调用 comments
方法后面串接查询条件方法:
$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();
同样的,您可以传入第二个参数到 hasMany
方法更改默认的外键名称。以及,如同 hasOne
关联,可以指定本身的对应字段:
return $this->hasMany('Comment', 'foreign_key');
return $this->hasMany('Comment', 'foreign_key', 'local_key');
定义相对的关联
要在 Comment
模型定义相对应的关联,可使用 belongsTo
方法:
class Comment extends Eloquent {
public function post()
{
return $this->belongsTo('Post');
}
}
多对多
多对多关联更为复杂。这种关联的例子如,一个用户( user )可能用有很多身份( role ),而一种身份可能很多用户都有。例如很多用户都是「管理者」。多对多关联需要用到三个数据库表: users
, roles
,和 role_user
。 role_user
枢纽表命名是以相关联的两个模型数据库表,依照字母顺序命名,枢纽表里面应该要有 user_id
和 role_id
字段。
可以使用 belongsToMany
方法定义多对多关系:
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
现在我们可以从 User
模型取得 roles:
$roles = User::find(1)->roles;
如果不想使用默认的枢纽数据库表命名方式,可以传递数据库表名称作为 belongsToMany
方法的第二个参数:
return $this->belongsToMany('Role', 'user_roles');
也可以更改默认的关联字段名称:
return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');
当然,也可以在 Role
模型定义相对的关联:
class Role extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
远层一对多关联
「远层一对多关联」提供了方便简短的方法,可以经由多层间的关联取得远层的关联。例如,一个 Country
模型可能通过 User
关联到很多 Post
模型。 数据库表间的关系可能看起来如下:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
虽然 posts
数据库表本身没有 country_id
字段,但 hasManyThrough
方法让我们可以使用 $country->posts
取得 country 的 posts。我们可以定义以下关联:
class Country extends Eloquent {
public function posts()
{
return $this->hasManyThrough('Post', 'User');
}
}
如果想要手动指定关联的字段名称,可以传入第三和第四个参数到方法里:
class Country extends Eloquent {
public function posts()
{
return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
}
}
多态关联
多态关联可以用一个简单的关联方法,就让一个模型同时关联多个模型。例如,您可能想让 photo 模型同时和一个 staff 或 order 模型关联。可以定义关联如下:
class Photo extends Eloquent {
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
class Order extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
取得多态关联对象
现在我们可以从 staff 或 order 模型取得多态关联对象:
$staff = Staff::find(1);
foreach ($staff->photos as $photo)
{
//
}
取得多态关联对象的拥有者
然而,多态关联真正神奇的地方,在于要从 Photo
模型取得 staff 或 order 对象时:
$photo = Photo::find(1);
$imageable = $photo->imageable;
Photo
模型里的 imageable
关联会回传 Staff
或 Order
实例,取决于这是哪一种模型拥有的照片。
多态关联的数据库表结构
为了理解多态关联的运作机制,来看看它们的数据库表结构:
staff
id - integer
name - string
orders
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
要注意的重点是 photos
数据库表的 imageable_id
和 imageable_type
。在上面的例子里, ID 字段会包含 staff 或 order 的 ID,而 type 是拥有者的模型类名称。这就是让 ORM 在取得 imageable
关联对象时,决定要哪一种模型对象的机制。
多态的多对多关联
多态的多对多关联数据库表结构
除了一般的多态关联,也可以使用多对多的多态关联。例如,Blog 的 Post
和 Video
模型可以共用多态的 Tag
关联模型。首先,来看看数据库表结构:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
现在,我们准备好设定模型关联了。 Post
和 Video
模型都可以经由 tags
方法建立 morphToMany
关联:
class Post extends Eloquent {
public function tags()
{
return $this->morphToMany('Tag', 'taggable');
}
}
在 Tag
模型里针对每一种关联建立一个方法:
class Tag extends Eloquent {
public function posts()
{
return $this->morphedByMany('Post', 'taggable');
}
public function videos()
{
return $this->morphedByMany('Video', 'taggable');
}
}
关联查询
根据关联条件查询
在取得模型数据时,您可能想要以关联模型作为查询限制。例如,您可能想要取得所有「至少有一篇评论」的Blog 文章。可以使用 has
方法达成目的:
$posts = Post::has('comments')->get();
也可以指定运算子和数量:
$posts = Post::has('comments', '>=', 3)->get();
如果想要更进阶,可以使用 whereHas
和 orWhereHas
方法,在 has
查询里设置 "where" 条件 :
$posts = Post::whereHas('comments', function($q)
{
$q->where('content', 'like', 'foo%');
})->get();
动态属性
Eloquent 可以经由动态属性取得关联对象。 Eloquent 会自动进行关联查询,而且会很聪明的知道应该要使用 get
(用在一对多关联)或是 first
(用在一对一关联)方法。可以经由和「关联方法名称相同」的动态属性取得对象。例如,如下面的模型对象 $phone
:
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
$phone = Phone::find(1);
或是像下面这样打印用户的 email :
echo $phone->user()->first()->email;
可以简写如下:
echo $phone->user->email;
注意: 若取得的是许多关联对象,会返回
Illuminate\Database\Eloquent\Collection
对象:
预载入
预载入是用来减少 N + 1 查询问题。例如,一个 Book
模型数据会关联到一个 Author
。关联会像下面这样定义:
class Book extends Eloquent {
public function author()
{
return $this->belongsTo('Author');
}
}
现在考虑下面的代码:
foreach (Book::all() as $book)
{
echo $book->author->name;
}
上面的循环会执行一次查询取回所有数据库表上的书籍,然而每本书籍都会执行一次查询取得作者。所以若我们有 25 本书,就会进行 26次查询。
很幸运地,我们可以使用预载入大量减少查询次数。使用 with
方法指定想要预载入的关联对象:
foreach (Book::with('author')->get() as $book)
{
echo $book->author->name;
}
现在,上面的循环总共只会执行两次查询:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
使用预载入可以大大提高程序的性能。
当然,也可以同时载入多种关联:
$books = Book::with('author', 'publisher')->get();
甚至可以预载入巢状关联:
$books = Book::with('author.contacts')->get();
上面的例子中, author
关联会被预载入, author 的 contacts
关联也会被预载入。
预载入条件限制
有时您可能想要预载入关联,同时也想要指定载入时的查询限制。下面有一个例子:
$users = User::with(array('posts' => function($query)
{
$query->where('title', 'like', '%first%');
}))->get();
上面的例子里,我们预载入了 user 的 posts 关联,并限制条件为 post 的 title 字段需包含 "first" 。
当然,预载入的闭合函数里不一定只能加上条件限制,也可以加上排序:
$users = User::with(array('posts' => function($query)
{
$query->orderBy('created_at', 'desc');
}))->get();
延迟预载入
也可以直接从模型的 collection 预载入关联对象。这对于需要根据情况决定是否载入关联对象时,或是跟缓存一起使用时很有用。
$books = Book::all();
$books->load('author', 'publisher');
新增关联模型
附加一个关联模型
您常常会需要加入新的关联模型。例如新增一个 comment 到 post 。除了手动设定模型的 post_id
外键,也可以从上层的 Post
模型新增关联的 comment :
$comment = new Comment(array('message' => 'A new comment.'));
$post = Post::find(1);
$comment = $post->comments()->save($comment);
上面的例子里,新增的 comment post_id
字段会被自动设定。
如果想要同时新增很多关联模型:
$comments = array(
new Comment(array('message' => 'A new comment.')),
new Comment(array('message' => 'Another comment.')),
new Comment(array('message' => 'The latest comment.'))
);
$post = Post::find(1);
$post->comments()->saveMany($comments);
从属关联模型 ( Belongs To )
要更新 belongsTo
关联时,可以使用 associate
方法。这个方法会设定子模型的外键:
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
新增多对多关联模型 ( Many To Many )
您也可以新增多对多的关联模型。让我们继续使用 User
和 Role
模型作为例子。我们可以使用 attach
方法简单地把 roles 附加给一个 user:
附加多对多模型
$user = User::find(1);
$user->roles()->attach(1);
也可以传入要存在枢纽表中的属性数组:
$user->roles()->attach(1, array('expires' => $expires));
当然,有 attach
就会有相反的 detach
:
$user->roles()->detach(1);
使用 Sync 方法同时附加一个以上多对多关联
您也可以使用 sync
方法附加关联模型。 sync
方法会把根据 ID 数组把关联存到枢纽表。附加完关联后,枢纽表里的模型只会关联到 ID 数组里的 id :
$user->roles()->sync(array(1, 2, 3));
Sync 时在枢纽表加入额外数据
也可以在把每个 ID 加入枢纽表时,加入其他字段的数据:
$user->roles()->sync(array(1 => array('expires' => true)));
有时您可能想要使用一个命令,在建立新模型数据的同时附加关联。可以使用 save
方法达成目的:
$role = new Role(array('name' => 'Editor'));
User::find(1)->roles()->save($role);
上面的例子里,新的 Role
模型对象会在储存的同时关联到 user 模型。也可以传入属性数组把数据加到关联数据库表:
User::find(1)->roles()->save($role, array('expires' => $expires));
更新上层时间戳
当模型 belongsTo
另一个模型,比方说一个 Comment
属于一个 Post
,如果能在子模型被更新时,更新上层的时间戳,这将会很有用。例如,当 Comment
模型更新时,您可能想要能够同时自动更新 Post
的 updated_at
时间戳。 Eloquent 让事情变得很简单。只要在子关联的类里,把关联方法名称加入
touches
属性即可:
class Comment extends Eloquent {
protected $touches = array('post');
public function post()
{
return $this->belongsTo('Post');
}
}
现在,当您更新 Comment
时,对应的 Post
会自动更新 updated_at
字段:
$comment = Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
使用枢纽表
如您所知,要操作多对多关联需要一个中间的数据库表。 Eloquent 提供了一些有用的方法可以和这张表互动。例如,假设 User
对象关联到很多 Role
对象。取出这些关联对象时,我们可以在关联模型上取得 pivot
数据库表的数据:
$user = User::find(1);
foreach ($user->roles as $role)
{
echo $role->pivot->created_at;
}
注意我们取出的每个 Role
模型对象会自动给一个 pivot
属性。这属性包含了枢纽表的模型数据,可以像一般的 Eloquent 模型一样使用。
默认 pivot
对象只会有关联键的属性。如果您想让 pivot
可以包含其他枢纽表的字段,可以在定义关联方法时指定那些字段:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
现在可以在 Role
模型的 pivot
对象上取得 foo
和 bar
属性了。
如果您想要可以自动维护枢纽表的 created_at
和 updated_at
时间戳,在定义关联方法时加上 withTimestamps
方法:
return $this->belongsToMany('Role')->withTimestamps();
删除枢纽表的关联数据
要删除模型在枢纽表的所有关联数据,可以使用 detach
方法:
User::find(1)->roles()->detach();
attach
和 detach
都要求 ID 数组作为输入:
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);
更新枢纽表的数据
有时您只想更新枢纽表的数据,而没有要移除关联。如果您想更新枢纽表,可以像下面的例子使用 updateExistingPivot
方法:
User::find(1)->roles()->updateExistingPivot($roleId, $attributes);
自定义枢纽模型
Laravel 允许您自定义枢纽模型。要自定义模型,首先要建立一个继承 Eloquent
的「基本」模型类。在其他的 Eloquent 模型继承这个自定义的基本类,而不是默认的 Eloquent
。在基本模型类里,加入下面的方法回传自定义的枢纽模型实例:
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
return new YourCustomPivot($parent, $attributes, $table, $exists);
}
Collections
所有 Eloquent 查询回传的数据,如果结果多于一条,不管是经由 get
方法或是 relationship
,都会转换成 collection 对象回传。这个对象实现了 IteratorAggregate
PHP 接口,所以可以像数组一般进行遍历。而 Collections 对象本身还拥有很多有用的方法可以操作模型数据。
确认 Collection 里是否包含特定键值
例如,我们可以使用 contains
方法,确认结果数据中,是否包含主键为特定值的对象。
$roles = User::find(1)->roles;
if ($roles->contains(2))
{
//
}
Collection 也可以转换成数组或 JSON:
$roles = User::find(1)->roles->toArray();
$roles = User::find(1)->roles->toJson();
如果 collection 被类型转换成字串,会回传 JSON 格式:
$roles = (string) User::find(1)->roles;
Collections 遍历
Eloquent collections 里包含了一些有用的方法可以进行循环或是进行过滤:
$roles = $user->roles->each(function($role)
{
//
});
Collection 过滤
过滤 collection 时,回调函数的使用方式和 array_filter 里一样。
$users = $users->filter(function($user)
{
return $user->isAdmin();
});
注意: 如果要在过滤 collection 之后转成 JSON,转换之前先调用
values
方法重设数组的键值。
遍历传入 Collection 里的每个对象到回调函数
$roles = User::find(1)->roles;
$roles->each(function($role)
{
//
});
依照属性值排序
$roles = $roles->sortBy(function($role)
{
return $role->created_at;
});
依照属性值排序
$roles = $roles->sortBy('created_at');
回传自定义的集合对象
有时您可能想要回传自定义的集合对象,让您可以在集合类里加入想要的方法。可以在 Eloquent 模型类里重写 newCollection
方法:
class User extends Eloquent {
public function newCollection(array $models = array())
{
return new CustomCollection($models);
}
}
获取器和修改器
定义获取器
Eloquent 提供了便利的方法,可以在取得或设定属性时进行转换。要定义获取器,只要在模型里加入类似 getFooAttribute
的方法。注意方法名称应该使用驼峰式大小写命名,而对应的 database 字段名称是底线分隔小写命名:
class User extends Eloquent {
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
上面的例子中, first_name
字段设定了一个获取器。注意传入方法的参数是原本的字段数据。
定义修改器
修改器的定义方式很雷同:
class User extends Eloquent {
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
日期转换器
默认 Eloquent 会把 created_at
和 updated_at
字段属性转换成 Carbon 实例,它提供了很多有用的方法,并继承了 PHP 原生的 DateTime
类。
您可以经由重写模型的 getDates
方法,自定义哪个字段可以被自动转换,或甚至完全不要让日期转换成 Carbon:
public function getDates()
{
return array('created_at');
}
当字段是表示日期的时候,可以将值设为 UNIX timestamp 、日期字串( Y-m-d
)、 日期时间( date-time )字串,当然还有 DateTime
或 Carbon
实例。
要完全关闭日期转换功能,只要从 getDates
方法回传空数组即可:
public function getDates()
{
return array();
}
Model Events
Eloquent 模型有很多事件可以驱动,让您可以在模型操作的生命周期中不同时间点,使用下列方法绑定事件: creating
、 created
、 updating
、 updated
、 saving
、 saved
、 deleting
、 deleted
、 restoring
、 restored
。
当一个对象初次被储存到数据库, creating
和 created
事件会被驱动。如果不是新对象而调用了 save
方法, updating
/ updated
事件会被驱动。而两者的 saving
/ saved
事件都会被驱动。
使用事件取消数据库操作
如果 creating
、 updating
、 saving
、 deleting
事件回传 false
的话,就会取消数据库操作:
User::creating(function($user)
{
if ( ! $user->isValid()) return false;
});
设定模型 Boot 方法
Eloquent 模型有静态的 boot
方法,可以使用它方便的注册事件绑定。
class User extends Eloquent {
public static function boot()
{
parent::boot();
// Setup event bindings...
}
}
模型观察者
要整合模型的事件处理,可以注册一个模型观察者。观察者类里要设定对应模型事件的方法。例如,观察者类里可能有 creating
、 updating
、 saving
方法,还有其他对应模型事件名称的方法:
例如,一个模型观察者类可能看起来如下:
class UserObserver {
public function saving($model)
{
//
}
public function saved($model)
{
//
}
}
可以使用 observe
方法注册一个观察者实例:
User::observe(new UserObserver);
转换成数组 / JSON
将模型数据转成数组
建立 JSON API 时,您可能常常需要把模型和关联对象转换成数组或 JSON 。所以 Eloquent 里已经包含了这些方法。要把模型和已载入的关联对象转成数组,可以使用 toArray
方法:
$user = User::with('roles')->first();
return $user->toArray();
记得也可以把模型 collection 转换成数组:
return User::all()->toArray();
把模型转换成 JSON
要把模型转换成 JSON,可以使用 toJson
方法:
return User::find(1)->toJson();
从路由回传模型
注意当模型或 collection 被类型转换成字符串时会自动转换成 JSON 格式,这意味着您可以直接从路由回传 Eloquent 对象!
Route::get('users', function()
{
return User::all();
});
转换成数组或 JSON 时隐藏属性
有时您可能想要限制能出现在数组或 JSON 格式的属性数据,比如密码。只要在模型里增加 hidden
属性即可:
class User extends Eloquent {
protected $hidden = array('password');
}
注意: 要隐藏关联数据,要使用关联的方法名称,而不是动态获取的属性名称。
此外,可以使用 visible
属性定义白名单:
protected $visible = array('first_name', 'last_name');
有时候您可能想要增加不存在数据库字段的属性数据。这时候只要定义一个获取器即可:
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
定义好获取器之后,再把对应的属性名称加到模型里的 appends
属性:
protected $appends = array('is_admin');
把属性加到 appends
数组之后,在模型数据转换成数组或 JSON 格式时就会有对应的值。