让你的ORM条件筛选更加的优雅[合理性的偷懒]

前言

隔了很久再来分享一个小魔法
在laravel中,为模型添加筛选条件一般都是使用when方法,如下:

use App\Models\Article;

public function index(Request $request)
{
    $articles = Article::with('category:id,name', 'tags:id,name')
        ->when($request->filled('keyword'), function ($query) use ($request) {
            $query->where('title', 'LIKE', "%{$request->query('keyword')}%");
        })
        ->paginate(20, ['id', 'title', 'introduce', 'is_recommend', 'category_id']);
    return view('admin.article.index', compact('articles', 'request'));
}

虽然没使用变量保存模型句柄,然后再使用if判断,可是我觉得代码是还可以再精【tou】简【lan】的,下面就来看看如何实现

精妙的使用数组展开符

在你的基础模型添加一个公用的scope方法

class Base extends Model
{
    /**
     * 按需进行条件筛选
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  bool   $bool
     * @param  array  $parameters
     * @return void
     */
    public function scopeWhenWhere(Builder $query, bool $bool, ...$parameters)
    {
        $bool && $query->where(...$parameters);
    }
}

你可能会在想这样之后呢?这样之后,就是偷懒的时刻!

/**
 * Display a listing of the resource.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\View\View
 */
public function index(Request $request)
{
    $articles = Article::with('category:id,name', 'tags:id,name')
        ->whenWhere($request->filled('keyword'), 'title', 'LIKE', "%{$request->query('keyword')}%")
        ->whenWhere($request->filled('category_id'), 'category_id', $request->query('category_id'))
        ->paginate(20, ['id', 'title', 'introduce', 'is_recommend', 'category_id']);
    return view('admin.article.index', compact('articles', 'request'));
}

这一行代码主要体现在使用数组展开符,也就是说第一个参数用来做短路判断,如原来when的第一个参数一样,接下来的参数按位置传给where函数,就会达到一模一样的效果,而且拓展性也是一模一样的,因为如果你要做复杂的判断,那么你也可以传入闭包【虽然这个时候你使用原来的when也是一样】

再进化,让其支持自动执行scope

Article模型内的scopeRecommend方法

/**
 * 是否为推荐文章
 * 
 * @param  \Illuminate\Database\Eloquent\Builder  $query
 * @param  bool  $recommend
 * @return bool
 */
public function scopeRecommend(Builder $query, bool $recommend = true)
{
    $query->where('is_recommend', $recommend);
}

这个需求诞生在我近段时间写开源的博客和其附带的app时遇到的,我不想笨笨的传入闭包然后在闭包内调用scope,如:

$articles = Article::with('category:id,name', 'tags:id,name')
    ->when($request->filled('is_recommend'), function ($query) use ($request) {
        $query->recommend($request->query('is_recommend'));
    })
    ->paginate(20, ['id', 'title', 'introduce', 'is_recommend', 'category_id']);

或者

$articles = Article::with('category:id,name', 'tags:id,name')
    ->whenWhere($request->filled('is_recommend'), function ($query) use ($request) {
        $query->recommend($request->query('is_recommend'));
    })
    ->paginate(20, ['id', 'title', 'introduce', 'is_recommend', 'category_id']);

那么让我们对whenWhere这个scope方法进行小小的修改,虽然代码就没有一行那么精妙,但是如果有需要调用scope的同学,可以进行如下修改:

/**
 * 按需进行条件筛选
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $query
 * @param  bool   $bool
 * @param  array  $parameters
 * @return void
 */
public function scopeWhenWhere(Builder $query, bool $bool, ...$parameters)
{
    if ($bool) {
        if (isset($parameters[1]) && $parameters[1] === 'scope') {
            $query->{$parameters[0]}(...array_slice($parameters, 2));
        } else {
            $query->where(...$parameters);
        }
    }
}

接下来我就只需要这样使用即可

$articles = Article::with('category:id,name', 'tags:id,name')
    ->whenWhere($request->filled('is_recommend'), 'recommend', 'scope', $request->query('is_recommend'))
    ->whenWhere($request->filled('keyword'), 'title', 'LIKE', "%{$request->query('keyword')}%")
    ->whenWhere($request->filled('category_id'), 'category_id', $request->query('category_id'))
    ->paginate(20, ['id', 'title', 'introduce', 'is_recommend', 'category_id']);

看完这篇文章,你学会偷懒的正确姿势了吗?
最后附带另外一个小魔法,细心的同学可能还发现了一个地方,那就是我的with的写法为什么是冒号加字段名,请不要误会,这里是laravel本身就有的写法,冒号前面是关联名称,冒号后面是需要查询的字段,可以很方便的帮你避免使用闭包然后在闭包内使用select方法限定字段。

预告

下一次发文可能就是开源博客代码的时候了
博客功能非常非常简单,前台使用媒体查询进行自适应,app端使用flutter进行开发
开发这个仅仅是想提供开发思路给大家,如后台的封装,前台的封装,写出更好的代码
laravel源码内会包含极度正规的代码规范和一些代码架构封装【但是并不会使用Repository模式,一些小缺陷会在开源时说明】
如果你想学习php良好的开发姿势,不妨关注一下

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 7
3年前 评论
Siam (楼主) 3年前

wanchaochao.github.io/laravel-repo...

引入包

composer require littlebug/laravel-repository:2.0.*

使用命令生成:ArticleRepositpory

 php artisan  core:repository --name=ArticleRepositpory --model=Article

生成文件如下:

use App\Models\Article;
use Littlebug\Repository\Repository;

class ArticleRepositpory extends Repository 
{
    public function __construct(Article $model)
    {
        parent::__construct($model);
    }
}

控制器中使用

$pagination = ArticleRepositpory::instance()->filterPaginate([
    'is_recommend' => request()->input('is_recommend'),
    'keyword:like' => request()->input('keyword'),
    'category_id'  => request()->input('category_id'),
], [
    // 本表字段查询
    'id', 'title', 'introduce', 'is_recommend', 'category_id', 
    // 关联表字段查询,相当于with
    'category' => ['id', 'name'],
    'tags' => ['id', 'name'],
]);
3年前 评论
Siam (楼主) 3年前

期待开源博客 :grin:

3年前 评论
Siam (楼主) 3年前

TP 的 搜索器 的思路可以更优雅,像 访问器 一样定义即可,调用时也无需这么一大堆代码,最重要最重要的是可以复用,可以参考下 TP 源码,实现方式很简单粗暴

3年前 评论
Siam (楼主) 3年前
 ->when($request->filled('keyword'), function ($query) use ($request) {
   $query->where('title', 'LIKE', "%{$request->query('keyword')}%");
 })->whenWhere($request->filled('keyword'), 'title', 'LIKE', "%{$request->query('keyword')}%")

就是少了个闭包 感觉还不够懒

3年前 评论
Siam (楼主) 3年前

感觉大家的理解和我的理解有些偏颇,此文仅仅是加一个scope作用域方法,并不是把这个筛选方法抽象出一个层次,需要注意的是,抽象的意义不是让你做的事情减少,甚至在绝大绝大部分情况下,抽象之后,你所做的事情会增多【小声哔哔,虽然我也在laravel的request层上又抽象了一个request】

3年前 评论

如果想要更多,可以自己定义各种scope,如大小比较,等于比较,区间比较等等,然后类似laravel源码一样,定义__callStatic魔术方法,按照你自己的规范从方法名截取字段名,再调用对应的scope,然后甚至再在scope方法内判断值是否存在...

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!