Eloquent 是基于查询构造器的数据库管理工具。 ActiveRecord ORM,为多种数据库类型提供单一抽象接口。MVC中的M层。
创建 Eloquent
php artisan make:model Contact# 使用迁移 -m 或者 --migrationphp artisan make:model Contact --migration
将得到 app/Contact.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
//
}
表名
默认 SecondaryContact 将会得到 secondary_contacts 表;
如果 自定义,请显式的设置:
protected $table = 'contacts_secondary';
主键
默认 id ;
如果 自定义,请显式设置:
protected $primaryKey = 'contact_id';
如果 不设置自增长:
public $incrementing = false;
时间戳
默认 created_at 和 updated_at
如果 你没有他们,请禁用:
public $timestamps = false;
如果 你想自定义存储到数据库的时间格式,就像 php 的 date 函数解析的那样,参考如下例子(记录 从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数)
protected $dateFormat = 'U';
获取数据
查询构造器 + 链式操作,你可以做很多事。
Get one
first() , firstOrFail() , find(id) , or findOrFail(id)
tip: 在控制器中使用 *OrFail() 方法时,我们不需要捕获异常,laravel 的
路由系统会捕获它,并返回我们一个404错误。
Get many
get() , all()
$vipContacts = Contact::where('vip', true)->get();
$contacts = Contact::all();
tip: 忘记 all() 的存在,优先使用 get() , get() 可以增加 where() 过滤。
chunk()
处理大量数据时,内存占用过多,chunk() 可以帮你分块处理,类似 yii2 中的 each() 和 batch()。
Contact::chunk(100, function ($contacts) {
foreach ($contacts as $contact) {
// Do something with $contact
}
});
统计
$countVips = Contact::where('vip', true)->count();
$sumVotes = Contact::sum('votes');
$averageSkill = User::avg('skill_level');
插入和更新模型
插入
// 方法1
$contact = new Contact;
$contact->name = 'Ken Hirata';
$contact->email = 'ken@hirata.com';
$contact->save();
// 方法2
$contact = new Contact([
'name' => 'Ken Hirata',
'email' => 'ken@hirata.com',
]);
$contact->save();
// or
$contact = Contact::make([
'name' => 'Ken Hirata',
'email' => 'ken@hirata.com',
]);
$contact->save();
以上方法 save() 之前不保存到数据库,即 没有 id, 也没有自动填充的字段,如 created_at 等
// 直接插入数据库 不需要 save()
$contact = Contact::create([
'name' => 'Ken Hirata',
'email' => 'ken@hirata.com',
]);
tip: new Model(), Model::make(), Model::create(), Model::update(), 传入的数组必须是 mass assignment(见下文)。
更新
// 单个更新
$contact = Contact::find(1);
$contact->email = 'natalie@parkfamily.com';
$contact->save();
// or
$contact = Contact::find(1);
$contact->update(['email' => 'natalie@parkfamily.com']);
// 批量更新
Contact::where('created_at', '<', now()->subYear())->update(['longevity' => 'ancient']);
Mass assignment 批量分配
假设您有一个 students 表,其字段为 "student_type, first_name, last_name"。您可能想批量分配 " first_name, last_name ",但是您想保护 student_type 避免被直接更改,这是 可填充的(fillable) 和 被守护的(guarded) 的地方。
更新程序 如下:
// StudentController
public function update(Student $student, Request $request)
{
$student->update($request->all());
}
如果恶意用户传入了 student_type 字段,则程序就会被直接修改,我们不希望用户自行修改 student_type 字段。 试想一些如果在更新 user 数据时,用户传入 password 字段,这是非常危险的。
laravel 提供了 Mass assignment 解决这个问题
class Student extends Model
{
protected $fillable = ['first_name', 'last_name'];
// or
protected $guarded = ['id', 'created_at', 'updated_at', 'student_type'];
}
这样就解决了,上述问题.
其中 非 fillable 的字段还是可以通过直接分配修改的, eg: $user->password = '123456';。
tip: 上例中,Request 对象的 all() 获取了用户的所有输入,其实 Request 还有一个 only() 方法用于采取用户的输入。那么,上述问题,也可以按照如下方式解决:
$student->update($request->only('first_name', 'last_name'));
firstOrCreate() 和 firstOrNew() 还有 updateOrCreate()
业务需求: 查找一条符合某个属性的记录,如果没有,则创建。
$contact = Contact::firstOrCreate(['email' => 'luis.ramos@myacme.com']);
// or
$contact = Contact::firstOrNew(['email' => 'luis.ramos@myacme.com']);
区别: firstOrCreate() 查找完直接入库了,firstOrNew() 要手动 save() 才能存入数据库。
第二个参数
$contact = Contact::firstOrCreate(['email' => 'luis.ramos@myacme.com'], ['name' => 'Luis Ramos']);
// or
$contact = Contact::firstOrNew(['email' => 'luis.ramos@myacme.com'], ['name' => 'Luis Ramos']);
如果根据条件(第一个参数),没查找到记录,则创建记录,并追加第二个参数的值到记录中; 如果找到了记录,则忽略第二个参数的值。
如果你想,无论记录找到与否,都按照第二个参数修改记录,则可以使用 updateOrCreate() 方法。
删除
正常删除
// 方法1
$contact = Contact::find(5);
$contact->delete();
// 方法2
Contact::destroy(1);
// or
Contact::destroy([1, 5, 7]);
// 方法3
Contact::where('updated_at', '<', now()->subYear())->delete();
软删除
软删除是,数据库状态标记为删除,但并非真正删除,方便管理员恢复和查看数据。
软删除的麻烦之处在于, 每次查询都要排除软删除。
幸运的是 laravel 的软删除默认会自动排除软删除。你只需在数据库加入 delete_at 列,并在模型中启用软删除。
启用软删除
// 1. 迁移中加入 delete_at 列
Schema::table('contacts', function (Blueprint $table) {
$table->softDeletes();
});
// 2. 模型中启用软删除
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Contact extends Model
{
use SoftDeletes; // use the trait
protected $dates = ['deleted_at']; // mark this column as a date
}
只要你完成了以上改变,每次调用 delete() 或 destroy() 时,就自动修改 delete_at 类为当期时间(标记删除),而不是直接删除它,而且所有的查询也将排除此行。
查询软删除
那么怎么查询软删除。
// 查询 包含 软删除的内容
$allHistoricContacts = Contact::withTrashed()->get();
// 判断记录是否是已经删除的
if ($contact->trashed()) {
// do something
}
// 只获取软删除的内容
$deletedContacts = Contact::onlyTrashed()->get();
恢复软删除
$contact->restore();
// or
Contact::onlyTrashed()->where('vip', true)->restore();
强制删除
强制删除开启了软删除的的记录。
$contact->forceDelete();
// or
Contact::onlyTrashed()->forceDelete();
Scopes
截止目前为止,我们一直都在手动创建查询构造器。
Scope 运行我们预构建查询过滤器,Scope 分为局部 Scope,可以直接链式调用;和 全局 Scope, 每次查询将自动调用。
Local scope (局部)
// 活跃的vip
$activeVips = Contact::where('vip', true)->where('trial', false)->get();
我们可能在很多地方用到这个查询,而且这个写法对开发者并不友好。
// 这样就好多了
$activeVips = Contact::activeVips()->get();
这里使用了 local scope ,要使以上代码生效,我们还要做。
class Contact
{
public function scopeActiveVips($query)
{
return $query->where('vip', true)->where('trial', false);
}
// code more...
使用参数
class Contact
{
public function scopeStatus($query, $status)
{
return $query->where('status', $status);
}
// code more...
$friends = Contact::status('friend')->get();
global scope (全局)
使用闭包创建全局 scope
class Contact extends Model
{
protected static function boot()
{
parent::boot();
// 增加了名为 active 的 scope
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('active', true);
});
}
这样,每次查询,就只会找到 active 为 true 的记录。
为 scope 单独定义类,实现 scope 接口
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
// 定义了名为 active 的 scope类
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
return $builder->where('active', true);
}
}
要想使用它,要做如下操作
<?php
use App\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ActiveScope);
}
}
移除 全局scope
// 闭包情况
$allContacts = Contact::withoutGlobalScope('active')->get();
// 独立类
Contact::withoutGlobalScope(ActiveScope::class)->get();
Contact::withoutGlobalScopes([ActiveScope::class, VipScope::class])->get();
// 移除所有
Contact::withoutGlobalScopes()->get();
获取器、修改器和属性转换
获取器
获取器,允许自定义字段,可能你想自定义已存在字段的输出形式,或者直接获取一个数据库不存在的字段。
编写 get{PascalCasedPropertyName}Attribute 方法来定义获取器。
// 定义:
class Contact extends Model
{
// 修饰已存在的列
public function getNameAttribute($value)
{
return $value ?: '(No name provided)';
}
// 修饰不存在的列
public function getFullNameAttribute()
{
return $this->first_name . $this->last_name;
}
}
// 使用:
$name = $contact->name;
$fullName = $contact->full_name;
修改器
和获取器一样,修改器用于设置数据,而不是获取数据。你可以用于写入已存在的字段,或者设置一个数据库不存在的字段。
编写 set{PascalCasedPropertyName}Attribute 的方法来定义修改器
// 定义:
class Order extends Model
{
// 设置存在的字段
public function setAmountAttribute($value)
{
$this->attributes['amount'] = $value > 0 ? $value : 0;
}
// 设置不存在的值
public function setWorkgroupNameAttribute($value)
{
$this->attributes['email'] = "{$workgroupName}@ourcompany.com";
}
}
// 使用:
$order->amount = '15';
$order->workgroup_name = 'jstott';
属性转换
属性转换 允许你 将所有整数类型字段转换为整数,对JSON进行编码和解码以存储在TEXT类型的列中,或者将TINYINT 0和1与布尔值进行相互转换。
所有的属性转换列表
| Type | Description |
|---|---|
| int|integer | Casts with PHP ( int ) |
| real|float|double | Casts with PHP ( float ) |
| string | Casts with PHP ( string ) |
| bool | boolean |
| object | Parses to/from JSON, as a stdClass object |
| array | Parses to/from JSON, as an array |
| collection | Parses to/from JSON, as a collection |
| date|datetime | Parses from database DATETIME to Carbon, and back |
| timestamp | Parses from database TIMESTAMP to Carbon, and back |
使用模型转换
class Contact
{
protected $casts = [
'vip' => 'boolean',
'children_names' => 'array',
'birthday' => 'date',
];
}
选定列为时间戳列
class Contact
{
protected $dates = [
'met_at',
];
// or
protected $casts = [
'met_at' => 'timestamp',
];
}
默认 created_at 和 updated_at 已经在 dates 数组中了。
Collections
使用模型时,返回 collection 而不是纯数组。
Collection 基础
laravel 的 collection 类似于一个对象的聚合,它对外释放的方法非常有用,如果您想在非 laravel 项目中使用它,请引入 Tightenco/Collect 包。
// 创建
$collection = collect([1, 2, 3]);
// 过滤掉偶数
$odds = $collection->reject(function($item){
return $item % 2 === 0;
});
// 整体扩大10倍
$multiplied = $collection->map(function ($item) {
return $item * 10;
});
// 偶数 扩大10倍 再求和
$sum = $collection
->filter(function ($item) {
return $item % 2 == 0;
})->map(function ($item) {
return $item * 10;
})->sum();
你会发现部分功能 array_map() 和 array_reduce() 也可以实现,但是 collection 可以让你从原生 php 混乱的参数顺序中解放出来。
collection 提供超过60种方法, 详细见 https://laravel.com/docs/master/collections
tip: collection 可以被用于任何地方,只要这个地方可以用数组(除强制指定数组)。例如,你可以 foreach 一个 collection, 或者 collection['key'] 这样获取 collection 中的特定值。
Eloquent 的 collection
每个模型都返回一个 正常的 collection, 而且对这个 collection 进行了功能扩充。
例如: modelKeys() 返回模型的主键。
另外一项功能是,为模型的 collection 扩充功能,见下例。
// 为模型自定义 collection 类
class OrderCollection extends Collection
{
public function sumBillableAmount()
{
return $this->reduce(function ($carry, $order) {
return $carry + ($order->billable ? $order->amount : 0);
}, 0);
}
}
class Order extends Model
{
public function newCollection(array $models = [])
{
return new OrderCollection($models);
}
// ...
现在,任何时候你得到 order collection 时,其实你得到了一个 OrderCollection 的实例。
$orders = Order::all();
$billableAmount = $orders->sumBillableAmount();
Eloquent 的序列化
$contactArray = Contact::first()->toArray();
$contactJson = Contact::first()->toJson();
$contactsArray = Contact::all()->toArray();
$contactsJson = Contact::all()->toJson();
$string = (string) $contact; // 这是允许的,本质是运行 toJson() 方法
路由默认转json
// routes/web.php
Route::get('api/contacts', function () {
return Contact::all();
});
Route::get('api/contacts/{id}', function ($id) {
return Contact::findOrFail($id);
});
json中隐藏部分属性
class Contact extends Model
{
protected $hidden = ['password', 'remember_token'];
// or
protected $visible = ['name', 'email', 'status'];
// 模型关联中也可以
class User extends Model
{
protected $hidden = ['contacts'];
public function contacts()
{
return $this->hasMany(Contact::class);
}
// ...
}
tip: 默认当你获取数据库记录时是不显示关联信息的,所有隐藏与否没什么关系。但是也有要包含关联的情况,所以就需要这个功能。如果要显示隐藏,如下
$user = User::with('contacts')->first();
某次查询可能要显示隐藏的内容,如下:
$array = $user->makeVisible('remember_token')->toArray();
tip: 如果你创建了一个字段不存在的获取器,你想添加该字段到json中,如下:
class Contact extends Model
{
// 每次都追加
protected $appends = ['full_name'];
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
// 单次追加
return $user->append('is_admin')->toArray();
return $user->setAppends(['is_admin'])->toArray();
日期序列化
// 自定义默认日期格式
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d');
}
// 为每个字段单独定义日期格式
protected $casts = [
'birthday' => 'date:Y-m-d',
'joined_at' => 'datetime:Y-m-d H:00',
];
Eloquent 关联
One to one
class Contact extends Model
{
public function phoneNumber()
{
// 默认 contact_id 可以省略,
// 等价于 return $this->hasOne(PhoneNumber::class, 'contact_id');
return $this->hasOne(PhoneNumber::class);
}
// ...
使用
$contact = Contact::first();
$contactPhone = $contact->phoneNumber;
从 phoneNumber 获取 contact
class PhoneNumber extends Model
{
public function contact()
{
return $this->belongsTo(Contact::class);
}
// ...
使用
$contact = $phoneNumber->contact;
tip: hasOne 和 belongsTo 主要区别在于关系的哪一侧持有关系的外键,以上例为主, phoneNumber 持有 Contact 的外键 contact_id 故,Contact hasOne phoneNumber && phoneNumber belongsTo Contact.
创建关联
实质是传递一个实例给 save() 或 传递一个实例数组给 saveMany(), 当然你也可以传递参数给 create() 或 createMany(), 他们将创造新的实例。
$contact = Contact::first();
$phoneNumber = new PhoneNumber;
$phoneNumber->number = 8008675309;
$contact->phoneNumbers()->save($phoneNumber);
// or
$contact->phoneNumbers()->saveMany([
PhoneNumber::find(1),
PhoneNumber::find(2),
]);
// or
$contact->phoneNumbers()->create([
'number' => '+13138675309',
]);
// or
$contact->phoneNumbers()->createMany([
['number' => '+13138675309'],
['number' => '+15556060842'],
]);
One to many
class User extends Model
{
public function contacts()
{
return $this->hasMany(Contact::class);
}
// ...
使用
$user = User::first();
// 返回一个 对象实例的 collection
$contactList = $user->contacts;
因为是 collection 所以, 你可以像下边那样使用
$donors = $user->contacts->filter(function ($contact) {
return $contact->status == 'donor';
});
$lifetimeValue = $contact->orders->reduce(function ($carry, $order) {
return $carry + $order->amount;
}, 0);
// ...
从 contact 获取 user
class contact extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
使用
$userName = $contact->user->name;
关联和取消关联
大部分情况下,我们可以在 父级 上运行 save() 并传递参数来创建关联(就像 one to one中创建关联一样)。如:$user->contacts()->save($contact)。 但是当我们要在 子级 上创建和取消关联,请使用 associate() 和 dissociate() 。如下:
// 创建关联 User::first() 和 Contact::first()
$contact = Contact::first();
$contact->user()->associate(User::first());
$contact->save();
// 取消关联
$contact->user()->dissociate();
$contact->save();
作为查询构建器使用关联
// 返回一个 collection
$user->contact;
// 返回一个 预处理的 查询构建器, 即 如果 user 的 id = 1, 调用 contact() 后,将获得一个 "获取所有 contact 记录中 user_id=1 的记录 "的预处理查询。
$user->contact();
// 所以你可以像以下这样使用
$user->contact()->where('status', 'donor')->get();
查找有关联关系的记录
// 查询有关联记录的
$usersWithContacts = User::has('contact')->get();
// 筛选关联记录数量
$usersWithManyContacts = User::has('contact', '>=', 5)->get();
// 嵌套关联 (user 与 contact 有关联,且 contact 与 phoneNumber 有关联)
$usersWithPhoneBook = User::has('contact.phoneNumbers')->get();
// 自定义查询
// Gets all contacts with a phone number containing the string "867-5309"
$jennyIGotYourNumber = Contact::whereHas('phoneNumbers', function ($query) {
$query->where('number', 'like', '%867-5309%');
});
Has many through
例子: 每个用户(user)有很多联系人(contact), 每个联系人有很多电话号码(phoneNumber), 现在想获得用户的联系人电话列表. 这就是 Has many through 的关系.
class User extends Model{
// 默认 phone_numbers 表有一个 contact_id 的列, 并且 contacts 表 有一个 user_id 的列
public function phoneNumbers()
{
return $this->hasManyThrough(PhoneNumber::class, Contact::class);
}
// or 自定义关联列名
// contacts 表 有一个 user_id 的列, phone_numbers 表有一个 owner_id 的列
public function phoneNumbers()
{
return $this->hasManyThrough(PhoneNumber::class, Contact::class, 'user_id', 'owner_id');
}
}
$user = User::find(1);
$phoneNumbers = $user->phoneNumbers;
Has one through
和 Has many through 一样, 只不过 不是通过 多个 中间项, 访问 多个 项, 而是 通过 一个 中间项, 访问 一个 项.
例子: 用户(user)属于一个公司(company), 公司有一个电话(phoneNumber),现在通过获得公司的电话来获取用户的电话.
class User extends Model
{
public function phoneNumber()
{
return $this->hasOneThrough(PhoneNumber::class, Company::class);
}
}
Many to many
例子: 一个用户(user)有多个联系人(contact), 每个联系人与多个用户有关系.
/**
* User 模型
*/
class User extends Model
{
public function contacts()
{
return $this->belongsToMany(Contact::class);
}
}
反过来,也一样.
/**
* Contact 模型
*/
class Contact extends Model
{
public function users()
{
return $this->belongsToMany(User::class);
}
}
因为是多对多关联, 单个用户没有 contact_id 列, 单个联系人也没有 user_id 列, 所以,我们需要一个中间表来关联. 通常这个中间表的命名规范是, 表1的名字_表2的名字, 表1表2谁在前, 取决于模型名字的字母顺序.
本例中, 我们的中间表, 应该是 contact_user , 因为 c<u , 所以 contacts 表在前, users 在后. 当然, 你也可以自定义中间表的名字, 记得, 写关联时, 要把表名作为第二个参数传递给 belongsToMany(). 另外, 中间表需要 contact_id 和 user_id 两列.
从中间表获取数据
中间表的数据越少越好, 但是有时我们还要往中间表存储一些数据, 例如 created_at 存储关系创建的时间等.
- 向中间表中加数据
public function contacts()
{
// withTimeStamps() 增加 created_at 和 updated_at 字段
// withPivot() 传入多个参数或数组, 表示新增的字段名
return $this->belongsToMany(Contact::class)
->withTimestamps()
->withPivot('status');
}
- 从中间表获得数据
当你通过关联关系获取一个模型实例时, 它将有一个 pivot 属性, 来存储中间表的相关信息, 你可以通过以下方式访问它们.
$user = User::first();
//
$user->contacts->each(function ($contact) {
echo sprintf(
'Contact associated with this user at: %s',
$contact->pivot->created_at
);
});
- 自定义
pivot属性的名字
/**
* User 模型
*/
class User extends Model
{
public function contacts()
{
return $this->belongsToMany(Contact::class)
->withTimestamps()
->withPivot(['status', 'is_first'])
->as('client');
}
}
// 使用
$user = User::find(1);
$user->contacts->each(function($contact){
echo sprintf('Client is first comming? %s', $contact->client->is_first);
});
绑定与解绑关系
- 因为中间表有自己的属性, 所以绑定多对多关系时, 你要设置这些属性, 如下
$user = User::first();
$contact = Contact::first();
$user->contacts()->save($contact, ['status' => 'donor']);
- 你还可以使用
attach()和detach()来绑定和解绑关系, 参数不需要传递实例, 只需要传递id或id的数组.
$user = User::first();
// 绑定
$user->contacts()->attach(1);
$user->contacts()->attach(2, ['status' => 'donor']);
$user->contacts()->attach([1, 2, 3]);
$user->contacts()->attach([
1 => ['status' => 'donor'],
2,
3,
]);
// 解绑
$user->contacts()->detach(1);
$user->contacts()->detach([1, 2]);
$user->contacts()->detach(); // 解绑所有,返回解绑记录数
- 把绑定的解绑, 没绑定的绑定
$user->contacts()->toggle([1, 2, 3]);
- 更新所有绑定的中间表的状态
$user->contacts()->updateExistingPivot($contactId, [
'status' => 'inactive',
]);
- 替换现有的绑定关系, 实际上解绑所有现有绑定,重新绑定
$user->contacts()->sync([1, 2, 3]);
// 等价于
$user->contacts()->detach();
$user->contacts()->attach([1, 2, 3]);
// 你还可以附加参数
$user->contacts()->sync([
1 => ['status' => 'donor'],
2,
3,
]);
多态
多态意味着, 我们有多个模型以一种相同的关联关系关联到同一模型. 类如, 我们(Users)可以收藏 Contacts 也可以收藏 Events.
stars 收藏表 有 id , starrable_id , 和 starrable_type 这些列 . starrable_type 用于区分 Contacts or Events , starrable_id 是 Contacts 或 Events 的 id.
构建多态
class Star extends Model
{
public function starrable()
{
return $this->morphTo();
}
}
class Contact extends Model
{
public function stars()
{
return $this->morphMany(Star::class, 'starrable');
}
}
class Events extends Model
{
public function stars()
{
return $this->morphMany(Star::class, 'starrable');
}
}
收藏一个联系人
$contact = Contact::first();
$contact->stars()->create();
获取该联系人的所有收藏记录
$contact = Contact::first();
$contact->stars->each(function ($star) {
// Do stuff
});
获取收藏记录 收藏的内容是联系人还是事件
$stars = Star::all();
$stars->each(function ($star) {
var_dump($star->starrable); // An instance of Contact or Event
});
怎么知道谁收藏了呢? 我们要加 user_id 到 stars 表里. users 和 stars 是 一对多关系 (即, 一条收藏记录属于一个用户, 一个用户可以有多条收藏记录).
// 增加 一对多 关系
class Star extends Model
{
public function starrable()
{
return $this->morphsTo;
}
public function user()
{
return $this->belongsTo(User::class);
}
}
class User extends Model
{
public function stars()
{
return $this->hasMany(Star::class);
}
}
那么新增收藏, 如下:
// 指定收藏用户
$user = User::first();
$event = Event::first();
$event->stars()->create(['user_id' => $user->id]);
多对多的多态
这是一种最复杂, 且不常见的多态形式, 多对多的多态.
最常见的例子是标签系统, 一个标签( tags )可以标记多个项目( contacts 或 events ), 每个项目又可以又多个标签.
先设计数据库, contacts 表, events 表, 和 一个 tags 表, 都包含一个 id 和其余你需要的列, 自不用说, 另外我们还需要一个 taggables 表, 包含 tag_id , taggable_id , 和 taggable_type 列. 每条 taggables 记录都将关联一个标签和一个可被加标签的项目.
定义关联模型
class Contact extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
class Event extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
class Tag extends Model
{
public function contacts()
{
return $this->morphedByMany(Contact::class, 'taggable');
}
public function events()
{
return $this->morphedByMany(Event::class, 'taggable');
}
}
添加标签
$tag = Tag::firstOrCreate(['name' => 'likes-cheese']);
$contact = Contact::first();
$contact->tags()->attach($tag->id);
两边都可以获取关联的信息
$contact = Contact::first();
$contact->tags->each(function ($tag) {
// Do stuff
});
$tag = Tag::first();
$tag->contacts->each(function ($contact) {
// Do stuff
});
$tag->events->each(function ($event) {
// Do stuff
});
子记录变动 更新父记录时间
当有 belongsTo 或 belongsToMany 关系时, 当子记录发生变动时,有时我们需要更新父记录的时间戳(updated_at).
如下设置:
class phoneNumber extends Model
{
// 传入 关联的方法名
protected $touches = ['contact'];
public function contact()
{
return $this->belongsTo(Contact::class);
}
}
Eager loading 解决 N+1 问题
默认,在加载模型关系时, Eloquent 是懒加载, 这意味着你首次访问模型实例时, 其关联模型不会被加载,知道你在实例上访问它们时,它们才会被加载,所以是 “懒惰” 的。
这就造成了 N+1 问题。
$contacts = Contact::all();
foreach ($contacts as $contact) {
foreach ($contact->phone_numbers as $phone_number) {
echo $phone_number->number;
}
}
如果你使用模型实例时, 你知道你要使用到关联模型,你可以使用 Eager Loading , 如下:
// 传递关联关系的方法名给 with 方法
$contacts = Contact::with('phoneNumbers')->all();
当使用 Eager Loading 时, 意味着,不是请求一次关联关系查询一次,而是先获取初始项(所有联系人),再获取所有初始项的关联关系(联系人电话),即简化N+1问题,为 1+1,大大提升效率。
- 可以 加载多个关系
$contacts = Contact::with('phoneNumbers', 'addresses')->get();
- 可以 加载关系的关系
$authors = Author::with('posts.comments')->get();
- 使用 eager loads 但不全部使用, 可以传一个闭包给 with()
$contacts = Contact::with(['addresses' => function ($query) {
$query->where('mailable', true);
}])->get();
- Lazy eager loading
有时,你并不知道你需要 eager loading , 直到你获取父模型后,某些条件下才需要
$contacts = Contact::all();
if ($someCondition) {
$contacts->load('phoneNumbers');
}
- 你也可以只在关联模型没被加载时加载
$contacts = Contact::all();
if ($showPhoneNumbers) {
$contacts->loadMissing('phoneNumbers');
}
- 仅仅 eager loading 关系数量
$authors = Author::withCount('posts')->get();
// Adds a "posts_count" integer to each Author with a count of that
// author's related posts
