lorisleiva/laravel-search-string

使用简单可定制的语法,根据一个唯一的字符串生成数据库查询。

v1.3.0 2023-03-11 10:11 UTC

This package is auto-updated.

Last update: 2024-09-11 13:20:55 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

使用简单可定制的语法,根据一个唯一的字符串生成数据库查询。

Example of a search string syntax and its result

简介

Laravel Search String 提供了一种简单的方法,使用人类可读和可定制的语法来范围数据库查询。它将简单的字符串转换成强大的查询构建器。

例如,以下搜索字符串将检索最新未发布的博客文章或标题为 "我的博客文章" 的文章。

Article::usingSearchString('title:"My blog article" or not published sort:-created_at');

// Equivalent to:
Article::where('title', 'My blog article')
       ->orWhere('published', false)
       ->orderBy('created_at', 'desc');

下一个示例将在 customerdescription 列上搜索 "John",同时确保发票已支付或存档。

Invoice::usingSearchString('John and status in (Paid,Archived) limit:10 from:10');

// Equivalent to:
Invoice::where(function ($query) {
           $query->where('customer', 'like', '%John%')
               ->orWhere('description', 'like', '%John%');
       })
       ->whereIn('status', ['Paid', 'Archived'])
       ->limit(10)
       ->offset(10);

您还可以查询相关记录的存在,例如,2020年发布的文章,这些文章有超过100条评论,这些评论不是垃圾邮件或由John编写的。

Article::usingSearchString('published = 2020 and comments: (not spam or author.name = John) > 100');

// Equivalent to:
Article::where('published_at', '>=', '2020-01-01 00:00:00')
        ->where('published_at', '<=', '2020-12-31 23:59:59')
        ->whereHas('comments', function ($query) {
            $query->where('spam', false)
                ->orWhereHas('author' function ($query) {
                    $query->where('name', 'John');
                });
        }, '>', 100);

如您所见,它不仅提供了一种方便的方式与您的 Laravel API 通信(而不是允许成百上千的查询字段),还可以将其展示给您的用户作为探索其数据的一种工具。

安装

# Install via composer
composer require lorisleiva/laravel-search-string

# (Optional) Publish the search-string.php configuration file
php artisan vendor:publish --tag=search-string

基本用法

SearchString 特性添加到您的模型中,并配置搜索字符串中应使用的列。

use Lorisleiva\LaravelSearchString\Concerns\SearchString;

class Article extends Model
{
    use SearchString;

    protected $searchStringColumns = [
        'title', 'body', 'status', 'rating', 'published', 'created_at',
    ];
}

注意,您可以在代码的其他部分中定义这些内容,并自定义每列的行为

这就完成了!现在您可以使用搜索字符串语法创建数据库查询。

Article::usingSearchString('title:"Hello world" sort:-created_at,published')->get();

搜索字符串语法

注意,操作符之间的空格无关紧要。

精确匹配

'rating: 0'
'rating = 0'
'title: Hello'               // Strings without spaces do not need quotes
'title: "Hello World"'       // Strings with spaces require quotes
"title: 'Hello World'"       // Single quotes can be used too
'rating = 99.99'
'created_at: "2018-07-06 00:00:00"'

比较

'title < B'
'rating > 3'
'created_at >= "2018-07-06 00:00:00"'

列表

'title in (Hello, Hi, "My super article")'
'status in(Finished,Archived)'
'status:Finished,Archived'

日期

该列必须被转换为日期或明确标记为日期在 列选项 中。

// Year precision
'created_at >= 2020'                    // 2020-01-01 00:00:00 <= created_at
'created_at > 2020'                     // 2020-12-31 23:59:59 < created_at
'created_at = 2020'                     // 2020-01-01 00:00:00 <= created_at <= 2020-12-31 23:59:59
'not created_at = 2020'                 // created_at < 2020-01-01 00:00:00 and created_at > 2020-12-31 23:59:59

// Month precision
'created_at = 01/2020'                  // 2020-01-01 00:00:00 <= created_at <= 2020-01-31 23:59:59
'created_at <= "Jan 2020"'              // created_at <= 2020-01-31 23:59:59
'created_at < 2020-1'                   // created_at < 2020-01-01 00:00:00

// Day precision
'created_at = 2020-12-31'               // 2020-12-31 00:00:00 <= created_at <= 2020-12-31 23:59:59
'created_at >= 12/31/2020"'             // 2020-12-31 23:59:59 <= created_at
'created_at > "Dec 31 2020"'            // 2020-12-31 23:59:59 < created_at

