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';

}

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 模型时也可用。

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

有时您可能希望在找不到模型时抛出异常。为此,您可以使用 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'];

}

NOTE

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

阻止所有属性的批量赋值

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

php
protected $guarded = ['*'];

插入、更新、删除

要从模型中在数据库中创建新记录,只需创建一个新的模型实例并调用 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 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]);

NOTE

使用 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;

NOTE

返回多个结果的关系将返回 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 的“Base”模型类。在其他 Eloquent 模型中,扩展此自定义基模型而不是默认的 Eloquent 基。在基模型中,添加以下函数以返回自定义枢纽模型的实例:

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

集合

所有通过 get 方法或 relationship 返回的 Eloquent 多结果集将返回一个集合对象。此对象实现了 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;
});

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

按值排序集合

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

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

返回自定义集合类型

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

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'];

}

NOTE

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

或者,您可以使用 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 配置。