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 配置。