// Hour and minute precisions
'created_at = "2020-12-31 16"'          // 2020-12-31 16:00:00 <= created_at <= 2020-12-31 16:59:59
'created_at = "2020-12-31 16:30"'       // 2020-12-31 16:30:00 <= created_at <= 2020-12-31 16:30:59
'created_at = "Dec 31 2020 5pm"'        // 2020-12-31 17:00:00 <= created_at <= 2020-12-31 17:59:59
'created_at = "Dec 31 2020 5:15pm"'     // 2020-12-31 17:15:00 <= created_at <= 2020-12-31 17:15:59

// Exact precision
'created_at = "2020-12-31 16:30:00"'    // created_at = 2020-12-31 16:30:00
'created_at = "Dec 31 2020 5:15:10pm"'  // created_at = 2020-12-31 17:15:10

// Relative dates
'created_at = today'                    // today between 00:00 and 23:59
'not created_at = today'                // any time before today 00:00 and after today 23:59
'created_at >= tomorrow'                // from tomorrow at 00:00
'created_at <= tomorrow'                // until tomorrow at 23:59
'created_at > tomorrow'                 // from the day after tomorrow at 00:00
'created_at < tomorrow'                 // until today at 23:59

布尔值

该列必须被转换为布尔值或明确标记为布尔值在 列选项 中。

或者,如果列被标记为日期,它将自动使用 is nullis not null 标记为布尔值。

'published'         // published = true
'created_at'        // created_at is not null

否定

'not title:Hello'
'not title="My super article"'
'not rating:0'
'not rating>4'
'not status in (Finished,Archived)'
'not published'     // published = false
'not created_at'    // created_at is null

空值

术语 NULL 是区分大小写的。

'body:NULL'         // body is null
'not body:NULL'     // body is not null

可搜索

至少必须有一个列被 定义为可搜索

查询的术语不能匹配布尔列,否则它将被处理为布尔查询。

'Apple'             // %Apple% like at least one of the searchable columns
'"John Doe"'        // %John Doe% like at least one of the searchable columns
'not "John Doe"'    // %John Doe% not like any of the searchable columns

与/或

'title:Hello body:World'        // Implicit and
'title:Hello and body:World'    // Explicit and
'title:Hello or body:World'     // Explicit or
'A B or C D'                    // Equivalent to '(A and B) or (C and D)'
'A or B and C or D'             // Equivalent to 'A or (B and C) or D'
'(A or B) and (C or D)'         // Explicit nested priority
'not (A and B)'                 // Equivalent to 'not A or not B'
'not (A or B)'                  // Equivalent to 'not A and not B'

关系

该列必须明确 定义为关系,并且与该关系关联的模型也必须使用 SearchString 特性。

在关系内部进行嵌套查询时,Laravel Search String 将使用相关模型的列定义。

在以下示例中,comments 是一个 HasMany 关系,而 author 是在 Comment 模型内部的一个嵌套的 BelongsTo 关系。

// Simple "has" check
'comments'                              // Has comments
'not comments'                          // Doesn't have comments
'comments = 3'                          // Has 3 comments
'not comments = 3'                      // Doesn't have 3 comments
'comments > 10'                         // Has more than 10 comments
'not comments <= 10'                    // Same as before
'comments <= 5'                         // Has 5 or less comments
'not comments > 5'                      // Same as before

// "WhereHas" check
'comments: (title: Superbe)'            // Has comments with the title "Superbe"
'comments: (not title: Superbe)'        // Has comments whose titles are different than "Superbe"
'not comments: (title: Superbe)'        // Doesn't have comments with the title "Superbe"
'comments: (quality)'                   // Has comments whose searchable columns match "%quality%"
'not comments: (spam)'                  // Doesn't have comments marked as spam
'comments: (spam) >= 3'                 // Has at least 3 spam comments
'not comments: (spam) >= 3'             // Has at most 2 spam comments
'comments: (not spam) >= 3'             // Has at least 3 comments that are not spam
'comments: (likes < 5)'                 // Has comments with less than 5 likes
'comments: (likes < 5) <= 10'           // Has at most 10 comments with less than 5 likes
'not comments: (likes < 5)'             // Doesn't have comments with less than 5 likes
'comments: (likes > 10 and not spam)'   // Has non-spam comments with more than 10 likes

