anderson-dw/laravel-eloquent-join

此包引入了Eloquent模型和关系的连接魔术。

4.1.3 2020-09-09 14:33 UTC

README

FORK以支持Laravel 10

Tests status

Laravel Eloquent Join

此包引入了Eloquent模型和关系的连接魔术。

简介

Eloquent是一个功能强大的ORM,但其连接功能非常有限。

第一个Eloquent问题(排序)

使用Laravel,您无法在不手动连接相关表的情况下对关系字段进行排序,这非常不自然。让我给你几个原因。如果您有一个包含posts和相关的categories的表,您的代码可能看起来像这样:

$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();

1. 第一个问题是你需要担心选择。

    ->select('posts.*')

原因:没有从categoryselect id,它可能被选中并注入到Post模型中。

2. 第二个问题是您需要担心groupBy

->groupBy('posts.id');

原因:如果关系是HasOne并且帖子有多个类别,查询将返回多个类别的行。

3. 第三个问题是您需要将所有其他where子句从

    ->where('date', $date)

改为

    ->where('posts.date', $date)

原因:一个postcategory可以有一个“date”属性,在这种情况下,如果没有选择具有表“模糊列”的属性,将会抛出错误。

4. 第四个问题是您正在使用表名(不是模型),这也不好,也不自然。

    ->where('posts.date', $date)

5. 第五个问题是您需要担心连接表的软删除。如果category使用了SoftDeletes特性,您必须添加

    ->where('categories.deleted_at', '=', null)

此包将为您处理上述所有问题。与排序不同,您可以在不连接相关表的情况下对关系字段进行过滤,但此包将提供更简单的方法来实现这一点。

第二个Eloquent问题(子查询)

使用Laravel,您可以在关系属性上执行where操作,但Laravel会生成比连接更慢的子查询。使用此包,您将以优雅的方式执行具有连接的关系的where操作。

要求

包还针对SQLite、MySQL和PostgreSQL进行了测试

安装和设置

1. 使用Composer安装包

composer require fico7489/laravel-eloquent-join

使用此语句,Composer将安装与您当前Laravel版本兼容的最高版本包。

2. 在您的基模型或特定模型中使用Fico7489\Laravel\EloquentJoin\Traits\EloquentJoinTrait特性。

...
use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model;

abstract class BaseModel extends Model
{
    use EloquentJoin;
...

3. 重要

对于MySQL,请确保strict配置设置为false

config/database.php

        'mysql' => [
			...
            'strict'    => false,
			...

就是这样,您可以开始了。

选项

选项可以在模型中设置

class Seller extends BaseModel
{
    protected $useTableAlias = false;
    protected $appendRelationsCount = false;
    protected $leftJoin = false;
    protected $aggregateMethod = 'MAX';

或查询上

    Order::setUseTableAlias(true)->get();
    Order::setAppendRelationsCount(true)->get();
    Order::setLeftJoin(true)->get();
    Order::setAggregateMethod(true)->get();

useTableAlias

是否应该为连接表使用别名(默认 = false)

使用true,查询将如下所示:

select "sellers".* from "sellers" 
    left join "locations" as "5b5c093d2e00f" 
	...

使用false,查询将如下所示:

select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...

别名是随机生成的字符串。

appendRelationsCount

是否应该自动将关系计数字段附加到结果(默认 = false)

使用true,查询将如下所示:

select "sellers".*, count(locations.id) AS locations_count
	from "sellers" 
	left join "locations" as "5b5c093d2e00f" 
	...

每个relation都用下划线连接,并在末尾添加前缀_count。例如,对于

->joinRelations('seller.locations')

字段将是seller_locations_count

leftJoin

是否应使用inner joinleft join(默认 = true)

select "sellers".* 
	from "sellers" 
	inner join "locations"                    
	...

vs

select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...

aggregateMethod

用于排序的聚合方法(默认 = 'MAX')。

当对连接表执行连接操作时,我们必须在排序字段上应用聚合函数,以便执行分组子句并防止结果重复。

select "sellers".*, MAX("locations" ."number") AS sort
	from "sellers" 
	left join "locations" 
	group by "locations" ."id"
	order by sort
	...

选项包括: 求和平均值最大值最小值计数

用法

当前可用于连接查询的关系

  • 属于
  • 有一个
  • 有多个

针对 BelongsTo 和 HasOne 关系的 Eloquent 生成器的新子句

joinRelations($relations, $leftJoin = null)

  • $relations 要连接的关系
  • $leftJoin 使用 左连接内连接,默认为 左连接

orderByJoin($column, $direction = 'asc', $aggregateMethod = null)

  • $column$direction 参数与默认 Eloquent orderBy() 中的参数相同
  • $aggregateMethod 参数定义要使用的聚合方法(求和平均值最大值最小值计数),默认为 最大值

whereJoin($column, $operator, $value, $boolean = 'and')

  • 参数与默认 Eloquent where() 中的参数相同

orWhereJoin($column, $operator, $value)

  • 参数与默认 Eloquent orWhere() 中的参数相同

whereInJoin($column, $values, $boolean = 'and', $not = false)

  • 参数与默认 Eloquent whereIn() 中的参数相同

whereNotInJoin($column, $values, $boolean = 'and')

  • 参数与默认 Eloquent whereNotIn() 中的参数相同

orWhereInJoin($column, $values)

  • 参数与默认 Eloquent orWhereIn() 中的参数相同

orWhereNotInJoin($column, $values)

  • 参数与默认 Eloquent orWhereNotIn() 中的参数相同

在 BelongsTo、HasOne 和 HasMany 关系上允许使用的子句,在这些关系上可以使用连接子句进行查询

  • 您想要用于连接查询的关系只能有这些子句: whereorWherewithTrashedonlyTrashedwithoutTrashed
  • whereorWhere 子句只能有这些变体 ** ->where($column, $operator, $value) ** ->where([$column => $value])
  • 不允许使用闭包。
  • 不允许使用其他子句,如 whereHasorderBy 等。
  • 您可以在关系上添加不允许的子句并以正常的 Eloquent 方式使用它们,但在这些情况下,您不能使用这些关系进行连接查询。

允许的关系

public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed();
}

不允许的关系

public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed()
        ->whereHas('state', function($query){return $query;}
        ->orderBy('name')
        ->where(function($query){
            return $query->where('is_primary', '=', 1);
        });
}

不允许第二个关系的原因是,这个包应该在连接子句上应用所有这些子句,Eloquent 使用子查询将所有这些子句隔离起来,而不是在连接子句上,这样做更简单。

您可能会觉得规则和限制太多,但实际上并不是这样。请放心,如果您创建了不允许的查询,将会抛出适当的异常,并且您会知道发生了什么。

其他

  • 如果模型使用了 SoftDelete 特性,则将自动应用 where deleted_at != null
  • 您可以无限次地组合新子句
  • 如果您在相同的关系上组合子句更多次,则包将只连接相关表一次
Seller::whereJoin('city.title', '=', 'test')
    ->orWhereJoin('city.title', '=', 'test2');
  • 您可以在闭包内部调用新子句
Seller::where(function ($query) {
    $query
        ->whereJoin('city.title', '=', 'test')
        ->orWhereJoin('city.title', '=', 'test2');
});
  • 您可以将连接子句(例如,whereJoin())与 Eloquent 子句(例如,orderBy())组合
Seller::whereJoin('title', '=', 'test')
    ->whereJoin('city.title', '=', 'test')
    ->orderByJoin('city.title')
    ->get();

查看真实示例上的操作

数据库模式

Database schema

模型

class Seller extends BaseModel
{
    public function locations()
    {
        return $this->hasMany(Location::class);
    }
    
    public function locationPrimary()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1);
    }

