Skip to content

Eloquent ORM

介绍

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

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

基本用法

要开始使用,请创建一个Eloquent模型。模型通常位于app目录中,但您可以根据composer.json文件的自动加载规则将它们放置在任何地方。所有Eloquent模型都继承自Illuminate\Database\Eloquent\Model

定义一个Eloquent模型

php
class User extends Model {}

您还可以使用make:model命令生成Eloquent模型:

php
php artisan make:model User

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

php
class User extends Model {

	protected $table = 'my_users';

}
lightbulb

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

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

检索所有记录

php
$users = User::all();

通过主键检索记录

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

var_dump($user->name);
lightbulb

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

通过主键检索模型或抛出异常

有时您可能希望在找不到模型时抛出异常。为此,您可以使用firstOrFail方法:

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

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

这样做将允许您捕获异常,以便根据需要记录和显示错误页面。要捕获ModelNotFoundException,请在app/Exceptions/Handler.php文件中添加一些逻辑。

php
use Illuminate\Database\Eloquent\ModelNotFoundException;

class Handler extends ExceptionHandler {

	public function render($request, Exception $e)
	{
		if ($e instanceof ModelNotFoundException)
		{
			// 自定义逻辑处理模型未找到...
		}

		return parent::render($request, $e);
	}

}

使用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', [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);

如果您正在使用读/写连接,您可以使用以下方法强制查询使用“写”连接:

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

批量赋值

在创建新模型时,您将属性数组传递给模型构造函数。然后,这些属性通过批量赋值分配给模型。这很方便;然而,当盲目地将用户输入传递给模型时,可能会成为一个严重的安全问题。如果用户输入被盲目地传递给模型,用户可以自由修改模型的任何所有属性。出于这个原因,所有Eloquent模型默认保护批量赋值。

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

在模型上定义可填充属性

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

php
class User extends Model {

	protected $fillable = ['first_name', 'last_name', 'email'];

}

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

在模型上定义受保护属性

fillable的反义词是guarded,它充当“黑名单”而不是“白名单”:

php
class User extends Model {

	protected $guarded = ['id', 'password'];

}
lightbulb

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

阻止所有属性的批量赋值

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

php
protected $guarded = ['*'];

插入、更新、删除

要从模型中在数据库中创建新记录,只需创建一个新模型实例并调用save方法。

保存新模型

php
$user = new User;

$user->name = 'John';

$user->save();
lightbulb

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

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

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

php
$insertedId = $user->id;

在模型上设置受保护属性

php
class User extends Model {

	protected $guarded = ['id', 'account_id'];

}

使用模型创建方法

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

// 通过属性检索用户,如果不存在则创建它...
$user = User::firstOrCreate(['name' => 'John']);

