Skip to content
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Eloquent ORM

介绍

Laravel 附带的 Eloquent ORM 提供了一个美观、简单的 ActiveRecord 实现,用于与数据库交互。每个数据库表都有一个对应的“模型”,用于与该表进行交互。

在开始之前,请确保在 app/config/database.php 中配置数据库连接。

基本用法

要开始使用,请创建一个 Eloquent 模型。模型通常位于 app/models 目录中,但您可以将它们放置在任何可以根据 composer.json 文件自动加载的位置。

定义 Eloquent 模型

php
class User extends Eloquent {}

请注意,我们没有告诉 Eloquent 使用哪个表来存储我们的 User 模型。类名的小写复数形式将用作表名,除非明确指定其他名称。因此,在这种情况下,Eloquent 将假定 User 模型存储在 users 表中。您可以通过在模型上定义 table 属性来指定自定义表:

php
class User extends Eloquent {

	protected $table = 'my_users';

}

NOTE

Eloquent 还将假定每个表都有一个名为 id 的主键列。您可以定义 primaryKey 属性来覆盖此约定。同样,您可以定义 connection 属性来覆盖在使用模型时应使用的数据库连接的名称。

定义模型后,您就可以开始检索和创建表中的记录。请注意,您需要在表中放置 updated_atcreated_at 列,默认情况下。如果您不希望这些列自动维护,请将模型上的 $timestamps 属性设置为 false

检索所有模型

php
$users = User::all();

按主键检索记录

php
$user = User::find(1);

var_dump($user->name);

NOTE

可用于 查询构建器 的所有方法在查询 Eloquent 模型时也可用。

按主键检索模型或抛出异常

有时您可能希望在未找到模型时抛出异常,允许您使用 App::error 处理程序捕获异常并显示 404 页面。

php
$model = User::findOrFail(1);

$model = User::where('votes', '>', 100)->firstOrFail();

要注册错误处理程序,请监听 ModelNotFoundException

php
use Illuminate\Database\Eloquent\ModelNotFoundException;

App::error(function(ModelNotFoundException $e)
{
	return Response::make('未找到', 404);
});

使用 Eloquent 模型查询

php
$users = User::where('votes', '>', 100)->take(10)->get();

foreach ($users as $user)
{
	var_dump($user->name);
}

Eloquent 聚合

当然,您也可以使用查询构建器聚合函数。

php
$count = User::where('votes', '>', 100)->count();

如果您无法通过流畅接口生成所需的查询,请随时使用 whereRaw

php
$users = User::whereRaw('age > ? and votes = 100', array(25))->get();

分块结果

如果您需要处理大量(数千个)Eloquent 记录,使用 chunk 命令将允许您在不消耗所有 RAM 的情况下进行处理:

php
User::chunk(200, function($users)
{
	foreach ($users as $user)
	{
		//
	}
});

传递给该方法的第一个参数是您希望每个“块”接收的记录数。作为第二个参数传递的闭包将在从数据库中提取的每个块上调用。

指定查询连接

您还可以指定在运行 Eloquent 查询时应使用哪个数据库连接。只需使用 on 方法:

php
$user = User::on('connection-name')->find(1);

批量赋值

创建新模型时,您将一个属性数组传递给模型构造函数。这些属性随后通过批量赋值分配给模型。这很方便;但是,当盲目地将用户输入传递给模型时,可能会造成 严重 的安全隐患。如果用户输入被盲目传递给模型,用户可以自由修改 任何所有 模型的属性。因此,所有 Eloquent 模型默认会防止批量赋值。

要开始,请在模型上设置 fillableguarded 属性。

在模型上定义可填充属性

fillable 属性指定哪些属性应可批量赋值。这可以在类级别或实例级别设置。

php
class User extends Eloquent {

	protected $fillable = array('first_name', 'last_name', 'email');

}

在此示例中,只有列出的三个属性将是可批量赋值的。

在模型上定义受保护属性

fillable 的反面是 guarded,它作为“黑名单”而不是“白名单”:

php
class User extends Eloquent {

	protected $guarded = array('id', 'password');

}

