使用多个可选过滤器过滤 Eloquent 模型

36 篇文章 0 订阅

wpcmf: wpcmf cms ,内容管理系统,类似 wordpress 系统

在显示到视图时,我们经常需要过滤 eloquent 模型。如果我们有少量过滤器,这可能很好,但如果您需要添加多个过滤器,则控制器可能会变得混乱且难以阅读。

在处理可以结合使用的多个可选过滤器时尤其如此。

但是,有一些方法可以创建这些过滤器,甚至可以使它们可重复使用。在本文结束时,您将能够更好地处理项目中的复杂过滤选项。

定义问题
比方说,我有一个控制器方法,它返回我们商店中的所有产品,这可以用于 API,将其传递给刀片模板,或者无论如何,控制器可能看起来像这样:

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductsController extends Controller
{
    public function index()
    {
        return Product::all();
    }
}


然而,这根本不现实。通常情况下,我们需要添加一些过滤,例如,假设我们只想获取给定类别中的产品,我们可以通过在查询字符串中发送类别 slug 然后使用 slug 进行过滤来做到这一点。所以我们的新控制器看起来像这样:

class ProductsController extends Controller
{
    public function index()
    {
        if ($request->filled('category')) {
            $categorySlug = $request->category;

            return Product::whereHas('category', function ($query) use ($categorySlug) {
                $query->where('slug', $categorySlug);
            });
        }

        return Product::all();
    }
}


如果我们只需要一个过滤器,这很好。但是,看看如果我们只引入另外两个过滤选项会发生什么:

class ProductsController extends Controller
{
    public function index(Request $request)
    {
        $query = Product::query();

        if ($request->filled('price')) {
            list($min, $max) = explode(",", $request->price);

            $query->where('price', '>=', $min)
                  ->where('price', '<=', $max);
        }

    
        if ($request->filled('category')) {
            $categorySlug = $request->category;

            $query->whereHas('category', function ($query) use ($categorySlug) {
                $query->where('slug', $categorySlug);
            });
        }

        if ($request->filled('brand')) {
            $brandSlug = $request->brand;

            $query->whereHas('brand', function ($query) use ($brandSlug) {
                $query->where('slug', $brandSlug);
            });
        }


        return $query->get();
    }
}


我们又推出了两种过滤器,一种用于品牌 slug,一种用于价格范围。在我看来,这太忙了,很难跟踪这里发生的事情,如果我们需要添加更多过滤选项,情况会很快变得更糟。

例如,当您在 eBay 上搜索产品时,您通常会获得十多个可选过滤器。我们需要寻找另一种方法来做到这一点。

想像
如果不是在我们的控制器中包含所有这些过滤器,我们可以做这样的事情怎么办:

class ProductsController extends Controller
{
    public function index(ProductFilters $filters)
    {
        return Product::filter($filters)->get();
    }
}


在这里,我们收到一个ProductFilters可能包含所有过滤器的类,然后我们可以将它们应用于名为 的查询范围filter。这使得我们的控制器非常纤薄,很容易猜到它发生了什么。我们正在筛选产品,如果我们需要了解更多细节,我们可以查看ProductFilters类。

实施新方法
首先,让我们将作用域添加到我们的模型中:

class Product extends Model
{
    use HasFactory;

    public function category() 
    {
        return $this->belongsTo(Category::class);
    }

    public function brand() 
    {
        return $this->belongsTo(Brand::class);
    }
    
    // This is the scope we added
    public function scopeFilter($query, $filters)
    {
        return $filters->apply($query);
    }
}


在这个范围内,我们接收一个QueryBuilder实例和ProductFilters我们从控制器传递下来的实例。apply然后我们在这个$filter实例上调用方法。

到目前为止,我们知道这个ProductFilters类看起来像这样:

namespace App\Filters;

class ProductFilters
{
    public function apply($query)
    {
        if (request()->filled('price')) {
            list($min, $max) = explode(",", $request->price);

            $query->where('price', '>=', $min)
                  ->where('price', '<=', $max);
        }

    
        if (request()->filled('category')) {
            $categorySlug = $request->category;

            $query->whereHas('category', function ($query) use ($categorySlug) {
                $query->where('slug', $categorySlug);
            });
        }

        if (request()->filled('brand')) {
            $brandSlug = $request->brand;

            $query->whereHas('brand', function ($query) use ($brandSlug) {
                $query->where('slug', $brandSlug);
            });
        }


        return $query->get();
    }
}


