Laravel模型关联高级教程

Laravel模型关联高级教程

在每个开发者的生活中,往往会有一个点,你必须与数据库进行互动。在这里,Eloquent,Laravel的对象关联映射器(ORM),使你与数据库表的互动过程变得直观和自然。

作为一个专业人士,你应该认识和理解六种关键的关联类型,这是至关重要的,我们将通过和审查。

  1. 什么是Eloquent中的关联?
  2. 一对一关联
  3. 一对多关联
  4. 一对多检索关联
  5. 远程一对一和远程一对多关联
  6. 多对多关联
  7. 多态关联
  8. 多态一对一关联
  9. 多态一对多关联
  10. 多态一对多检索关联
  11. 多态多对多关联
  12. 优化Eloquent的速度

什么是Eloquent中的关联?

在关联型数据库中处理表时,我们可以将关联描述为表之间的连接。这可以帮助你毫不费力地组织和构建数据,使数据的可读性和处理更加出色。在实践中,有三种类型的数据库关联:

  • 一对一 – 一个表中的一条记录与另一个表中的一条,而且只有一条相关联。例如,一个人和一个社会安全号码。
  • 一对多 – 一个记录与另一个表中的多个记录相关联。例如,一个作家和他们的博客。
  • 多对多 – 一个表中的多个记录与另一个表中的多个记录相关联。例如, 学生和他们所注册的课程。

Laravel在Eloquent中使用面向对象的语法,使得互动和管理数据库关联变得天衣无缝。

伴随着这些定义,Laravel引入了更多的关联,即:

  • 远程一对
  • 多态关联
  • 多态多对多

以一个商店为例,它的库存包含了各种各样的文章,每个都有自己的类别。因此,从商业角度来看,将数据库分割成多个表是有意义的。这也带来了自身的问题,因为你并不想查询每一个表。

我们可以很容易地在Laravel中创建一个简单的一对多的关联来帮助我们,比如当我们需要查询产品的时候,我们可以通过使用产品模型来完成。

有三个表和一个代表多态关联的联合表的数据库模式

有三个表和一个代表多态关联的联合表的数据库模式

一对一关联

作为Laravel提供的第一个基本关联,他们将两个表联系在一起,这样第一张表的一条记录就只和另一张表的一条记录相关联。

为了看到这一点, 我们必须创建两个有自己迁移的模型:

php artisan make:model Tenant 
Php artisan make:model Rent

在这一点上,我们有两个模型,一个是租户,另一个是他们的租金。

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
/**
* Get the rent of a Tenant
*/
public function rent() 
{
return $this->hasOne(Rent::class);
}
}

因为eloquent根据父模型的名称(本例中是Tenant)来确定外键关联,Rent模型假定存在一个tenant_id外键。

我们可以用hasOne方法的一个额外参数轻松地覆盖它:

return $this- >hasOne(Rent::class, "custom_key");

Eloquent还假设定义的外键和父记录(租户模型)的主键之间存在匹配。默认情况下,它将寻求将tenant_id与租户记录的id键相匹配。我们可以用hasOne方法中的第三个参数来覆盖这一点,这样它就可以匹配另一个键:

return $this->hasOne(Rent::class, "custom_key", "other_key");

现在我们已经定义了模型之间的一对一关联,我们可以很容易地使用它,像这样:

$rent = Tenant::find(10)->rent;

通过这行代码,我们得到了租户的租金,如果它存在的话,ID为10。

一对多关联

像前面的关联一样,这将定义一个单亲模型和多个子模型之间的关联。我们的租户不太可能只有一张租金账单,因为它是一个经常性的付款,因此,他将有多次付款。

在这种情况下,我们之前的关联有缺陷,我们可以修复它们:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tenant extends Model
{
/**
* Get the rents of a Tenant
*/
public function rent() 
{
return $this->hasMany(Rent::class);
}
}

在我们调用方法获得租金之前,需要知道的一件好事是,关联可以作为查询构建器,所以我们可以进一步添加约束条件(如日期之间的租金,最低付款额等),并将它们连接起来,以获得我们想要的结果:

$rents = Tenant::find(10)->rent()->where('payment', '>', 500)->first();

和之前的关联一样,我们可以通过传递额外的参数来覆盖外键和本地键:

return $this->hasMany(Rent::class, "foreign_key");
return $this->hasMany(Rent::class, "foreign_key", "local_key");