// "WhereHas" shortcuts
'comments.title: Superbe'               // Same as 'comments: (title: Superbe)'
'not comments.title: Superbe'           // Same as 'not comments: (title: Superbe)'
'comments.spam'                         // Same as 'comments: (spam)'
'not comments.spam'                     // Same as 'not comments: (spam)'
'comments.likes < 5'                    // Same as 'comments: (likes < 5)'
'not comments.likes < 5'                // Same as 'not comments: (likes < 5)'

// Nested relationships
'comments: (author: (name: John))'      // Has comments from the author named John
'comments.author: (name: John)'         // Same as before
'comments.author.name: John'            // Same as before

// Nested relationships are optimised
'comments.author.name: John and comments.author.age > 21'   // Same as: 'comments: (author: (name: John and age > 21))
'comments.likes > 10 or comments.author.age > 21'           // Same as: 'comments: (likes > 10 or author: (age > 21))

请注意,所有这些表达式都委托给 has 查询方法。因此,它与以下关系类型无缝工作: HasOneHasManyHasOneThroughHasManyThroughBelongsToBelongsToManyMorphOneMorphManyMorphToMany

目前不支持的关系类型是 MorphTo,因为 Laravel Search String 需要一个明确的关联模型来用于嵌套查询。

特殊关键字

请注意,这些关键字可以自定义

'fields:title,body,created_at'  // Select only title, body, created_at
'not fields:rating'             // Select all columns but rating
'sort:rating,-created_at'       // Order by rating asc, created_at desc
'limit:1'                       // Limit 1
'from:10'                       // Offset 10

配置列

列别名

如果您想用不同的名称查询列,您可以将其定义为键值对,其中键是数据库列名称,值是您希望使用的别名。

protected $searchStringColumns = [
    'title',
    'body' => 'content',
    'published_at' => 'published',
    'created_at' => 'created',
];

您还可以提供一个正则表达式模式以进行更灵活的别名定义。

protected $searchStringColumns = [
    'published_at' => '/^(published|live)$/',
    // ...
];

列选项

您可以通过为列分配选项数组进一步配置列。

protected $searchStringColumns = [
    'created_at' => [
        'key' => 'created',         // Default to column name: /^created_at$/
        'date' => true,             // Default to true only if the column is cast as date.
        'boolean' => true,          // Default to true only if the column is cast as boolean or date.
        'searchable' => false       // Default to false.
        'relationship' => false     // Default to false.
        'map' => ['x' => 'y']       // Maps data from the user input to the database values. Default to [].
    ],
    // ...
];

key选项是到目前为止我们一直在配置的,即列的别名。它可以是正则表达式模式(因此允许多个匹配)或用于精确匹配的常规字符串。

日期

如果列被标记为date,查询值将使用Carbon进行解析,同时保持用户提供的精度级别。例如,如果created_at列被标记为date

'created_at >= tomorrow' // Equivalent to:
$query->where('created_at', '>=', 'YYYY-MM-DD 00:00:00');
// where `YYYY-MM-DD` matches the date of tomorrow.

'created_at = "July 6, 2018"' // Equivalent to:
$query->where('created_at', '>=', '2018-07-06 00:00:00');
      ->where('created_at', '<=', '2018-07-06 23:59:59');

默认情况下,任何被转换为日期的列(使用Laravel属性),对于LaravelSearchString都会被标记为日期。您可以通过将date分配给false来强制一个列不标记为日期。

布尔值

如果列被标记为boolean,它可以不使用运算符或值进行使用。例如,如果paid列被标记为boolean

'paid' // Equivalent to:
$query->where('paid', true);

'not paid' // Equivalent to:
$query->where('paid', false);

如果列被同时标记为booleandate,当用作布尔值时,它将比较null。例如,如果published_at列被标记为booleandate并使用published别名

'published' // Equivalent to:
$query->whereNotNull('published');

'not published_at' // Equivalent to:
$query->whereNull('published');

默认情况下,任何被转换为布尔值或日期的列(使用Laravel属性),都会被标记为布尔值。您可以通过将boolean分配给false来强制一个列不标记为布尔值。

可搜索

如果列被标记为searchable,它将用于匹配搜索查询,即单独的术语,但不像是布尔值那样的Apple Banana"John Doe"

例如,如果列titledescription都被标记为searchable

'Apple Banana' // Equivalent to:
$query->where(function($query) {
          $query->where('title', 'like', '%Apple%')
                ->orWhere('description', 'like', '%Apple%');
      })
      ->where(function($query) {
          $query->where('title', 'like', '%Banana%')
                ->orWhere('description', 'like', '%Banana%');
      });