// 通过属性检索用户,如果不存在则实例化一个新实例...
$user = User::firstOrNew(['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(['status' => 2]);
lightbulb

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

删除现有模型

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

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

$user->delete();

通过键删除现有模型

php
User::destroy(1);

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

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

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

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

仅更新模型的时间戳

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

php
$user->touch();

软删除

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

php
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model {

	use SoftDeletes;

	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 Model {

	protected $table = 'users';

	public $timestamps = false;

}

提供自定义时间戳格式

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

php
class User extends Model {

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

}

查询范围

定义查询范围

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

php
class User extends Model {

	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 Model {

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

}

然后将参数传递给范围调用:

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

全局范围

有时您可能希望定义一个适用于模型上所有查询的范围。从本质上讲,这就是Eloquent自己的“软删除”功能的工作方式。全局范围是使用PHP特性和Illuminate\Database\Eloquent\ScopeInterface的实现组合定义的。

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

php
trait SoftDeletes {

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

}

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

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

php
/**
 * 将范围应用于给定的Eloquent查询构建器。
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @return void
 */
public function apply(Builder $builder, Model $model)
{
	$builder->whereNull($model->getQualifiedDeletedAtColumn());

	$this->extend($builder);
}

/**
 * 从给定的Eloquent查询构建器中移除范围。
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @return void
 */
public function remove(Builder $builder, Model $model)
{
	$column = $model->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 Model {

	public function phone()
	{
		return $this->hasOne('App\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('App\Phone', 'foreign_key');

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

定义关系的反向

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

php
class Phone extends Model {

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

}

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

php
class Phone extends Model {

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

}

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

php
class Phone extends Model {

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

}

一对多

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

php
class Post extends Model {

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

}

现在我们可以通过动态属性访问文章的评论:

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

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

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

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

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

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

定义关系的反向

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

php
class Comment extends Model {

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

}

多对多

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

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

php
class User extends Model {

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

}

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

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

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

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

您还可以覆盖常规关联键:

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

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

php
class Role extends Model {

	public function users()
	{
		return $this->belongsToMany('App\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 Model {

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

}

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

php
class Country extends Model {

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

}

多态关系

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

php
class Photo extends Model {

	public function imageable()
	{
		return $this->morphTo();
	}

}

class Staff extends Model {

	public function photos()
	{
		return $this->morphMany('App\Photo', 'imageable');
	}

}

class Order extends Model {

	public function photos()
	{
		return $this->morphMany('App\Photo', 'imageable');
	}

}

检索多态关系

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

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

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

检索多态关系的所有者

然而,真正的“多态”魔法是在您从Photo模型访问员工或订单时:

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

$imageable = $photo->imageable;

Photo模型上的imageable关系将返回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 Model {

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

}

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

php
class Tag extends Model {

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

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

}

查询关系

在选择时查询关系

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

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

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

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

嵌套的has语句也可以使用“点”符号构造:

php
$posts = Post::has('comments.votes')->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 Model {

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

}

$phone = Phone::find(1);

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

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

可以简化为:

php
echo $phone->user->email;
lightbulb

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

预加载

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

php
class Book extends Model {

	public function author()
	{
		return $this->belongsTo('App\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(['posts' => function($query)
{
	$query->where('title', 'like', '%first%');

}])->get();

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

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

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

}])->get();

延迟预加载

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

php
$books = Book::all();

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

您还可以传递一个闭包来设置查询的约束:

php
$books->load(['author' => function($query)
{
	$query->orderBy('published_date', 'asc');
}]);

插入关联模型

附加关联模型

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

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

$post = Post::find(1);

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

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

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

php
$comments = [
	new Comment(['message' => 'A new comment.']),
	new Comment(['message' => 'Another comment.']),
	new Comment(['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, ['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将在模型的中间表上:

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

在同步时添加枢纽数据

您还可以将其他枢纽表值与给定的ID关联:

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

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

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

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

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

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

更新父级时间戳

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

php
class Comment extends Model {

	protected $touches = ['post'];

	public function post()
	{
		return $this->belongsTo('App\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('App\Role')->withPivot('foo', 'bar');

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

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

php
return $this->belongsToMany('App\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方法还是通过relationship,都将返回一个集合对象。此对象实现了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();
});
lightbulb

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

对每个集合对象应用回调

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

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

按值排序集合

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

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

按值排序集合

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

$roles = $roles->sortByDesc('created_at');

返回自定义集合类型

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

php
class User extends Model {

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

}

访问器和修改器

定义访问器

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

php
class User extends Model {

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

}

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

定义修改器

修改器的声明方式类似:

php
class User extends Model {

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

}

日期修改器

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

您可以自定义哪些字段会自动修改,甚至完全禁用此修改,方法是覆盖模型的getDates方法:

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

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

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

php
public function getDates()
{
	return [];
}

属性类型转换

如果您有一些属性希望始终转换为其他数据类型,可以将属性添加到模型的casts属性中。否则,您将不得不为每个属性定义一个修改器,这可能会耗费时间。以下是使用casts属性的示例:

php
/**
 * 应该转换为本机类型的属性。
 *
 * @var array
 */
protected $casts = [
	'is_admin' => 'boolean',
];

现在,即使底层值在数据库中存储为整数,is_admin属性在访问时也将始终转换为布尔值。其他支持的转换类型有:integerrealfloatdoublestringbooleanobjectarray

array转换对于处理存储为序列化JSON的列特别有用。例如,如果您的数据库有一个包含序列化JSON的TEXT类型字段,将array转换添加到该属性将自动在访问Eloquent模型时将属性反序列化为PHP数组:

php
/**
 * 应该转换为本机类型的属性。
 *
 * @var array
 */
protected $casts = [
	'options' => 'array',
];

现在,当您使用Eloquent模型时:

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

// $options是一个数组...
$options = $user->options;

// options会自动序列化回JSON...
$user->options = ['foo' => 'bar'];

模型事件

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

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

通过事件取消保存操作

如果 creatingupdatingsavingdeleting 事件返回 false,操作将被取消:

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

在哪里注册事件监听器

你的 EventServiceProvider 是一个方便的地方来注册你的模型事件绑定。例如:

php
/**
 * 为应用程序注册其他事件。
 *
 * @param  \Illuminate\Contracts\Events\Dispatcher  $events
 * @return void
 */
public function boot(DispatcherContract $events)
{
	parent::boot($events);

	User::creating(function($user)
	{
		//
	});
}

模型观察者

为了整合模型事件的处理,你可以注册一个模型观察者。观察者类可以有与各种模型事件对应的方法。例如,creatingupdatingsaving 方法可以在观察者中,除此之外还有其他任何模型事件名称。

例如,一个模型观察者可能如下所示:

php
class UserObserver {

	public function saving($model)
	{
		//
	}

	public function saved($model)
	{
		//
	}

}

你可以使用 observe 方法注册一个观察者实例:

php
User::observe(new UserObserver);

模型 URL 生成

当你将一个模型传递给 routeaction 方法时,它的主键会被插入到生成的 URI 中。例如:

php
Route::get('user/{user}', 'UserController@show');

action('UserController@show', [$user]);

在这个例子中,$user->id 属性将被插入到生成的 URL 的 {user} 占位符中。然而,如果你想使用 ID 以外的其他属性,你可以在模型上重写 getRouteKey 方法:

php
public function getRouteKey()
{
	return $this->slug;
}

转换为数组 / 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 Model {

	protected $hidden = ['password'];

}
lightbulb

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

或者,你可以使用 visible 属性来定义一个白名单:

php
protected $visible = ['first_name', 'last_name'];

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

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

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

php
protected $appends = ['is_admin'];

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