Eloquent ORM
介绍
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('未找到', 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
命令将允许您在不消耗所有 RAM 的情况下进行处理:
User::chunk(200, function($users)
{
foreach ($users as $user)
{
//
}
});
传递给该方法的第一个参数是您希望每个“块”接收的记录数。作为第二个参数传递的闭包将在从数据库中提取的每个块上调用。
指定查询连接
您还可以指定在运行 Eloquent 查询时应使用哪个数据库连接。只需使用 on
方法:
$user = User::on('connection-name')->find(1);
批量赋值
创建新模型时,您将一个属性数组传递给模型构造函数。这些属性随后通过批量赋值分配给模型。这很方便;但是,当盲目地将用户输入传递给模型时,可能会造成 严重 的安全隐患。如果用户输入被盲目传递给模型,用户可以自由修改 任何 和 所有 模型的属性。因此,所有 Eloquent 模型默认会防止批量赋值。
要开始,请在模型上设置 fillable
或 guarded
属性。
在模型上定义可填充属性
fillable
属性指定哪些属性应可批量赋值。这可以在类级别或实例级别设置。
class User extends Eloquent {
protected $fillable = array('first_name', 'last_name', 'email');
}
在此示例中,只有列出的三个属性将是可批量赋值的。
在模型上定义受保护属性
fillable
的反面是 guarded
,它作为“黑名单”而不是“白名单”:
class User extends Eloquent {
protected $guarded = array('id', 'password');
}
使用 guarded
时,您仍然不应将 Input::get()
或任何原始用户控制输入数组传递给 save
或 update
方法,因为任何未被保护的列都可能被更新。
阻止所有属性的批量赋值
在上面的示例中,id
和 password
属性 不能 被批量赋值。所有其他属性将是可批量赋值的。您还可以使用保护属性阻止 所有 属性的批量赋值:
protected $guarded = array('*');
插入、更新、删除
要从模型创建新记录,只需创建一个新的模型实例并调用 save
方法。
保存新模型
$user = new User;
$user->name = 'John';
$user->save();
通常,您的 Eloquent 模型将具有自增键。但是,如果您希望指定自己的键,请将模型上的 incrementing
属性设置为 false
。
您还可以使用 create
方法在一行中保存新模型。插入的模型实例将从该方法返回。但是,在这样做之前,您需要在模型上指定 fillable
或 guarded
属性,因为所有 Eloquent 模型都防止批量赋值。
保存或创建使用自增 ID 的新模型后,您可以通过访问对象的 id
属性来检索 ID:
$insertedId = $user->id;
设置模型上的受保护属性
class User extends Eloquent {
protected $guarded = array('id', 'account_id');
}
使用模型创建方法
// 在数据库中创建新用户...
$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
列。只需将这些 timestamp
列添加到您的表中,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();
全局作用域
有时,您可能希望定义一个适用于对模型执行的所有查询的作用域。实际上,这就是 Eloquent 自身的“软删除”功能的工作原理。全局作用域是使用 PHP 特性和 Illuminate\Database\Eloquent\ScopeInterface
的实现组合定义的。
首先,让我们定义一个特性。对于这个示例,我们将使用 Laravel 附带的 SoftDeletingTrait
:
trait SoftDeletingTrait {
/**
* 为模型引导软删除特性。
*
* @return void
*/
public static function bootSoftDeletingTrait()
{
static::addGlobalScope(new SoftDeletingScope);
}
}
如果 Eloquent 模型使用具有与 bootNameOfTrait
命名约定匹配的方法的特性,则在引导 Eloquent 模型时将调用该特性方法,从而使您有机会注册全局作用域或执行其他任何操作。作用域必须实现 ScopeInterface
,该接口指定两个方法:apply
和 remove
。
apply
方法接收一个 Illuminate\Database\Eloquent\Builder
查询构建器对象,并负责添加作用域希望添加的任何附加 where
子句。remove
方法也接收一个 Builder
对象,并负责撤消 apply
所采取的操作。换句话说,remove
应该删除添加的 where
子句(或任何其他子句)。因此,对于我们的 SoftDeletingScope
,方法看起来像这样:
/**
* 将作用域应用于给定的 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 中定义此关系:
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');
}
}
一对多
一对多关系的示例是一个博客文章“拥有许多”评论。我们可以这样建模此关系:
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');
}
}
多对多
多对多关系是一种更复杂的关系类型。例如,用户可能有许多角色,而这些角色也被其他用户共享。例如,许多用户可能具有“管理员”角色。此关系需要三个数据库表:users
、roles
和 role_user
。role_user
表是根据相关模型名称的字母顺序派生的,并应具有 user_id
和 role_id
列。
我们可以使用 belongsToMany
方法定义多对多关系:
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
现在,我们可以通过 User
模型检索角色:
$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
访问一个国家的帖子。让我们定义关系:
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');
}
}
多态关系
多态关系允许一个模型属于多个其他模型,建立单一关联。例如,您可能有一个照片模型,它可以属于员工模型或订单模型。我们将这样定义此关系:
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 = Staff::find(1);
foreach ($staff->photos as $photo)
{
//
}
检索多态关系的所有者
然而,真正的“多态”魔力在于您可以从 Photo
模型访问员工或订单:
$photo = Photo::find(1);
$imageable = $photo->imageable;
imageable
关系在 Photo
模型上将返回 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 将包含拥有者的 ID 值,而在此示例中,类型将包含拥有模型的类名。这就是 ORM 在访问 imageable
关系时确定返回哪种类型的拥有模型的原因。
多态多对多关系
多态多对多关系表结构
除了传统的多态关系外,您还可以指定多态多对多关系。例如,博客 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');
}
}
查询关系
查询选择时的关系
在访问模型的记录时,您可能希望根据关系的存在限制结果。例如,您希望提取至少有一个评论的所有博客文章。为此,您可以使用 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);
而不是像这样回显用户的电子邮件:
echo $phone->user()->first()->email;
可以简化为:
echo $phone->user->email;
返回多个结果的关系将返回 Illuminate\Database\Eloquent\Collection
类的实例。
急切加载
急切加载旨在缓解 N + 1 查询问题。例如,考虑一个与 Author
相关的 Book
模型。关系定义如下:
class Book extends Eloquent {
public function author()
{
return $this->belongsTo('Author');
}
}
现在,考虑以下代码:
foreach (Book::all() as $book)
{
echo $book->author->name;
}
此循环将执行 1 个查询以检索表中的所有书籍,然后为每本书执行另一个查询以检索作者。因此,如果我们有 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
关系将被急切加载,作者的 contacts
关系也将被加载。
急切加载约束
有时,您可能希望急切加载关系,但还要为急切加载指定条件。以下是一个示例:
$users = User::with(array('posts' => function($query)
{
$query->where('title', 'like', '%first%');
}))->get();
在此示例中,我们急切加载用户的帖子,但仅当帖子的标题列包含单词“first”时。
当然,急切加载闭包不仅限于“约束”。您还可以应用排序:
$users = User::with(array('posts' => function($query)
{
$query->orderBy('created_at', 'desc');
}))->get();
懒惰急切加载
也可以直接从已经存在的模型集合中急切加载相关模型。这在动态决定是否加载相关模型时可能很有用,或者与缓存结合使用。
$books = Book::all();
$books->load('author', 'publisher');
插入相关模型
附加相关模型
您经常需要插入新的相关模型。例如,您可能希望为帖子插入新评论。您可以直接从其父 Post
模型插入新评论,而无需手动在模型上设置 post_id
外键:
$comment = new Comment(array('message' => 'A new comment.'));
$post = Post::find(1);
$comment = $post->comments()->save($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);
关联模型(属于)
在更新 belongsTo
关系时,您可以使用 associate
方法。此方法将在子模型上设置外键:
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
插入相关模型(多对多)
您还可以在处理多对多关系时插入相关模型。让我们继续使用 User
和 Role
模型作为示例。我们可以使用 attach
方法轻松将新角色附加到用户:
附加多对多模型
$user = User::find(1);
$user->roles()->attach(1);
您还可以传递一个应存储在关系的中间表上的属性数组:
$user->roles()->attach(1, array('expires' => $expires));
当然,attach
的反面是 detach
:
$user->roles()->detach(1);
attach
和 detach
也接受 ID 数组作为输入:
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);
使用 Sync 附加多对多模型
您还可以使用 sync
方法附加相关模型。sync
方法接受一个 ID 数组以放置在中间表中。完成此操作后,数组中的 ID 将是模型的中间表中的唯一 ID:
$user->roles()->sync(array(1, 2, 3));
在同步时添加中间表数据
您还可以将其他中间表值与给定 ID 关联:
$user->roles()->sync(array(1 => array('expires' => true)));
有时,您可能希望创建一个新的相关模型并在单个命令中附加它。为此操作,您可以使用 save
方法:
$role = new Role(array('name' => 'Editor'));
User::find(1)->roles()->save($role);
在此示例中,将保存新的 Role
模型并附加到用户模型。您还可以传递一个属性数组以放置在此操作的连接表中:
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
对象上。如果您的中间表包含额外的属性,则必须在定义关系时指定它们:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
现在,foo
和 bar
属性将在 Role
模型的 pivot
对象上可访问。
如果您希望中间表具有自动维护的 created_at
和 updated_at
时间戳,请在关系定义中使用 withTimestamps
方法:
return $this->belongsToMany('Role')->withTimestamps();
删除中间表上的记录
要删除模型的中间表上的所有记录,您可以使用 detach
方法:
User::find(1)->roles()->detach();
请注意,此操作不会从 roles
表中删除记录,而仅从中间表中删除记录。
在中间表上更新记录
有时,您可能需要更新中间表,但不想将其分离。如果您希望就地更新中间表,您可以使用 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);
}
集合
通过 Eloquent 返回的所有多结果集,无论是通过 get
方法还是关系,都将返回一个集合对象。此对象实现了 IteratorAggregate
PHP 接口,因此可以像数组一样进行迭代。但是,此对象还具有多种其他有用的方法来处理结果集。
检查集合是否包含键
例如,我们可以使用 contains
方法确定结果集是否包含给定的主键:
$roles = User::find(1)->roles;
if ($roles->contains(2))
{
//
}
集合还可以转换为数组或 JSON:
$roles = User::find(1)->roles->toArray();
$roles = User::find(1)->roles->toJson();
如果将集合转换为字符串,则将以 JSON 形式返回:
$roles = (string) User::find(1)->roles;
迭代集合
Eloquent 集合还包含一些有用的方法,用于循环和过滤它们包含的项目:
$roles = $user->roles->each(function($role)
{
//
});
过滤集合
在过滤集合时,提供的回调将用作 array_filter 的回调。
$users = $users->filter(function($user)
{
return $user->isAdmin();
});
在过滤集合并将其转换为 JSON 时,请尝试首先调用 values
函数以重置数组的键。
对每个集合对象应用回调
$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
方法即可声明访问器。请记住,这些方法应遵循驼峰命名法,即使您的数据库列是蛇形命名:
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
方法:
public function getDates()
{
return array('created_at');
}
当列被视为日期时,您可以将其值设置为 UNIX 时间戳、日期字符串(Y-m-d
)、日期时间字符串,当然还有 DateTime
/ Carbon
实例。
要完全禁用日期变换,只需从 getDates
方法返回一个空数组:
public function getDates()
{
return array();
}
模型事件
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;
});
设置模型引导方法
Eloquent 模型还包含一个静态 boot
方法,可以提供一个方便的地方来注册事件绑定。
class User extends Eloquent {
public static function boot()
{
parent::boot();
// 设置事件绑定...
}
}
模型观察者
为了整合模型事件的处理,您可以注册模型观察者。观察者类可以具有与各种模型事件相对应的方法。例如,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();
请注意,整个模型集合也可以转换为数组:
return User::all()->toArray();
将模型转换为 JSON
要将模型转换为 JSON,您可以使用 toJson
方法:
return User::find(1)->toJson();
从路由返回模型
请注意,当模型或集合被转换为字符串时,它将被转换为 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 形式中。appends
列表中的属性遵循模型上的 visible
和 hidden
配置。