NOTE

使用 guarded 时,您仍然不应将 Input::get() 或任何原始用户控制输入数组传递给 saveupdate 方法,因为任何未被保护的列都可能被更新。

阻止所有属性的批量赋值

在上面的示例中,idpassword 属性 不能 被批量赋值。所有其他属性将是可批量赋值的。您还可以使用保护属性阻止 所有 属性的批量赋值:

php
protected $guarded = array('*');

插入、更新、删除

要从模型创建新记录,只需创建一个新的模型实例并调用 save 方法。

保存新模型

php
$user = new User;

$user->name = 'John';

$user->save();

NOTE

通常,您的 Eloquent 模型将具有自增键。但是,如果您希望指定自己的键,请将模型上的 incrementing 属性设置为 false

您还可以使用 create 方法在一行中保存新模型。插入的模型实例将从该方法返回。但是,在这样做之前,您需要在模型上指定 fillableguarded 属性,因为所有 Eloquent 模型都防止批量赋值。

保存或创建使用自增 ID 的新模型后,您可以通过访问对象的 id 属性来检索 ID:

php
$insertedId = $user->id;

设置模型上的受保护属性

php
class User extends Eloquent {

	protected $guarded = array('id', 'account_id');

}

使用模型创建方法

php
// 在数据库中创建新用户...
$user = User::create(array('name' => 'John'));

// 通过属性检索用户,或在不存在时创建它...
$user = User::firstOrCreate(array('name' => 'John'));

// 通过属性检索用户,或实例化一个新实例...
$user = User::firstOrNew(array('name' => 'John'));

更新检索到的模型

要更新模型,您可以检索它,改变一个属性,然后使用 save 方法:

php
$user = User::find(1);

$user->email = 'john@foo.com';

$user->save();

保存模型和关系

有时,您可能希望保存的不仅是模型,还有它的所有关系。为此,您可以使用 push 方法:

php
$user->push();

您还可以对一组模型运行更新查询:

php
$affectedRows = User::where('votes', '>', 100)->update(array('status' => 2));

NOTE

通过 Eloquent 查询构建器更新一组模型时,不会触发任何模型事件。

删除现有模型

要删除模型,只需在实例上调用 delete 方法:

php
$user = User::find(1);

$user->delete();

按键删除现有模型

php
User::destroy(1);

User::destroy(array(1, 2, 3));

User::destroy(1, 2, 3);

当然,您还可以对一组模型运行删除查询:

php
$affectedRows = User::where('votes', '>', 100)->delete();

仅更新模型的时间戳

如果您只想更新模型的时间戳,可以使用 touch 方法:

php
$user->touch();

软删除

在软删除模型时,它实际上并不会从数据库中删除。相反,会在记录上设置 deleted_at 时间戳。要为模型启用软删除,请将 SoftDeletingTrait 应用到模型:

php
use Illuminate\Database\Eloquent\SoftDeletingTrait;

class User extends Eloquent {

	use SoftDeletingTrait;

	protected $dates = ['deleted_at'];

}

要在表中添加 deleted_at 列,您可以在迁移中使用 softDeletes 方法:

php
$table->softDeletes();

现在,当您在模型上调用 delete 方法时,deleted_at 列将设置为当前时间戳。当查询使用软删除的模型时,“已删除”模型将不会包含在查询结果中。

强制软删除模型出现在结果中

要强制软删除模型出现在结果集中,请在查询中使用 withTrashed 方法:

php
$users = User::withTrashed()->where('account_id', 1)->get();

withTrashed 方法可以在定义的关系上使用:

php
$user->posts()->withTrashed()->get();

如果您只希望在结果中接收软删除的模型,可以使用 onlyTrashed 方法:

php
$users = User::onlyTrashed()->where('account_id', 1)->get();

要将软删除的模型恢复到活动状态,请使用 restore 方法:

php
$user->restore();

您还可以在查询上使用 restore 方法:

php
User::withTrashed()->where('account_id', 1)->restore();

withTrashed 一样,restore 方法也可以在关系上使用:

