kirchbaum-development/eloquent-power-joins

将 Laravel 的魔法应用于连接。

3.5.8 2024-09-10 10:28 UTC

README

Eloquent Power Joins

Laravel Supported Versions run-tests MIT Licensed Latest Version on Packagist Total Downloads

您所熟知的 Laravel 魔法,现在应用于连接。

连接在许多方面非常有用。如果您在这里,您很可能会了解并使用它们。Eloquent 非常强大,但在使用连接时缺乏一点“Laravel 风格”。这个包使您的连接更加 Laravel 化,代码可读性更高,代码量更少,同时隐藏了不需要暴露的实现细节。

我们在使用连接时认为缺少了一些非常强大的 Eloquent 功能。

  • 使用关系定义来创建连接的能力;
  • 在不同的上下文中使用模型范围的能力;
  • 使用连接而不是 WHERE EXISTS 来查询关系存在的能力;
  • 根据相关表中的列或聚合轻松排序结果的能力;

您可以在这篇博客文章中阅读更多关于该包解决的问题的详细说明。

安装

您可以通过 composer 安装此包

composer require kirschbaum-development/eloquent-power-joins

对于 Laravel 版本 < 8,使用 2.* 版本

composer require kirschbaum-development/eloquent-power-joins:2.*

用法

此包提供了一些功能。

1 - 连接关系

假设您有一个具有 hasMany 关系到 Post 模型的 User 模型。如果您想连接表,您通常会写一些像这样的东西

User::select('users.*')->join('posts', 'posts.user_id', '=', 'users.id');

此包为您提供了一个新的 joinRelationship() 方法,它做的是完全相同的事情。

User::joinRelationship('posts');

两种选择产生相同的结果。在代码方面,您并没有节省那么多,但现在您正在使用 UserPost 模型之间的关系来连接表。这意味着现在您正在隐藏在幕后(实现细节)如何处理这种关系。如果关系类型发生变化,您也不需要更改代码。您现在拥有更易于阅读且更具可读性的代码。

但是,当您需要连接嵌套关系时,情况会更好。假设您还在这两个模型之间有一个 hasMany 关系,并且您需要连接这些表,您可以简单地写

User::joinRelationship('posts.comments');

好多了,不是吗?!您还可以根据需要将 leftright 连接到关系。

User::leftJoinRelationship('posts.comments');
User::rightJoinRelationship('posts.comments');

连接多态关系

让我们想象,您有一个 Image 模型,它是一个多态关系(Post -> morphMany -> Image)。除了常规连接之外,您还需要应用 where imageable_type = Post::class 条件,否则您可能会得到混乱的结果。

结果发现,如果您连接一个多态关系,Eloquent Power Joins 会自动为您应用此条件。您只需要调用相同的方法。

Post::joinRelationship('images');

您也可以连接 MorphTo 关系。

Image::joinRelationship('imageable', morphable: Post::class);

注意:查询 morph to 关系一次只支持一种可变类型。

在连接中应用条件 & 回调

现在,假设您想要对您正在创建的连接应用一个条件。您只需要将回调作为 joinRelationship 方法的第二个参数传递。

User::joinRelationship('posts', fn ($join) => $join->where('posts.approved', true))->toSql();

对于 嵌套调用,您只需要传递一个引用关系名称的数组。

User::joinRelationship('posts.comments', [
    'posts' => fn ($join) => $join->where('posts.published', true),
    'comments' => fn ($join) => $join->where('comments.approved', true),
]);

对于 属于多个 调用,您需要传递一个包含关系的数组,然后是一个包含表名称的数组。

User::joinRelationship('groups', [
    'groups' => [
        'groups' => function ($join) {
            // ...
        },
        // group_members is the intermediary table here
        'group_members' => fn ($join) => $join->where('group_members.active', true),
    ]
]);

在连接回调中使用模型范围 🤯

我们认为这是这个包最有用的功能之一。比如说,你在《Post》模型上有一个《published》作用域。

    public function scopePublished($query)
    {
        $query->where('published', true);
    }

当建立关联关系时,你可以使用被连接的模型中定义的作用域。这有多酷?

User::joinRelationship('posts', function ($join) {
    // the $join instance here can access any of the scopes defined in Post 🤯
    $join->published();
});

当在连接子句中使用模型作用域时,你不能在作用域中对《$query》参数进行类型提示。同时,请记住你在一个连接中,所以你只能使用连接支持的条件。

使用别名

有时候,你可能需要在连接中为同一个表使用表别名,因为你需要多次连接同一个表。实现这一点的其中一个选项是使用《joinRelationshipUsingAlias》方法。

Post::joinRelationshipUsingAlias('category.parent')->get();

如果你需要指定将要使用的别名名称,你可以有两种不同的方式

  1. 将字符串作为第二个参数传递(这不能用于嵌套连接)
Post::joinRelationshipUsingAlias('category', 'category_alias')->get();
  1. 在连接回调中调用《as》函数。
Post::joinRelationship('category.parent', [
    'category' => fn ($join) => $join->as('category_alias'),
    'parent' => fn ($join) => $join->as('category_parent'),
])->get()

对于《belongs to many》或《has many through》调用,你需要传递一个包含关系的数组,然后是一个包含表名称的数组。

