通过模型关系列对分页器对象进行排序

Posted

技术标签:

【中文标题】通过模型关系列对分页器对象进行排序【英文标题】:Sorting of paginator object through a model relation column 【发布时间】:2021-11-15 03:02:51 【问题描述】:

我有三个表:productsproduct_inventoriesproduct_inventory_details。各型号的ORM如下图,

产品型号

class Product extends Model
    ...

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        ...,
        'title', 
        'selected_inventory_id', 
        ...
    ];

    /**
     * Get the inventories those belongs to this model.
     */
    public function inventory()
        return $this->hasMany('App\Models\ProductInventory');
    

    /**
     * Get the selected product_inventory_detail that owns this model.
     */
    public function selected()
        return $this->hasOne('App\Models\ProductInventoryDetail', 'id', 'selected_inventory_id');
    
    
    ...

产品库存模型

class ProductInventory extends Model
    ...
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'product_id', 
        ...
    ];


    /**
     * Get the inventory_details those belongs to this model.
     */
    public function items()
        return $this->hasMany('App\Models\ProductInventoryDetail');
    

    ...

ProductInventoryDe​​tail 模型

class ProductInventoryDetail extends Model
    ...
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'product_inventory_id',  
        'price', 
        ...
    ];

我通过排序方式下拉列表和每页显示下拉列表的用户输入对结果进行排序和限制。当按 Alphabetical: High to Low 选项排序时,我正在运行 query builder 方法来排序结果:

$products = $products->orderBy($sort['column'], $sort['order'])->paginate($limit);

现在按价格排序,我无法运行查询构建器方法orderBy(),因为我没有使用连接并通过关系属性获取数据。相反,我使用 Laravel 的 collection 方法对其进行排序:

$products = $products->paginate($limit);

$products = $products->sortBy(function($prod, $key)
    return $prod->selected->price;
);

如果我不使用分页方法,上述块工作正常。但是,我还需要使用pagination,因为用户还可以限制每页的结果。我还使用Paginator 对象的方法将一些参数附加到每个页面 URL:

$products->appends($paramsArr);

由于运行sortBy() 方法返回一个集合而不是Paginator 对象,它给了我undefined method 异常。

我的问题

如何在当前场景中按价格对结果集进行排序,而无需实现联接?有没有办法做到这一点??

【问题讨论】:

【参考方案1】:

我会使用SpatieQueryBuilder 包。创建可排序和可过滤的网格表将使您的生活更轻松。你可以这样使用那个包:

$query = Product::with(['inventory', 'selected']);
$products = \Spatie\QueryBuilder\QueryBuilder::for($query)
    ->allowedFilters([
          'name' => 'name', // name column in the products DB table.
          'selected.price' => 'product_inventory_details.column_price', // price column in the product_inventory_details DB table.
     ])
     ->defaultSort('name')
     ->allowedSorts([
          'name',
          \Spatie\QueryBuilder\AllowedSort::custom('selected.price', new SortSelectedPrice())
     ])
     ->paginate(20)
     ->appends(request()->query());

外部自定义排序类如下所示:

 class SortSelectedPrice implements \Spatie\QueryBuilder\Sorts\Sort
 
      public function __invoke(Builder $query, bool $descending, string $property)
      
           $direction = $descending ? 'DESC' : 'ASC';
           $query->leftJoin('product_inventory_details', 'products.id', '=', 'product_inventory_details.product_id');
           $query->orderBy('product_inventory_details.column_price', direction);
      
  

确保您的 URL 包含这样的查询字符串,以便将名称从 A 排序到 Z,并将价格从 1xxxxxxxx 排序到 0:

domain.com/products?sort=name,-selected.price

我使用 composer 安装了 Spatie 包。不要忘记这样做。

【讨论】:

谢谢 O Conner,我一定会检查并试一试。【参考方案2】:

我找到了一种无需实现连接即可处理它的方法。我添加了一个新变量并将 Paginator 对象的 items() 方法结果集存储到其中。

$products = $products->paginate($limit);

$productItems = $products->items(); // returns the items array from paginator

并对特定变量进行排序,而不是对整个分页器对象进行排序。这样,$products 变量中的链接和 URL 就不会被触及和修改,并且产品的数据位于单独的变量中。

if($sort['column'] == 'price')
    if($sort['order'] == 'DESC')
        $productItems = $products->sortByDesc(function($prod, $key)
            return $prod->selected->price;
        );
     else
        $productItems = $products->sortBy(function($prod, $key)
            return $prod->selected->price;
        );
    

我还必须将渲染变量从 $products 更改为 $productItems,并从旧的 $products 变量访问分页链接。

@forelse ($productItems as $product)
    @include('site.components.product-grid')
@empty
    <div class="col text-center">...</div>
@endforelse

...

 $products->links() 

如果有更好的方法,我将其发布在这里,以供社区受益/讨论/批评。

【讨论】:

以上是关于通过模型关系列对分页器对象进行排序的主要内容,如果未能解决你的问题,请参考以下文章

排序在 Knp 分页器中不起作用

yii2 一对多关系的对分页造成的影响

Laravel orderBy 与分页和关系

Django 批量插入数据自定义分页器多表关系的建立及Form组件(待更新。。。)

如何使用 MongoDB 中的自定义字段对分页进行索引和排序,例如:名称而不是 id

Laravel Eloquent按关系表列排序