    public function city()
    {
        return $this->belongsTo(City::class);
    }
class Location extends BaseModel
{
    public function locationAddressPrimary()
    {
        return $this->hasOne(LocationAddress::class)
            ->where('is_primary', '=', 1);
    }
    
class City extends BaseModel
{
    public function state()
    {
        return $this->belongsTo(State::class);
    }
}

连接

连接 BelongsTo

Seller::joinRelations('city')

连接 HasOne

Seller::joinRelations('locationPrimary')

连接 HasMany

Seller::joinRelations('locations')

连接(混合左连接)

Seller::joinRelations('city.state')

连接(混合左连接)

Seller::joinRelations('city', true)->joinRelations('city.state', false)

连接(多个关系)

Seller::join(['city.state', 'locations'])

排序

排序 BelongsTo

Seller::orderByJoin('city.title')

订单一对一

卖家::按连接排序('locationPrimary.address')

订单多对多

卖家::按连接排序('locations.title')

订单混合

卖家::按连接排序('city.state.title')

排序(使用聚合函数的特殊情况)

按关系数量排序

卖家::按连接排序('locations.id', 'asc', 'COUNT')

按关系字段排序 SUM

卖家::按连接排序('locations.is_primary', 'asc', 'SUM')

按关系字段排序 AVG

卖家::按连接排序('locations.is_primary', 'asc', 'AVG')

按关系字段排序 MAX

卖家::按连接排序('locations.is_primary', 'asc', 'MAX')

按关系字段排序 MIN

卖家::按连接排序('locations.is_primary', 'asc', 'MIN')

过滤(where 或 orWhere)

过滤属于

卖家::whereJoin('city.title', '=', 'test')

过滤一对一

卖家::whereJoin('locationPrimary.address', '=', 'test')

过滤多对多

卖家::whereJoin('locations.title', '=', 'test')

过滤混合

卖家::whereJoin('city.state.title', '=', 'test')

关系计数

$sellers = Seller::setAppendRelationsCount(true)->join('locations', '=', 'test')
    ->get();
    
foreach ($sellers as $seller){
    echo 'Number of location = ' . $seller->locations_count;
}

过滤(混合左连接)

Seller::joinRelations('city', true)
    ->joinRelations('city.state', false)
    ->whereJoin('city.id', '=', 1)
    ->orWhereJoin('city.state.id', '=', 1)

生成的查询

查询

Order::whereJoin('seller.id', '=', 1)->get();

Sql

select "orders".* 
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "sellers"."id" = ? 
    and "orders"."deleted_at" is null 
    group by "orders"."id"

查询

Order::orderByJoin('seller.id', '=', 1)->get();

Sql

select "orders".*, MAX(sellers.id) as sort
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "orders"."deleted_at" is null 
    group by "orders"."id"
    order by sort asc

包的优雅性

让我们看看文档中的第一个示例现在看起来像什么。这段代码

$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('date', $date)
}

$posts = $posts->get();

现在

$posts = Post::orderByJoin('category.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();

两个片段做同样的事情。

测试

此包经过良好的测试覆盖。如果您想运行测试,只需运行 composer update 然后使用 "vendor/bin/phpunit" 运行测试

贡献

请随时为新问题创建

  • 错误
  • 通知
  • 请求新功能
  • 问题
  • 澄清
  • 等...

许可

MIT

自由软件,太棒了!