关键词新增拼音查询支持,减少大量 orWhere () 的分享

最新甲方提出了搜索产品支持拼音及其首字母查询的需求,此前支持按产品的编码、正式名称、别名、生产商的搜索。
按照传统方法,需要添加大量 ->orWhere(‘field’, ‘like’, ‘%’ . $keyword . ‘%’) 语句,造成冗余。
后来根据 where() 里面可以传入闭包,在 helpers.php 写了一个函数,调用该函数,即可省去大量 orWhere() 语句。

/**
 * 带拼音的查询
 * @author long2ge
 * @param string $keyword 关键词
 * @param string[] $prefixes 前缀
 * @param string[] $suffixes 后缀
 * @param bool $hasLeftPercentage 有否左百分号
 * @param mixed ...$fields 字段
 * @return Closure
 */
function queryWithPinyin(
    string $keyword,
    array $prefixes,
    array $suffixes,
    bool $hasLeftPercentage = true,
    ...$fields
): Closure {
    if (func_num_args() < 5 || ! func_get_arg(4)) {
        throw new BadFunctionCallException(__FUNCTION__ . '() requires a 5th parameter which is not empty!');
    }

    $keyword = ($hasLeftPercentage ? '%' : '') . $keyword . '%';
    $arg_5 = func_get_arg(4);
    $keywordConds = $prefixConds = $suffixConds = [];

    if (is_array($arg_5)) {
        $fields = $arg_5;
    } elseif ($arg_5 instanceof Illuminate\Contracts\Support\Arrayable) {
        $fields = $arg_5->toArray();
    }

    $keywordConds = array_map(function ($field) use ($keyword) {
        return [$field, 'like', $keyword];
    }, $fields);

    $hasMultibyteChar = strlen($keyword) != mb_strlen($keyword);

    if ($prefixes && ! $hasMultibyteChar) {
        foreach ($prefixes as $prefix) {
            $prefixConds = array_merge($prefixConds, array_map(function ($field) use ($keyword) {
            return [$prefix . '_' . $field, 'like', $keyword];
        }, $fields));
        }
    }

    if ($suffixes && ! $hasMultibyteChar) {
        foreach ($suffixes as $suffix) {
            $suffixConds = array_merge($suffixConds, array_map(function ($field) use ($keyword) {
            return [$field . '_' . $suffix, 'like', $keyword];
        }, $fields));
        }
    }

    $conds = array_merge($keywordConds, $prefixConds, $suffixConds);

    return function ($query) use ($conds) {
        foreach ($conds as $cond) {
            $query->orWhere([$cond]);
        }
    };
}

这样子,添加拼音及其首字母查询功能,就无须再大量调用 orWhere() 方法,可以把上述函数返回结果直接作为传入 where() 乃至 whereHas() 的参数。

public function getProductCollection(int $shopId, string $keyword)
{
    return Product::where('shop_id', $shopId)
        ->where(function ($q) use ($keyword) {
            $q->where(queryWithPinyin(
                $keyword,
                ['initial'],
                ['pinyin'],
                true,
                'product_code',
                'product_name',
                'product_alias'
            ))->orWhereHas('factory', queryWithPinyin( // 生产商暂不支持首字母搜索
                $keyword,
                [],
                ['pinyin'],
                false,
                ['factory_name', 'factory_alias']
            ));
        })->get();
}
Long2Ge
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 5
long2ge

初次发表分享与讨论,若存在错误或者可以改善的地方,望各位大佬多多指点。

3年前 评论

我觉得做成全局scope会更优雅

3年前 评论

scope 就可以了, 可以参考zhuanlan.zhihu.com/p/60398404 这种多条件的

3年前 评论
long2ge

@sinofaneliu @沈小明 ,为了避免带汉字的字符串无意义查询拼音字段造成性能损耗,稍微改了一下代码。 仍然可以用 scope 么?

3年前 评论

@long2ge 我觉得 能不能在表里怎加一个字段提供关联查询 这样比较方便

3年前 评论

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