这段代码可以工作,但并不比控制器中的过滤器好多少。而不是这样做,我想有单独的过滤器类,只包含它们的过滤逻辑。

让我们为类别过滤器执行此操作:

namespace App\Filters;


class CategoryFilter
{
    function __invoke($query, $categorySlug)
    {
        return $query->whereHas('category', function ($query) use ($categorySlug) {
            $query->where('slug', $categorySlug);
        });
    }
}


这里我们有一个自包含的类,我们甚至可以在其他模型中使用,假设我们有一个附加到商店的博客,我们可以对博客文章使用相同的过滤器。

顺便说一句,如果您不熟悉__invoke魔术方法,可以在这里找到更多信息:https ://www.php.net/manual/en/language.oop5.magic.php#object.invoke 。简而言之,它只是让我们像调用函数一样调用类的实例。

在为过滤器创建类之后,我们需要找到一种从ProductFilters类中调用它们的方法。有很多方法可以做到这一点,对于本文,我将采用以下方法:


namespace App\Filters;

class ProductFilters
{
    protected $filters = [
        'price' => PriceFilter::class,
        'category' => CategoryFilter::class,
        'brand' => BrandFilter::class,
    ];

    public function apply($query)
    {
        foreach ($this->receivedFilters() as $name => $value) {
            $filterInstance = new $this->filters[$name];
            $query = $filterInstance($query, $value);
        }

        return $query;
    }


    public function receivedFilters()
    {
        return request()->only(array_keys($this->filters));
    }
}


这是完成的课程,让我分解并解释每个部分。

这是如何运作的?
首先,让我们从$filters属性开始

 

   protected $filters = [
        'category' => CategoryFilter::class,
        'price' => PriceFilter::class,
        'brand' => BrandFilter::class,
    ];


在这里,我为产品定义了每个可选过滤器,数组的键是用于检查过滤器是否在请求中的键,数组的值是定义过滤器行为的类。这使我们可以轻松添加更多过滤器,当我们需要添加新过滤器时,我们只需创建新过滤器类并将该类添加到此数组中。

第二部分是这个方法:

    public function receivedFilters()
    {
        return request()->only(array_keys($this->filters));
    }


此方法用于查找请求中正在使用哪些过滤器,因此我们仅应用所需的过滤器。我们还调用该only方法并将$filters数组的键传递给它,以防止我们尝试调用不存在的过滤器类。假设有人发送过滤器“大小”,但我们目前不支持它,这将使请求密钥被忽略。

现在来说说这个类的肉,apply方法:

    public function apply($query)
    {
        foreach ($this->receivedFilters() as $name => $value) {
            $filterInstance = new $this->filters[$name];
            $query = $filterInstance($query, $value);
        }

        return $query;
    }


这个方法只是一个 for each 循环通过接收到的过滤器创建一个过滤器类,然后使用$query请求中接收到的值和值调用过滤器。

例如,假设我们收到一个如下所示的请求:

['category' => 'mobile-phones', 'price' => '100,150']


此类将首先创建一个实例,CategoryFilter然后将$query和“手机”传递给它。本质上,它会这样做:

$filterInstance = new CategoryFilter();
$query = $filterInstance($query, 'mobile-phones');


下一个同样的事情PhoneFilter:

$filterInstance = new PhoneFilter();
$query = $filterInstance($query, '100,150');


然后在应用所有查询之后,它将返回$query带有每个请求过滤器的对象。这就是我们在控制器中应用范围时收到的内容:

class ProductsController extends Controller
{
    public function index(ProductFilters $filters)
    {
        return Product::filter($filters)->get();
    }
}


结论
我们创建了一种可扩展的方式来为我们的产品制作多个过滤器,并且这里有一些我们可以改进的地方,例如为过滤器值添加验证或创建一个基类,ProductFilters以便我们可以将相同类型的过滤器添加到其他模型中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rorg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值