现在我们有了一个租户的所有租金,但当我们知道了租金并想弄清楚它属于谁时,我们该怎么做?我们可以利用 belongsTo 属性:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rent extends Model
{
/**
* Return the tenant for the rent
*/
public function tenant() 
{
return $this->belongsTo(Tenant::class);
}
}

而现在我们可以很容易地得到租户:

$tenant = Rent::find(1)->tenant;

对于 belongsTo 方法,我们也可以像之前那样覆盖外键和本地键。

一对多检索关联

由于我们的租户模型可以与许多租金模型相关联,我们想轻松地检索关联中最新或最旧的相关模型。

一个方便的方法是结合hasOneofMany方法:

public function latestRent() {
return $this->hasOne(Rent::class)->latestOfMany();
}
public function oldestRent() {
return $this->hasOne(Rent::class)->oldestOfMany();
}

默认情况下,我们是根据主键来获取数据的,这是可排序的,但我们可以为ofMany方法创建我们自己的过滤器:

return $this->hasOne(Rent::class)->ofMany('price', 'min');

远程一对一和远程一对多关联

-Through方法表明我们的模型将不得不通过另一个其他模型来建立与所需模型的关联。例如,我们可以将租金与房东联系起来,但租金必须首先通过租户才能到达房东那里。

这方面所需的表的键看起来是这样的:

rent
id - integer
name - string
value - double
tenants
id - integer
name - string
rent_id - integer
landlord
id - integer
name - string
tenant_id - integer

在想象出我们的表格的样子后,我们可以制作模型:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rent extends Model
{
/**
* Return the rents' landlord
*/
public function rentLandlord() 
{
return $this->hasOneThrough(Landlord::class, Tenant::class);
}
}

hasOneThrough方法的第一个参数是你要访问的模型,第二个参数是你要经过的模型。

就像以前一样,你可以覆盖外键和本地键。现在我们有两个模型,我们有两个各自的模型要按这个顺序覆盖:

public function rentLandlord() 
{
return $this->hasOneThrough(
Landlord::class,
Tenant::class,
"rent_id",    // Foreign key on the tenant table
"tenant_id",  // Foreign key on the landlord table
"id",         // Local key on the tenant class
"id"          // Local key on the tenant table
);
}

同样, Laravel Eloquent中的 “Has Many Through” 关联在你想通过中间表访问远方表的记录时很有用。让我们考虑一个有三个表的例子:

  • country
  • users
  • games

每个国家有很多用户, 每个用户有很多游戏。我们想通过用户表来检索属于某个国家的所有游戏。

你可以这样定义这些表:

country
id - integer
name - string
user
id - integer
country_id - integer
name - string
games
id - integer
user_id - integer
title - string