php
$user->posts()->restore();

如果您希望真正从数据库中删除模型,可以使用 forceDelete 方法:

php
$user->forceDelete();

forceDelete 方法也适用于关系:

php
$user->posts()->forceDelete();

要确定给定模型实例是否已软删除,您可以使用 trashed 方法:

php
if ($user->trashed())
{
	//
}

时间戳

默认情况下,Eloquent 将自动维护数据库表上的 created_atupdated_at 列。只需将这些 timestamp 列添加到您的表中,Eloquent 将处理其余部分。如果您不希望 Eloquent 维护这些列,请在模型中添加以下属性:

禁用自动时间戳

php
class User extends Eloquent {

	protected $table = 'users';

	public $timestamps = false;

}

提供自定义时间戳格式

如果您希望自定义时间戳的格式,可以在模型中重写 getDateFormat 方法:

php
class User extends Eloquent {

	protected function getDateFormat()
	{
		return 'U';
	}

}

查询作用域

定义查询作用域

作用域允许您轻松重用模型中的查询逻辑。要定义作用域,只需在模型方法前加上 scope 前缀:

php
class User extends Eloquent {

	public function scopePopular($query)
	{
		return $query->where('votes', '>', 100);
	}

	public function scopeWomen($query)
	{
		return $query->whereGender('W');
	}

}

使用查询作用域

php
$users = User::popular()->women()->orderBy('created_at')->get();

动态作用域

有时,您可能希望定义一个接受参数的作用域。只需将参数添加到作用域函数中:

php
class User extends Eloquent {

	public function scopeOfType($query, $type)
	{
		return $query->whereType($type);
	}

}

然后将参数传递给作用域调用:

php
$users = User::ofType('member')->get();

全局作用域

有时,您可能希望定义一个适用于对模型执行的所有查询的作用域。实际上,这就是 Eloquent 自身的“软删除”功能的工作原理。全局作用域是使用 PHP 特性和 Illuminate\Database\Eloquent\ScopeInterface 的实现组合定义的。

首先,让我们定义一个特性。对于这个示例,我们将使用 Laravel 附带的 SoftDeletingTrait

php
trait SoftDeletingTrait {

	/**
	 * 为模型引导软删除特性。
	 *
	 * @return void
	 */
	public static function bootSoftDeletingTrait()
	{
		static::addGlobalScope(new SoftDeletingScope);
	}

}

如果 Eloquent 模型使用具有与 bootNameOfTrait 命名约定匹配的方法的特性,则在引导 Eloquent 模型时将调用该特性方法,从而使您有机会注册全局作用域或执行其他任何操作。作用域必须实现 ScopeInterface,该接口指定两个方法:applyremove

apply 方法接收一个 Illuminate\Database\Eloquent\Builder 查询构建器对象,并负责添加作用域希望添加的任何附加 where 子句。remove 方法也接收一个 Builder 对象,并负责撤消 apply 所采取的操作。换句话说,remove 应该删除添加的 where 子句(或任何其他子句)。因此,对于我们的 SoftDeletingScope,方法看起来像这样:

php
/**
 * 将作用域应用于给定的 Eloquent 查询构建器。
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @return void
 */
public function apply(Builder $builder)
{
	$model = $builder->getModel();

	$builder->whereNull($model->getQualifiedDeletedAtColumn());
}

