如何实现一个Laravel查询过滤器
今天小编给大家分享一下如何实现一个Laravel查询过滤器的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
创新互联建站是一家专注网站建设、网络营销策划、小程序设计、电子商务建设、网络推广、移动互联开发、研究、服务为一体的技术型公司。公司成立十余年以来,已经为上1000+成都砂岩浮雕各业的企业公司提供互联网服务。现在,服务的上1000+客户与我们一路同行,见证我们的成长;未来,我们一起分享成功的喜悦。
上下文
在撰写本文时,我在 PHP 8.1 和 MySQL 8 上使用 Laravel 9。我相信技术栈不是一个大问题,这里我们主要关注构建一个查询过滤器系统。在本文中,我将演示为 users 表构建过滤器。
id();
$table->string('name');
$table->string('email')->unique();
$table->string('gender', 10)->nullable()->index();
$table->boolean('is_active')->default(true)->index();
$table->boolean('is_admin')->default(false)->index();
$table->timestamp('birthday')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* 回退迁移
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
此外,我还使用 Laravel Telescope 轻松监控查询。
初始点
在学习使用 Laravel 的第一天,我经常直接在控制器上调用过滤器。简单,没有魔法,容易理解,但是这种方式有问题:
控制器中放置的大量逻辑导致控制器膨胀
不能重复使用
许多相同的工作重复
has('name')) {
$query->where('name', 'like', "%{$request->input('name')}%");
}
if ($request->has('email')) {
$query->where('email', 'like', "%{$request->input('email')}%");
}
if ($request->has('gender')) {
$query->where('gender', $request->input('gender'));
}
if ($request->has('is_active')) {
$query->where('is_active', $request->input('is_active') ? 1 : 0);
}
if ($request->has('is_admin')) {
$query->where('is_admin', $request->input('is_admin') ? 1 : 0);
}
if ($request->has('birthday')) {
$query->whereDate('birthday', $request->input('birthday'));
}
return $query->paginate();
// select * from `users` where `name` like '%ryder%' and `email` like '%hartman%' and `gender` = 'male' and `is_active` = 1 and `is_admin` = 0 and date(`birthday`) = '2014-11-30' limit 15 offset 0
}
}
使用 Local Scope
为了能够在过滤期间隐藏逻辑,让我们尝试使用 Laravel 的 Local Scope。将查询转换为 User 模型中的函数范围:
// User.php
public function scopeName(Builder $query): Builder
{
if (request()->has('name')) {
$query->where('name', 'like', "%" . request()->input('name') . "%");
}
return $query;
}
public function scopeEmail(Builder $query): Builder
{
if (request()->has('email')) {
$query->where('email', 'like', "%" . request()->input('email') . "%");
}
return $query;
}
public function scopeGender(Builder $query): Builder
{
if (request()->has('gender')) {
$query->where('gender', request()->input('gender'));
}
return $query;
}
public function scopeIsActive(Builder $query): Builder
{
if (request()->has('is_active')) {
$query->where('is_active', request()->input('is_active') ? 1 : 0);
}
return $query;
}
public function scopeIsAdmin(Builder $query): Builder
{
if (request()->has('is_admin')) {
$query->where('is_admin', request()->input('is_admin') ? 1 : 0);
}
return $query;
}
public function scopeBirthday(Builder $query): Builder
{
if (request()->has('birthday')) {
$query->where('birthday', request()->input('birthday'));
}
return $query;
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = User::query()
->name()
->email()
->gender()
->isActive()
->isAdmin()
->birthday();
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
通过这种设置,我们将大部分数据库操作移到了模型类中,但是代码重复非常多。示例 2 的名称和电子邮件范围过滤器相同,性别生日和 is_active/is_admin 组相同。我们将对类似的查询功能进行分组。
// User.php
public function scopeRelativeFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, 'like', "%" . request()->input($inputName) . "%");
}
return $query;
}
public function scopeExactFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, request()->input($inputName));
}
return $query;
}
public function scopeBooleanFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, request()->input($inputName) ? 1 : 0);
}
return $query;
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = User::query()
->relativeFilter('name')
->relativeFilter('email')
->exactFilter('gender')
->booleanFilter('is_active')
->booleanFilter('is_admin')
->exactFilter('birthday');
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
至此,我们已经对大部分重复项进行了分组。但是,删除 if 语句或将这些过滤器扩展到另一个模型有点困难。我们正在寻找一种方法来彻底解决这个问题。
使用管道设计模式
管道设计模式是一种设计模式,它提供了逐步构建和执行一系列操作的能力。 Laravel 有内置的 Pipeline 让我们可以很容易地在实际中应用这种设计模式,但由于某种原因,它没有在官方文档中列出。 Laravel 本身也将 Pipeline 应用于请求和响应之间的中间件。最基本的,要在 Laravel 中使用 Pipeline,我们可以这样使用
app(\Illuminate\Pipeline\Pipeline::class)
->send($intialData)
->through($pipes)
->thenReturn(); // data with pipes applied
对于我们的问题,可以将初始查询 User:query() 传递给 pipeline,通过过滤器步骤,并返回应用过滤器的查询构建器。
// UserController
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = app(Pipeline::class)
->send(User::query())
->through([
// filters
])
->thenReturn();
return $query->paginate();
现在我们需要构建管道过滤器:
// File: app/Models/Pipes/RelativeFilter.php
has($this->inputName)) {
$query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
}
return $next($query);
}
}
// File: app/Models/Pipes/ExactFilter.php
has($this->inputName)) {
$query->where($this->inputName, request()->input($this->inputName));
}
return $next($query);
}
}
//File: app/Models/Pipes/BooleanFilter.php
has($this->inputName)) {
$query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
}
return $next($query);
}
}
// UserController
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = app(Pipeline::class)
->send(User::query())
->through([
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
])
->thenReturn();
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
通过将每个查询逻辑移动到一个单独的类,我们解锁了使用 OOP 的定制可能性,包括多态、继承、封装、抽象。比如你在 pipeline 的 handle 函数中看到,只有 if 语句中的逻辑不同,我会通过创建抽象类 BaseFilter 的方式将其分离抽象出来
//File: app/Models/Pipes/BaseFilter.php
has($this->inputName)) {
$query = $this->apply($query);
}
return $next($query);
}
abstract protected function apply(Builder $query): Builder;
}
// BooleanFilter
class BooleanFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
}
}
// ExactFilter
class ExactFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, request()->input($this->inputName));
}
}
// RelativeFilter
class RelativeFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
}
}
现在我们的过滤器直观且高度可重用,易于实现甚至扩展,只需创建一个管道,扩展 BaseFilter 并声明函数 apply 即可应用到 Pipeline.中。
将 Local Scope 与 Pipeline 相结合
此时,我们将尝试在控制器上隐藏 Pipeline,通过在 Model 中创建一个调用 Pipeline 的作用域来使我们的代码更简洁。
// User.php
public function scopeFilter(Builder $query)
{
$criteria = $this->filterCriteria();
return app(\Illuminate\Pipeline\Pipeline::class)
->send($query)
->through($criteria)
->thenReturn();
}
public function filterCriteria(): array
{
return [
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
];
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
return User::query()
->filter()
->paginate()
->appends($request->query()); // 将所有当前查询附加到分页链接中
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
用户现在可以从任何地方调用过滤器。但是其他模型也想实现过滤,我们将创建一个包含范围的 Trait,并在模型内部声明参与过滤过程的 Pipeline。
// User.php
use App\Models\Concerns\Filterable;
class User extends Authenticatable {
use Filterable;
protected function getFilters()
{
return [
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
];
}
// 其余代码
// File: app/Models/Concerns/Filterable.php
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pipeline\Pipeline;
trait Filterable
{
public function scopeFilter(Builder $query)
{
$criteria = $this->filterCriteria();
return app(Pipeline::class)
->send($query)
->through($criteria)
->thenReturn();
}
public function filterCriteria(): array
{
if (method_exists($this, 'getFilters')) {
return $this->getFilters();
}
return [];
}
}
以上就是“如何实现一个Laravel查询过滤器”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注创新互联行业资讯频道。
网站栏目:如何实现一个Laravel查询过滤器
文章网址:http://ybzwz.com/article/gphdpd.html