现在你应该为每一个表定义Eloquent模型:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
protected $fillable = ['name'];
public function users()
{
return $this->hasMany(User::class);
}
public function games()
{
return $this->hasManyThrough(Games::class, User::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = [article_id, 'name'];
public function country()
{
return $this->belongsTo(Country::class);
}
public function posts()
{
return $this->hasMany(Post::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Game extends Model
{
protected $fillable = ['user_id', 'title'];
public function user()
{
return $this->belongsTo(User::class);
}
}

现在我们可以调用国家模型的games()方法来获得所有的游戏,因为我们通过用户模型在国家和游戏之间建立了 “Has Many Through” 关联。

<?php
$country = Country::find(159);
// Retrieve all games for the country
$games = $country->games;

多对多关联

多对多的关联更为复杂。一个很好的例子是一个拥有多个角色的雇员。一个角色也可以分配给多个雇员。这就是多对多关联的基础。

为此,我们必须有employeesroles, 和 role_employees表。

我们的数据库表结构将看起来像这样:

employees
id - integer
name - string
roles 
id - integer
name - string
role_employees
user_id - integer
role_id - integer

知道了关联表的结构,我们可以很容易地将我们的Employee model定义为属于belongToMany Role模型。

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
public function roles() 
{
return $this- >belongsToMany(Role::class);
}
}

一旦我们定义了这个,我们就可以访问一个雇员的所有角色,甚至可以过滤它们:

$employee = Employee::find(1);
$employee->roles->forEach(function($role) { // });
// OR 
$employee = Employee::find(1)->roles()->orderBy('name')->where('name', 'admin')->get();

像所有其他方法一样,我们可以覆盖 belongsToMany 方法的外键和本地键。

要定义 belongsToMany 的逆向关联,我们只需使用同样的方法,但现在是在子方法上,以父方法作为参数。

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function employees() 
{
return $this->belongsToMany(Employee::class);
}
}

中间表的用途

我们可能已经注意到,当我们使用多对多的关联时,我们总是应该有一个中间表。在这种情况下,我们使用的是role_employees表。

默认情况下,我们的透视表将只包含id属性。如果我们想要其他属性,我们必须像这样指定它们:

return $this->belongsToMany(Employee::class)->withPivot("active", "created_at");

如果我们想缩短时间戳的pivots,我们可以这样做:

return $this->belongsToMany(Employee::class)->withTimestamps();

要知道的一个诀窍是,我们可以将 “pivot” 的名称自定义为任何更适合我们应用的东西:

return $this->belongsToMany(Employee::class)->as('subscription')->withPivot("active", "created_by");

过滤一个雄辩的查询结果是任何想要加强他们的游戏和优化他们的Laravel应用程序的开发人员必须知道的。

因此,Laravel提供了一个奇妙的功能,即pivots,可以用来过滤我们想要收集的数据。所以,我们可以用一些有用的方法来过滤数据,比如wherePivot, wherePivotIn, wherePivotNotIn, wherePivotBetween, wherePivotNotBetween, wherePivotNull, wherePivotNull,我们可以在定义表之间的关联时使用它们,而不是使用其他的功能,比如数据库事务来获取我们的数据块!

return $this->belongsToMany(Employee::class)->wherePivot('promoted', 1);
return $this->belongsToMany(Employee::class)->wherePivotIn('level', [1, 2]);
return $this->belongsToMany(Employee::class)->wherePivotNotIn('level', [2, 3]);
return $this->belongsToMany(Employee::class)->wherePivotBetween('posted_at', ['2023-01-01 00:00:00', '2023-01-02 00:00:00']);
return $this->belongsToMany(Employee::class)->wherePivotNull('expired_at');
return $this->belongsToMany(Employee::class)->wherePivotNotNull('posted_at');

最后一个令人惊奇的功能是,我们可以按pivots排序:

return $this->belongsToMany(Employee::class)
->where('promoted', true)
->orderByPivot('hired_at', 'desc');

多态关联

Polymorphic(多态)这个词来自希腊语,它的意思是 “多种形式”。就像这样,我们应用程序中的一个模型可以采取多种形式,也就是说它可以有多个关联。想象一下,我们正在建立一个有博客、视频、投票等的应用程序。一个用户可以为其中任何一个创建评论。因此,一个Comment model可能属于BlogsVideos, 和 Polls模型。

多态一对一关联

这种类型的关联类似于标准的一对一关联。唯一的区别是,子模型可以通过一个关联属于一个以上的模型类型。

以一个TenantLandlord模型为例,它可能与WaterBill模型共享一个多态关联。

表的结构可以是这样的:

tenants
id – integer
name – string
landlords
id – integer
name – string
waterbills
id – integer
amount – double
waterbillable_id
waterbillable_type

我们使用waterbillable_id来表示landlordtenant的ID,而waterbillable_type包含了父模型的类名。eloquent使用这个类型列来计算要返回的父模型。

这种关联的模型定义看起来如下:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WaterBill extends Model
{
public function billable()
{
return $this->morphTo();
}
}
class Tenant extends Model
{
public function waterBill()    
{
return $this->morphOne(WaterBill::class, 'billable');
}
}
class Landlord extends Model
{
public function waterBill()    
{
return $this->morphOne(WaterBill::class, 'billable');
}
}

一旦我们把这些都准备好了,我们就可以从房东和租户模型中获取数据:

<?php
$tenant = Tenant::find(1)->waterBill;
$landlord = Landlord::find(1)->waterBill;

多态一对多关联

这类似于普通的一对多关联,唯一的关键区别是,子模型可以属于一个以上的模型类型,使用一个关联。

在像Facebook这样的应用中,用户可以对帖子、视频、投票、直播等进行评论。通过多态的一对多,我们可以使用一个单一的comments表来存储我们所有类别的评论。我们的表的结构会是这样的:

posts 
id – integer
title – string
body – text
videos
id – integer
title – string
url – string
polls
id – integer
title – string
comments 
id – integer
body – text
commentable_id – integer
commentable_type – string

commentable_id是记录的id,而commentable_type是类的类型,所以eloquent知道要找什么。至于模型结构,它与多态的一对多非常相似:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model 
{
public function commentable()
{
return $this->morphTo();
}
}
class Poll extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Live extends Model
{
public function comments()
{
return $this->morphMany(Comments::class, 'commentable');
}
}

现在要检索一个Live的评论,我们可以简单地用id调用find方法,现在我们可以访问评论可迭代类:

<?php
use App\Models\Live;
$live = Live::find(1);
foreach ($live->comments as $comment) { }
// OR
Live::find(1)->comments()->each(function($comment) { // });
Live::find(1)->comments()->map(function($comment) { // });
Live::find(1)->comments()->filter(function($comment) { // });
// etc.

如果我们有了评论,并想知道它属于谁,我们就访问可评论方法:

<?php
use App\Models\Comment;
$comment = Comment::find(10);
$commentable = $comment->commentable;
// commentable – type of Post, Video, Poll, Live

多态一对多检索关联

在很多具有规模的应用中,我们希望有一种简单的方式来与模型和模型之间进行交互。我们可能想要一个用户的第一个或最后一个帖子,这可以通过morphOneofMany方法的组合来完成:

<?php
public function latestPost()
{
return $this->morphOne(Post::class, 'postable')->latestOfMany();
}
public function oldestPost()
{
return $this->morphOne(Post::class, 'postable')->oldestOfMany();
}

latestOfManyoldestOfMany方法是基于模型的主键来检索最新或最旧的模型,这是它可排序的条件。

在某些情况下,我们不希望按ID排序,也许我们改变了一些帖子的发布日期,我们希望它们按这个顺序排列,而不是按它们的ID。

这可以通过向ofMany方法传递2个参数来帮助实现。第一个参数是我们想要过滤的key,第二个参数是sorting method(排序方法)

<?php
public function latestPublishedPost()
{
return $this->morphOne(Post::class, "postable")->ofMany("published_at", "max");
}

考虑到这一点,我们有可能为此构建更高级的关联! 想象一下,我们有这样的场景。我们被要求生成一个当前所有帖子的列表,按照它们被发布的顺序。当我们有两个具有相同published_at值的帖子时,以及当帖子被安排在未来发布时,问题就出现了。

要做到这一点,我们可以将我们希望应用过滤器的顺序传递给ofMany方法。这样,我们按published_at排序,如果它们是相同的,我们就按id排序。其次,我们可以在ofMany方法中应用一个查询函数,以排除所有预定要发布的帖子!

<?php
public function currentPosts()
{
return $this->hasOne(Post::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}

多态多对多关联

多态的多对多比普通的多对多要稍微复杂一些。一个常见的情况是,在你的应用程序中,标签适用于更多的资产。例如,在TikTok,我们的标签可以应用于视频、短剧、故事等。

多态的多对多允许我们有一个与视频、短片和故事相关的标签表。

表的结构很简单:

videos
id – integer
description – string
stories 
id – integer
description – string
taggables 
tag_id – integer
taggable_id – integer
taggable_type – string

表准备好后,我们可以制作模型并使用morphToMany方法。这个方法接受模型类的名称和 “relationship name”:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

有了这个,我们可以很容易地定义逆向关联。我们知道,对于每个子模型,我们要调用morphedByMany方法:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
public function stories()
{
return $this->morphedByMany(Story::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
} 
}

而现在,当我们得到一个标签时,我们可以检索到与该标签相关的所有视频和故事!

<?php
use App\Model\Tag;
$tag = Tag::find(10);
$posts = $tag->stories;
$videos = $tag->stories;

优化Eloquent的速度

当使用Laravel的Eloquent ORM时,了解如何优化数据库查询并尽量减少获取数据所需的时间和内存是至关重要的。其中一个方法就是在你的应用程序中实现缓存

Laravel提供了一个灵活的缓存系统,支持各种后端,如Redis, Memcached, 和基于文件的缓存。通过缓存Eloquent查询结果, 你可以减少数据库查询的次数, 使你的应用程序更快,更有价值。

此外, 你可以使用Laravel的查询生成器来创建额外的复杂查询, 进一步优化你的应用程序的性能.

小结

总之,Eloquent关联是Laravel的一个强大的功能,允许开发人员轻松处理相关数据。从一对一到多对多的关联,Eloquent提供了一个简单而直观的语法来定义和查询这些关联.

作为一个Laravel开发者,掌握Eloquent关联可以极大地提高你的开发工作流程,使你的代码更有效率和可读性。如果你有兴趣了解更多关于Laravel的信息,有各种资源可用,包括一个关于Laravel入门的教程和一篇关于Laravel开发者工资的文章

评论留言