Group::joinRelationship('posts.user', [
    'posts' => [
        'posts' => fn ($join) => $join->as('posts_alias'),
        'post_groups' => fn ($join) => $join->as('post_groups_alias'),
    ],
])->toSql();

选择表中的所有字段

当你进行连接时,使用《select * from ...》可能是危险的,因为父表和连接表之间可能存在同名字段,导致冲突。考虑到这一点,如果你在没有先选择任何特定列的情况下调用《joinRelationship》方法,Eloquent Power Joins 将自动为你包括这些列。例如,看看以下示例

User::joinRelationship('posts')->toSql();
// select users.* from users inner join posts on posts.user_id = users.id

并且,如果你指定了选择语句

User::select('users.id')->joinRelationship('posts')->toSql();
// select users.id from users inner join posts on posts.user_id = users.id

软删除

当连接任何使用《SoftDeletes》特质的模型时,以下条件将自动应用于所有你的连接。

and "users"."deleted_at" is null

如果你想包含已删除的模型,你可以在连接回调中调用《->withTrashed()》方法。

UserProfile::joinRelationship('users', fn ($join) => $join->withTrashed());

你还可以调用《onlyTrashed》模型。

UserProfile::joinRelationship('users', ($join) => $join->onlyTrashed());

在关系定义中定义的额外条件

如果你在关系定义中有额外的条件,它们将自动为你应用。

class User extends Model
{
    public function publishedPosts()
    {
        return $this->hasMany(Post::class)->published();
    }
}

如果你调用《User::joinRelationship('publishedPosts')->get()`》,它也将应用额外的已发布作用域到连接子句。它将生成类似于以下SQL的SQL语句

select users.* from users inner join posts on posts.user_id = posts.id and posts.published = 1

全局作用域

如果你的模型有全局作用域被应用,你可以在连接子句中通过调用《withGlobalScopes》方法来启用全局作用域,如下所示

UserProfile::joinRelationship('users', fn ($join) => $join->withGlobalScopes());

不过,这里有一个问题。你的全局作用域在《apply》方法的第一个参数中不能对《Eloquent.Builder》类进行类型提示,否则你将得到错误。

2 - 查询关系存在性(使用连接)

查询关系存在性是Eloquent非常强大且方便的功能。然而,它使用《where exists》语法,这并不总是最佳选择,可能也不是性能更好的选择,这取决于你有多少记录或你表的结构。

这个包实现了相同的功能,但它不是使用《where exists》语法,而是使用《joins》。以下是你可以在包中看到的方法,以及Laravel的等效方法。

请注意,尽管方法相似,但使用连接时,根据你的查询上下文,你不会总是得到相同的结果。你应该意识到使用《where exists》与使用《joins》查询数据的差异。

Laravel原生方法

User::has('posts');
User::has('posts.comments');
User::has('posts', '>', 3);
User::whereHas('posts', fn ($query) => $query->where('posts.published', true));
User::whereHas('posts.comments', ['posts' => fn ($query) => $query->where('posts.published', true));
User::doesntHave('posts');

包等效,但使用连接

User::powerJoinHas('posts');
User::powerJoinHas('posts.comments');
User::powerJoinHas('posts.comments', '>', 3);
User::powerJoinWhereHas('posts', function ($join) {
    $join->where('posts.published', true);
});
User::powerJoinDoesntHave('posts');

当使用涉及1个以上表(一对一、多对多等)关系的powerJoinWhereHas方法时,请使用数组语法传递回调。

User::powerJoinWhereHas('commentsThroughPosts', [
    'comments' => fn ($query) => $query->where('body', 'a')
])->get());

3 - 排序

您也可以使用orderByPowerJoins方法通过另一张表的列对查询结果进行排序。

User::orderByPowerJoins('profile.city');

如果您需要为排序函数传递一些原始值,可以这样做

User::orderByPowerJoins(['profile', DB::raw('concat(city, ", ", state)']);

此查询将根据user_profiles表上的city列对结果进行排序。您还可以根据聚合函数(COUNTSUMAVGMINMAX)对结果进行排序。

例如,要按发帖数最多的用户排序,您可以这样做

$users = User::orderByPowerJoinsCount('posts.id', 'desc')->get();

或者,要获取评论中平均得票数最高的帖子列表。

$posts = Post::orderByPowerJoinsAvg('comments.votes', 'desc')->get();

您还有用于SUMMINMAX的方法

Post::orderByPowerJoinsSum('comments.votes');
Post::orderByPowerJoinsMin('comments.votes');
Post::orderByPowerJoinsMax('comments.votes');

如果您想在排序中使用左连接,也可以

Post::orderByLeftPowerJoinsCount('comments.votes');
Post::orderByLeftPowerJoinsAvg('comments.votes');
Post::orderByLeftPowerJoinsSum('comments.votes');
Post::orderByLeftPowerJoinsMin('comments.votes');
Post::orderByLeftPowerJoinsMax('comments.votes');

贡献

有关详细信息,请参阅CONTRIBUTING

安全

如果您发现任何与安全相关的问题,请通过电子邮件security@kirschbaumdevelopment.com联系,而不是使用问题跟踪器。

鸣谢

赞助

本软件包的开发由Kirschbaum Development Group赞助,这是一家以解决问题、团队建设和社区为中心的开发者驱动型公司。了解更多关于我们加入我们

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件

Laravel软件包模板

此软件包是使用Laravel软件包模板生成的。