当前位置:网站首页>Filter the eloquent model using multiple optional filters
Filter the eloquent model using multiple optional filters
2022-04-22 01:12:00 【rorg】
wpcmf: wpcmf cms , Content management system , similar wordpress System
When displayed in the view , We often need to filter eloquent Model . If we have a few filters , This could be good , But if you need to add multiple filters , The controller may become cluttered and difficult to read .
This is especially true when dealing with multiple optional filters that can be used in combination .
however , There are some ways to create these filters , You can even make them reusable . At the end of this article , You will be able to better handle the complex filtering options in your project .
Define the problem
For example , I have a controller method , It returns all the products in our store , This can be used for API, Pass it to the blade template , Or whatever , The controller may look like this :
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductsController extends Controller
{
public function index()
{
return Product::all();
}
}
However , It's not realistic at all . Usually , We need to add some filters , for example , Suppose we just want to get products in a given category , We can send the category in the query string slug And then use slug Filter to do this . So our new controller looks like this :
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();
}
}
If we only need one filter , This is good . however , See what happens if we only introduce two other filtering options :
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();
}
}
We have introduced two more filters , One for brand slug, A price range . in my opinion , It's too busy , It's hard to track what's happening here , If we need to add more filtering options , Things will get worse soon .
for example , When you are in eBay When searching for products on , You usually get more than a dozen optional filters . We need to find another way to do this .
Imagine
If we didn't include all these filters in our controller , What if we can do something like this :
class ProductsController extends Controller
{
public function index(ProductFilters $filters)
{
return Product::filter($filters)->get();
}
}
ad locum , We received a ProductFilters Classes that may contain all filters , Then we can apply them to a project called The query scope of filter. This makes our controller very thin , It's easy to guess what happened to it . We are screening products , If we need more details , We can see ProductFilters class .
Implement new methods
First , Let's add scope to our model :
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);
}
}
In this range , We receive a QueryBuilder Instance and ProductFilters The example we passed down from the controller .apply And then we're in this $filter Call method on instance .
up to now , We know this ProductFilters Class looks like this :
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();
}
}
This code works , But not much better than the filter in the controller . Instead of doing that , I want to have a separate filter class , Only their filtering logic .
Let's do this for the category filter :
namespace App\Filters;
class CategoryFilter
{
function __invoke($query, $categorySlug)
{
return $query->whereHas('category', function ($query) use ($categorySlug) {
$query->where('slug', $categorySlug);
});
}
}
Here we have a self-contained class , We can even use it in other models , Suppose we have a blog attached to the store , We can use the same filter for blog posts .
By the way , If you are not familiar with it __invoke Magic methods , More information can be found here :https ://www.php.net/manual/en/language.oop5.magic.php#object.invoke . In short , It just lets us call an instance of a class like a function .
After creating the class for the filter , We need to find a way from ProductFilters Method of calling middle note in class . There are many ways to do this , For this article , I will use the following methods :
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));
}
}
This is the completed course , Let me break down and explain each part .
How it works ?
First , Let's start with $filters Property start
protected $filters = [
'category' => CategoryFilter::class,
'price' => PriceFilter::class,
'brand' => BrandFilter::class,
];
ad locum , I defined each optional filter for the product , The key of the array is the key used to check whether the filter is in the request , The value of the array is the class that defines the behavior of the filter . This allows us to easily add more filters , When we need to add a new filter , We just need to create a new filter class and add it to this array .
The second part is this method :
public function receivedFilters()
{
return request()->only(array_keys($this->filters));
}
This method is used to find which filters are being used in the request , So we only apply the required filters . We also call the only Methods and $filters The key of the array is passed to it , To prevent us from trying to call a non-existent filter class . Suppose someone sends a filter “ size ”, But we don't support it at the moment , This will cause the request key to be ignored .
Now let's talk about this kind of meat ,apply Method :
public function apply($query)
{
foreach ($this->receivedFilters() as $name => $value) {
$filterInstance = new $this->filters[$name];
$query = $filterInstance($query, $value);
}
return $query;
}
This method is just a for each Loop through the received filter to create a filter class , And then use $query The value received in the request and the value call filter .
for example , Suppose we receive a request as follows :
['category' => 'mobile-phones', 'price' => '100,150']
This class will first create an instance ,CategoryFilter And then $query and “ mobile phone ” Pass it on . Essentially , It will do this :
$filterInstance = new CategoryFilter();
$query = $filterInstance($query, 'mobile-phones');
Next same thing PhoneFilter:
$filterInstance = new PhoneFilter();
$query = $filterInstance($query, '100,150');
Then after applying all the queries , It will return $query Objects with each request filter . This is what we received when we applied the scope in the controller :
class ProductsController extends Controller
{
public function index(ProductFilters $filters)
{
return Product::filter($filters)->get();
}
}
Conclusion
We created an extensible way to make multiple filters for our products , And here are some things we can improve , For example, add validation for filter values or create a base class ,ProductFilters So that we can add the same type of filter to other models .
版权声明
本文为[rorg]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204220033597214.html
边栏推荐
- I use ehcache to improve the query performance by 100 times. It's really fragrant
- 作文以记之 ~ 二叉树的中序遍历
- 腾讯云数据库MYSQL备份失败原因
- What are the main constraints on the development of mobile phone hardware performance
- ZGC garbage collector for JVM from getting started to giving up
- Lincoln Z vs. Audi A4L: the product power gap is actually the epitome of the two times
- js数组对象去重
- 《Vite学习指南---基于腾讯云Webify部署项目》视频课程上线“云+社区”
- Technology cloud report: DPU market is hot. Will several large factories be allowed to eat alone in the future?
- kubernetes+prometheus+grafana
猜你喜欢

Huawei cloud hosting experience: your software storage expert

Apachecon Asia 2022 speech collection begins!

We sincerely invite you to sign up for the first openharmony developer growth plan sharing day

MySQL数据库常识之储存引擎

I use ehcache to improve the query performance by 100 times. It's really fragrant

How can devices of different brands be compatible and interconnected? Yiwen teaches you to understand

Rpcx source code learning - server side

A simple and easy-to-use file upload scheme

Build a personal blog (WordPress) on Alibaba cloud

Watch mechanism of redis
随机推荐
Introduction to Haskell monoid
MySQL基础合集
Basic operation of redis transaction
Cloud security daily 220421: Cisco virtualization infrastructure manager software found a privilege upgrade vulnerability and needs to be upgraded as soon as possible
Mysql database common sense storage engine
The R language coin package is applied to permutation tests for independence problems and Wilcox Test function performs Wilcoxon signed rank test for two groups of data and wilcoxsign is used_ Test fu
Beauty salon management system based on SSM (with source code + project display)
L'Internet des objets n'a - t - il pas d'avenir?
The AOV function of R language is used for one-way ANOVA, the normality of the dependent variable of ANOVA is evaluated by Q-Q diagram, the equality (homogeneity) of Bartlett's verification variance,
Solve the problem that the idea web project does not have small blue dots
精彩回顾 | DEEPNOVA x Iceberg Meetup Online《基于Iceberg打造实时数据湖》
JS array object de duplication
Logstash import movie lens test data
观测云登陆阿里云计算巢,共建ISV新生态
MySQL进阶之表的增删改查
Public testing, exclusive, penetration testing, picking up ragged tips
Huawei cloud hosting experience: your software storage expert
How can devices of different brands be compatible and interconnected? Yiwen teaches you to understand
Shallow comparison between oceanbase and tidb - implementation plan
Boutique: thousand word long text hand-in-hand teaches you to use the byte beating volcanic engine veimagex