'"John Doe"' // Equivalent to:
$query->where(function($query) {
          $query->where('title', 'like', '%John Doe%')
                ->orWhere('description', 'like', '%John Doe%');
      });

如果没有提供可搜索的列,则此类术语或字符串将被忽略。

关系

如果列被标记为relationship,它将用于查询关系。

列名称必须匹配模型上的有效关系方法,但像往常一样,可以使用key选项创建别名。

与该关系方法关联的模型也必须使用SearchString特性以嵌套关系查询。

例如,假设您有一个Article模型,并且您想查询其相关的评论。那么,必须有一个有效的comments关系方法,并且Comment模型本身必须使用SearchString特性。

use Lorisleiva\LaravelSearchString\Concerns\SearchString;

class Article extends Model
{
    use SearchString;

    protected $searchStringColumns = [
        'comments' => [
            'key' => '/^comments?$/',   // aliases the column to `comments` or `comment`.
            'relationship' => true,     // There must be a `comments` method that defines a relationship.
        ],
    ];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

class Comment extends Model
{
    use SearchString;

    protected $searchStringColumns = [
        // ...
    ];
}

请注意,由于Laravel Search String只是委托给$builder->has(...)方法,因此您可以提供任何您想要的复杂关系方法,并且约束将会保留。例如

protected $searchStringColumns = [
    'myComments' => [
        'key' => 'my_comments',
        'relationship' => true,
    ],
];

public function myComments()
{
    return $this->hasMany(Comment::class)->where('author_id', Auth::user()->id);
}

配置特殊关键字

您可以通过在$searchStringKeywords属性内定义键值对来自定义关键字的名称。

protected $searchStringKeywords = [
    'select' => 'fields',   // Updates the selected query columns
    'order_by' => 'sort',   // Updates the order of the query results
    'limit' => 'limit',     // Limits the number of results
    'offset' => 'from',     // Starts the results at a further index
];

类似于列值,您可以为关键字定义一个自定义的key数组。请注意,对于关键字,datebooleansearchablerelationship选项不适用。

protected $searchStringKeywords = [
    'select' => [
        'key' => 'fields',
    ],
    // ...
];

其他配置位置

如我们所见,您可以通过在模型上的searchStringColumnssearchStringKeywords属性上配置您的列和特殊关键字。

您还可以在模型上重写getSearchStringOptions方法,默认为

public function getSearchStringOptions()
{
    return [
        'columns' => $this->searchStringColumns ?? [],
        'keywords' => $this->searchStringKeywords ?? [],
    ];
}

如果您不想在模型本身上定义这些配置,可以直接在config/search-string.php文件中定义,如下所示

// config/search-string.php
return [
    'default' => [
        'keywords' => [ /* ... */ ],
    ],

    Article::class => [
        'columns'  => [ /* ... */ ],
        'keywords' => [ /* ... */ ],
    ],
];

当解决特定模型的选项时,LaravelSearchString将按照以下顺序合并这些配置

  1. 首先使用在模型上定义的配置
  2. 然后使用与模型类匹配键的配置文件
  3. 然后使用default键的配置文件
  4. 最后使用一些回退配置

配置不区分大小写的搜索

当使用PostgreSql等数据库时,您可以通过在选项中将case_insensitive设置为true来覆盖默认的大小写敏感搜索行为。例如,在config/search-string.php中

return [
    'default' => [
        'case_insensitive' => true, // <- Globally.
        // ...
    ],

    Article::class => [
        'case_insensitive' => true, // <- Only for the Article class.
        // ...
    ],
];

设置为true时,它会在使用like运算符比较之前将列和值都转换为小写。

$value = mb_strtolower($value, 'UTF8');
$query->whereRaw("LOWER($column) LIKE ?", ["%$value%"]);

错误处理

提供的搜索字符串可能因多种原因无效。

  • 它不符合搜索字符串语法
  • 它试图查询一个不存在的列或列别名
  • 它为特殊关键词(如limit)提供了无效的值
  • 等等。

任何这些错误都将抛出InvalidSearchStringException

但是,您可以选择是否希望这些异常冒泡到Laravel异常处理器,或者希望它们静默失败。为此,您需要在config/search-string.php配置文件中选择一个失败策略

// config/search-string.php
return [
    'fail' => 'all-results', // (Default) Silently fail with a query containing everything.
    'fail' => 'no-results',  // Silently fail with a query containing nothing.
    'fail' => 'exceptions',  // Throw exceptions.

    // ...
];