/**
 * 从给定的 Eloquent 查询构建器中删除作用域。
 *
 * @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)
	{
		// 如果 where 子句是软删除日期约束,我们将从查询中删除它并重置 wheres 的键。这允许开发人员在懒加载的关系结果集中包含已删除的模型。
		if ($this->isSoftDeleteConstraint($where, $column))
		{
			unset($query->wheres[$key]);

			$query->wheres = array_values($query->wheres);
		}
	}
}

关系

当然,您的数据库表可能彼此相关。例如,博客文章可能有许多评论,或者订单可能与下订单的用户相关。Eloquent 使管理和处理这些关系变得简单。Laravel 支持多种类型的关系:

一对一

定义一对一关系

一对一关系是一种非常基本的关系。例如,User 模型可能有一个 Phone。我们可以在 Eloquent 中定义此关系:

php
class User extends Eloquent {

	public function phone()
	{
		return $this->hasOne('Phone');
	}

}

传递给 hasOne 方法的第一个参数是相关模型的名称。一旦定义了关系,我们可以使用 Eloquent 的 动态属性 检索它:

php
$phone = User::find(1)->phone;

该语句执行的 SQL 如下:

php
select * from users where id = 1

select * from phones where user_id = 1

请注意,Eloquent 根据模型名称假定关系的外键。在这种情况下,Phone 模型假定使用 user_id 外键。如果您希望覆盖此约定,可以将第二个参数传递给 hasOne 方法。此外,您可以将第三个参数传递给该方法以指定应用于关联的本地列:

php
return $this->hasOne('Phone', 'foreign_key');

return $this->hasOne('Phone', 'foreign_key', 'local_key');

定义关系的反向

要在 Phone 模型上定义关系的反向,我们使用 belongsTo 方法:

php
class Phone extends Eloquent {

	public function user()
	{
		return $this->belongsTo('User');
	}

}

在上面的示例中,Eloquent 将查找 phones 表上的 user_id 列。如果您想定义不同的外键列,可以将其作为第二个参数传递给 belongsTo 方法:

php
class Phone extends Eloquent {

	public function user()
	{
		return $this->belongsTo('User', 'local_key');
	}

}

此外,您可以传递第三个参数,指定父表上关联列的名称:

php
class Phone extends Eloquent {

	public function user()
	{
		return $this->belongsTo('User', 'local_key', 'parent_key');
	}

}

一对多

一对多关系的示例是一个博客文章“拥有许多”评论。我们可以这样建模此关系:

php
class Post extends Eloquent {

	public function comments()
	{
		return $this->hasMany('Comment');
	}

}

现在,我们可以通过 动态属性 访问帖子的评论:

php
$comments = Post::find(1)->comments;

如果您需要添加进一步的约束以检索哪些评论,可以调用 comments 方法并继续链接条件:

php
$comments = Post::find(1)->comments()->where('title', '=', 'foo')->first();

同样,您可以通过将第二个参数传递给 hasMany 方法来覆盖约定的外键。与 hasOne 关系一样,本地列也可以指定:

php
return $this->hasMany('Comment', 'foreign_key');

return $this->hasMany('Comment', 'foreign_key', 'local_key');

定义关系的反向

要在 Comment 模型上定义关系的反向,我们使用 belongsTo 方法:

php
class Comment extends Eloquent {

	public function post()
	{
		return $this->belongsTo('Post');
	}

}

多对多

多对多关系是一种更复杂的关系类型。例如,用户可能有许多角色,而这些角色也被其他用户共享。例如,许多用户可能具有“管理员”角色。此关系需要三个数据库表:usersrolesrole_userrole_user 表是根据相关模型名称的字母顺序派生的,并应具有 user_idrole_id 列。

我们可以使用 belongsToMany 方法定义多对多关系:

php
class User extends Eloquent {

	public function roles()
	{
		return $this->belongsToMany('Role');
	}

}

现在,我们可以通过 User 模型检索角色:

php
$roles = User::find(1)->roles;

如果您希望为您的中间表使用非常规表名,可以将其作为第二个参数传递给 belongsToMany 方法:

php
return $this->belongsToMany('Role', 'user_roles');

您还可以覆盖约定的关联键:

php
return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');

当然,您还可以在 Role 模型上定义关系的反向:

php
class Role extends Eloquent {

	public function users()
	{
		return $this->belongsToMany('User');
	}

}

通过关系的一对多

“通过关系的一对多”关系提供了一种方便的快捷方式,通过中间关系访问远程关系。例如,Country 模型可能通过 User 模型拥有许多 Post。此关系的表结构如下所示:

php
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 访问一个国家的帖子。让我们定义关系:

php
class Country extends Eloquent {

	public function posts()
	{
		return $this->hasManyThrough('Post', 'User');
	}

}

如果您希望手动指定关系的键,可以将它们作为第三和第四个参数传递给该方法:

php
class Country extends Eloquent {

	public function posts()
	{
		return $this->hasManyThrough('Post', 'User', 'country_id', 'user_id');
	}

}

多态关系

多态关系允许一个模型属于多个其他模型,建立单一关联。例如,您可能有一个照片模型,它可以属于员工模型或订单模型。我们将这样定义此关系:

php
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');
	}

}

检索多态关系

现在,我们可以检索员工或订单的照片:

php
$staff = Staff::find(1);

foreach ($staff->photos as $photo)
{
	//
}

检索多态关系的所有者

然而,真正的“多态”魔力在于您可以从 Photo 模型访问员工或订单:

php
$photo = Photo::find(1);

$imageable = $photo->imageable;

imageable 关系在 Photo 模型上将返回 StaffOrder 实例,具体取决于哪个类型的模型拥有该照片。

多态关系表结构

为了帮助理解其工作原理,让我们探讨多态关系的数据库结构:

php
staff
	id - integer
	name - string

orders
	id - integer
	price - integer

photos
	id - integer
	path - string
	imageable_id - integer
	imageable_type - string

需要注意的关键字段是 photos 表上的 imageable_idimageable_type。ID 将包含拥有者的 ID 值,而在此示例中,类型将包含拥有模型的类名。这就是 ORM 在访问 imageable 关系时确定返回哪种类型的拥有模型的原因。

多态多对多关系

多态多对多关系表结构

除了传统的多态关系外,您还可以指定多态多对多关系。例如,博客 PostVideo 模型可以共享与 Tag 模型的多态关系。首先,让我们检查表结构:

php
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

接下来,我们准备在模型上设置关系。PostVideo 模型都将通过 tags 方法具有 morphToMany 关系:

php
class Post extends Eloquent {

	public function tags()
	{
		return $this->morphToMany('Tag', 'taggable');
	}

}

Tag 模型可以为其每个关系定义一个方法:

php
class Tag extends Eloquent {

	public function posts()
	{
		return $this->morphedByMany('Post', 'taggable');
	}

	public function videos()
	{
		return $this->morphedByMany('Video', 'taggable');
	}

}

查询关系

查询选择时的关系

在访问模型的记录时,您可能希望根据关系的存在限制结果。例如,您希望提取至少有一个评论的所有博客文章。为此,您可以使用 has 方法:

php
$posts = Post::has('comments')->get();

您还可以指定运算符和计数:

php
$posts = Post::has('comments', '>=', 3)->get();

如果您需要更多的功能,您可以使用 whereHasorWhereHas 方法在 has 查询中放置“where”条件:

php
$posts = Post::whereHas('comments', function($q)
{
	$q->where('content', 'like', 'foo%');

})->get();

动态属性

Eloquent 允许您通过动态属性访问关系。Eloquent 将自动为您加载关系,并且足够聪明,知道何时调用 get(对于一对多关系)或 first(对于一对一关系)方法。然后,它将通过与关系同名的动态属性进行访问。例如,使用以下模型 $phone

php
class Phone extends Eloquent {

	public function user()
	{
		return $this->belongsTo('User');
	}

}

$phone = Phone::find(1);

而不是像这样回显用户的电子邮件:

php
echo $phone->user()->first()->email;

可以简化为:

php
echo $phone->user->email;

NOTE

返回多个结果的关系将返回 Illuminate\Database\Eloquent\Collection 类的实例。

急切加载

急切加载旨在缓解 N + 1 查询问题。例如,考虑一个与 Author 相关的 Book 模型。关系定义如下:

php
class Book extends Eloquent {

	public function author()
	{
		return $this->belongsTo('Author');
	}

}

现在,考虑以下代码:

php
foreach (Book::all() as $book)
{
	echo $book->author->name;
}

此循环将执行 1 个查询以检索表中的所有书籍,然后为每本书执行另一个查询以检索作者。因此,如果我们有 25 本书,此循环将运行 26 个查询。

幸运的是,我们可以使用急切加载大幅减少查询数量。应急加载的关系可以通过 with 方法指定:

php
foreach (Book::with('author')->get() as $book)
{
	echo $book->author->name;
}

在上面的循环中,仅会执行两个查询:

php
select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

明智地使用急切加载可以大幅提高应用程序的性能。

当然,您可以一次急切加载多个关系:

php
$books = Book::with('author', 'publisher')->get();

您甚至可以急切加载嵌套关系:

php
$books = Book::with('author.contacts')->get();

在上面的示例中,author 关系将被急切加载,作者的 contacts 关系也将被加载。

急切加载约束

有时,您可能希望急切加载关系,但还要为急切加载指定条件。以下是一个示例:

php
$users = User::with(array('posts' => function($query)
{
	$query->where('title', 'like', '%first%');

}))->get();

在此示例中,我们急切加载用户的帖子,但仅当帖子的标题列包含单词“first”时。

当然,急切加载闭包不仅限于“约束”。您还可以应用排序:

php
$users = User::with(array('posts' => function($query)
{
	$query->orderBy('created_at', 'desc');

}))->get();

懒惰急切加载

也可以直接从已经存在的模型集合中急切加载相关模型。这在动态决定是否加载相关模型时可能很有用,或者与缓存结合使用。

php
$books = Book::all();

$books->load('author', 'publisher');

插入相关模型

附加相关模型

您经常需要插入新的相关模型。例如,您可能希望为帖子插入新评论。您可以直接从其父 Post 模型插入新评论,而无需手动在模型上设置 post_id 外键:

php
$comment = new Comment(array('message' => 'A new comment.'));

$post = Post::find(1);

$comment = $post->comments()->save($comment);

在此示例中,post_id 字段将自动设置在插入的评论上。

如果您需要保存多个相关模型:

php
$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);

关联模型(属于)

在更新 belongsTo 关系时,您可以使用 associate 方法。此方法将在子模型上设置外键:

php
$account = Account::find(10);

$user->account()->associate($account);

$user->save();

插入相关模型(多对多)

您还可以在处理多对多关系时插入相关模型。让我们继续使用 UserRole 模型作为示例。我们可以使用 attach 方法轻松将新角色附加到用户:

附加多对多模型

php
$user = User::find(1);

$user->roles()->attach(1);

您还可以传递一个应存储在关系的中间表上的属性数组:

php
$user->roles()->attach(1, array('expires' => $expires));

当然,attach 的反面是 detach

php
$user->roles()->detach(1);

attachdetach 也接受 ID 数组作为输入:

php
$user = User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);

使用 Sync 附加多对多模型

您还可以使用 sync 方法附加相关模型。sync 方法接受一个 ID 数组以放置在中间表中。完成此操作后,数组中的 ID 将是模型的中间表中的唯一 ID:

php
$user->roles()->sync(array(1, 2, 3));

在同步时添加中间表数据

您还可以将其他中间表值与给定 ID 关联:

php
$user->roles()->sync(array(1 => array('expires' => true)));

有时,您可能希望创建一个新的相关模型并在单个命令中附加它。为此操作,您可以使用 save 方法:

php
$role = new Role(array('name' => 'Editor'));

User::find(1)->roles()->save($role);

在此示例中,将保存新的 Role 模型并附加到用户模型。您还可以传递一个属性数组以放置在此操作的连接表中:

php
User::find(1)->roles()->save($role, array('expires' => $expires));

更新父时间戳

当模型 belongsTo 另一个模型时,例如 Comment 属于 Post,在更新子模型时更新父模型的时间戳通常很有用。例如,当更新 Comment 模型时,您可能希望自动更新拥有的 Postupdated_at 时间戳。Eloquent 使这变得简单。只需在子模型中添加一个包含关系名称的 touches 属性:

php
class Comment extends Eloquent {

	protected $touches = array('post');

	public function post()
	{
		return $this->belongsTo('Post');
	}

}

现在,当您更新 Comment 时,拥有的 Post 将更新其 updated_at 列:

php
$comment = Comment::find(1);

$comment->text = 'Edit to this comment!';

$comment->save();

与中间表一起工作

正如您已经了解到的,处理多对多关系需要中间表的存在。Eloquent 提供了一些非常有用的方法来与此表进行交互。例如,假设我们的 User 对象与许多 Role 对象相关联。在访问此关系后,我们可以访问模型上的 pivot 表:

php
$user = User::find(1);

foreach ($user->roles as $role)
{
	echo $role->pivot->created_at;
}

请注意,我们检索到的每个 Role 模型都自动分配了一个 pivot 属性。此属性包含表示中间表的模型,并可以像其他 Eloquent 模型一样使用。

默认情况下,只有键将存在于 pivot 对象上。如果您的中间表包含额外的属性,则必须在定义关系时指定它们:

php
return $this->belongsToMany('Role')->withPivot('foo', 'bar');

现在,foobar 属性将在 Role 模型的 pivot 对象上可访问。

如果您希望中间表具有自动维护的 created_atupdated_at 时间戳,请在关系定义中使用 withTimestamps 方法:

php
return $this->belongsToMany('Role')->withTimestamps();

删除中间表上的记录

要删除模型的中间表上的所有记录,您可以使用 detach 方法:

php
User::find(1)->roles()->detach();

请注意,此操作不会从 roles 表中删除记录,而仅从中间表中删除记录。

在中间表上更新记录

有时,您可能需要更新中间表,但不想将其分离。如果您希望就地更新中间表,您可以使用 updateExistingPivot 方法,如下所示:

php
User::find(1)->roles()->updateExistingPivot($roleId, $attributes);

定义自定义中间模型

Laravel 还允许您定义自定义中间模型。要定义自定义模型,首先创建一个扩展 Eloquent 的自定义“基础”模型类。在其他 Eloquent 模型中,扩展此自定义基础模型,而不是默认的 Eloquent 基础模型。在您的基础模型中,添加以下函数以返回自定义中间模型的实例:

php
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
	return new YourCustomPivot($parent, $attributes, $table, $exists);
}

集合

通过 Eloquent 返回的所有多结果集,无论是通过 get 方法还是关系,都将返回一个集合对象。此对象实现了 IteratorAggregate PHP 接口,因此可以像数组一样进行迭代。但是,此对象还具有多种其他有用的方法来处理结果集。

检查集合是否包含键

例如,我们可以使用 contains 方法确定结果集是否包含给定的主键:

php
$roles = User::find(1)->roles;

if ($roles->contains(2))
{
	//
}

集合还可以转换为数组或 JSON:

php
$roles = User::find(1)->roles->toArray();

$roles = User::find(1)->roles->toJson();

如果将集合转换为字符串,则将以 JSON 形式返回:

php
$roles = (string) User::find(1)->roles;

迭代集合

Eloquent 集合还包含一些有用的方法,用于循环和过滤它们包含的项目:

php
$roles = $user->roles->each(function($role)
{
	//
});

过滤集合

在过滤集合时,提供的回调将用作 array_filter 的回调。

php
$users = $users->filter(function($user)
{
	return $user->isAdmin();
});

NOTE

在过滤集合并将其转换为 JSON 时,请尝试首先调用 values 函数以重置数组的键。

对每个集合对象应用回调

php
$roles = User::find(1)->roles;

$roles->each(function($role)
{
	//
});

按值对集合进行排序

php
$roles = $roles->sortBy(function($role)
{
	return $role->created_at;
});

按值对集合进行排序

php
$roles = $roles->sortBy('created_at');

返回自定义集合类型

有时,您可能希望返回一个带有您自己添加的方法的自定义集合对象。您可以通过重写 Eloquent 模型中的 newCollection 方法来指定这一点:

php
class User extends Eloquent {

	public function newCollection(array $models = array())
	{
		return new CustomCollection($models);
	}

}

访问器和修改器

定义访问器

Eloquent 提供了一种方便的方法来在获取或设置模型属性时转换它们。只需在模型上定义一个 getFooAttribute 方法即可声明访问器。请记住,这些方法应遵循驼峰命名法,即使您的数据库列是蛇形命名:

php
class User extends Eloquent {

	public function getFirstNameAttribute($value)
	{
		return ucfirst($value);
	}

}

在上面的示例中,first_name 列具有一个访问器。请注意,属性的值会传递给访问器。

定义修改器

修改器的声明方式类似:

php
class User extends Eloquent {

	public function setFirstNameAttribute($value)
	{
		$this->attributes['first_name'] = strtolower($value);
	}

}

日期修改器

默认情况下,Eloquent 将 created_atupdated_at 列转换为 Carbon 的实例,提供了一系列有用的方法,并扩展了原生 PHP DateTime 类。

您可以自定义哪些字段会自动变换,甚至完全禁用此变换,通过重写模型的 getDates 方法:

php
public function getDates()
{
	return array('created_at');
}

当列被视为日期时,您可以将其值设置为 UNIX 时间戳、日期字符串(Y-m-d)、日期时间字符串,当然还有 DateTime / Carbon 实例。

要完全禁用日期变换,只需从 getDates 方法返回一个空数组:

php
public function getDates()
{
	return array();
}

模型事件

Eloquent 模型触发多个事件,允许您在模型生命周期的各个点挂钩,使用以下方法:creatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored

每当首次保存新项目时,creatingcreated 事件将触发。如果项目不是新项目并且调用了 save 方法,则将触发 updating / updated 事件。在这两种情况下,saving / saved 事件也将触发。

通过事件取消保存操作

如果从 creatingupdatingsavingdeleting 事件返回 false,则将取消该操作:

php
User::creating(function($user)
{
	if ( ! $user->isValid()) return false;
});

设置模型引导方法

Eloquent 模型还包含一个静态 boot 方法,可以提供一个方便的地方来注册事件绑定。

php
class User extends Eloquent {

	public static function boot()
	{
		parent::boot();

		// 设置事件绑定...
	}

}

模型观察者

为了整合模型事件的处理,您可以注册模型观察者。观察者类可以具有与各种模型事件相对应的方法。例如,creatingupdatingsaving 方法可以在观察者中,以及任何其他模型事件名称。

因此,例如,模型观察者可能如下所示:

php
class UserObserver {

	public function saving($model)
	{
		//
	}

	public function saved($model)
	{
		//
	}

}

您可以使用 observe 方法注册观察者实例:

php
User::observe(new UserObserver);

转换为数组/JSON

将模型转换为数组

在构建 JSON API 时,您可能经常需要将模型及其加载的关系转换为数组或 JSON。因此,Eloquent 包含用于执行此操作的方法。要将模型及其加载的关系转换为数组,您可以使用 toArray 方法:

php
$user = User::with('roles')->first();

return $user->toArray();

请注意,整个模型集合也可以转换为数组:

php
return User::all()->toArray();

将模型转换为 JSON

要将模型转换为 JSON,您可以使用 toJson 方法:

php
return User::find(1)->toJson();

从路由返回模型

请注意,当模型或集合被转换为字符串时,它将被转换为 JSON,这意味着您可以直接从应用程序的路由返回 Eloquent 对象!

php
Route::get('users', function()
{
	return User::all();
});

从数组或 JSON 转换中隐藏属性

有时,您可能希望限制包含在模型的数组或 JSON 形式中的属性,例如密码。为此,请在模型中添加 hidden 属性定义:

php
class User extends Eloquent {

	protected $hidden = array('password');

}

NOTE

隐藏关系时,请使用关系的 方法 名称,而不是动态访问器名称。

或者,您可以使用 visible 属性定义白名单:

php
protected $visible = array('first_name', 'last_name');

偶尔,您可能需要添加没有对应数据库列的数组属性。为此,只需为该值定义一个访问器:

php
public function getIsAdminAttribute()
{
	return $this->attributes['admin'] == 'yes';
}

一旦您创建了访问器,只需将值添加到模型的 appends 属性中:

php
protected $appends = array('is_admin');

一旦属性被添加到 appends 列表中,它将包含在模型的数组和 JSON 形式中。appends 列表中的属性遵循模型上的 visiblehidden 配置。