vaheidirn/laravel-filter-query

为Laravel模型添加查询过滤功能的特性

dev-master 2018-05-11 12:22 UTC

This package is not auto-updated.

Last update: 2024-09-19 15:24:32 UTC


README

为Laravel模型提供可组合查询的特性。

预期使用方法是提供一个简单的方法来声明模型字段如何进行过滤查询,并允许过滤查询以易于表示的形式(例如JSON对象)表达。

VahidIrn\FilterQuery\FilterQueryTrait 为模型添加了一个 filter() 方法,该方法与 Laravel 查询构建器一起工作。filter() 方法接受一个参数,该参数是一个包含 [字段 => 值] 对的数组,用于定义正在进行的搜索查询。

安装

composer require vahidirn/laravel-filter-query

用法

基本用法是将特性添加到模型类中,并配置过滤字段。

use VahidIrn\FilterQuery\FilterQuery;

class User extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  protected $filterQuery = [
    'name' => FilterQuery::String,
    'email' => FilterQuery::String
  ];
}

$filter = [
  'name' => 'John'
];
User::filter($filter)->toSql();
// select * from users where name = ?

$filter = [
  'name_LIKE' => 'Jo%n',
  'email' => 'john@example.com'
];
User::filter($filter)->toSql();
// select * from users where name like ? and email = ?

$filterQuery 属性定义了在过滤查询中可能使用的字段。该值是一个包含过滤规则数组,列出过滤可能使用的可能变体。

内置规则是

  • EQ - 等于
  • LIKE - SQL like
  • MATCH - 通配符模式匹配
  • MIN - 大于等于
  • MAX - 小于等于
  • GT - 大于
  • LT - 小于
  • RE - 正则表达式
  • FT - 全文搜索(见以下说明)
  • IN - 包含在列表中
  • NULL - 空值比较

提供了一套标准规则

  • 字符串 = [EQ, LIKE, ILIKE, MATCH]
  • 数字 = [EQ, MIN, MAX, LT, GT]
  • 枚举 = [EQ, IN]
  • 日期 = [EQ, MIN, MAX, LT, GT]
  • 布尔值 = [EQ]

模型过滤查询定义设置了每个字段可用的规则。列表中的第一个规则是该字段的默认规则。其他规则必须在查询字段名称后添加规则名称作为后缀。

在以下定义中,'name' 字段可以使用任何字符串规则:EQ,LIKE,ILIKE,MATCH。

User::$filterQuery = [
  'name' => FilterQuery::String
]

因此,查询可以使用

$filter = [
  'name_MATCH' => 'John'
];
User::filter($filter);

执行的 SQL 将匹配任何名称为不区分大小写的匹配“John”的用户,例如“Little john”,“Johnathon”,“JOHN”。

模型过滤查询定义也可以通过在类上重载 getFilterQuery() 方法动态返回。

class User extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  public function getFilterQuery () {
    return [
      'name' => FilterQuery::String,
      'email' => FilterQuery::String
    ];
  }
}

自定义规则

类可以提供用于字段的自定义规则。

class User extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  protected $filterQuery = [
    'keyword' => 'Keyword'
  ];
  public function scopeFilterKeyword($query, $field, $arg) {
    $query->where(function ($q) use ($arg) {
      $q->where('name', $arg);
      $q->orWhere('email', $arg);
      $q->orWhere('phone', $arg);
    });
    return $query;
  }
}

自定义规则可以与内置规则一起列出在 $filterQuery 定义中,并按相同的方式工作。$filterQuery 中的第一个规则是该字段的默认规则。如果不是第一个规则,它必须在查询字段名称后添加规则名称作为后缀。

规则名称被转换为 'ucfirst' 并附加到 'scopeFilter'。

否定规则

可以通过使用 'NOT' 修饰符为默认规则或使用 'NOT_' 前缀为其他规则来否定规则。

// anyone but John
$filter = [
  'name_NOT' => 'John'
];
User::filter($filter)->toSql();
// select * from users where name != ?

// any status except 'active' or 'expired'
$filter = [
  'status_NOT_IN' => ['active', 'expired']
];
User::filter($filter)->toSql();
// select * from users where status not in (?, ?)

注意:比较规则(MIN,MAX,LT,GT)没有否定形式。

自定义规则可以通过定义相应的范围函数来实现否定版本,该函数具有类似的名字,但在规则名称之前有 'Not'。

class User extends Model
{
   ...
   public function scopeFilterNotKeyword($query, $field, $arg) {
     return $query
       ->where('name', '!=', $arg)
       ->where('email', '!=', $arg)
       ->where('phone', '!=', $arg);
   }
}

使用 AND,OR,NOT 和 NOR 进行逻辑组合

通过将过滤结构为嵌套查询,过滤可以创建更复杂的查询。

$filter = [
  'OR' => [
    [
      'name' => 'John'
    ],
    [
      'name' => 'Alice'
    ]
  ]
];
User::filter($filter)->toSql();
// select * from users where ((name = ?) or (name = ?))

“与”、“或”、“非”和“与非”嵌套操作符都接受一个嵌套过滤器列表来应用。嵌套过滤器查询可以使用嵌套操作符来创建更复杂的查询。

关系

可以使用关系来筛选相关模型。


class Post extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  public function getFilterQuery () {
    return [
      'comment' => $this->comments()
    ];
  }
  
  public function comments() {
    return $this->hasMany(Comment::class);
  }
}

class Comment extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  public function getFilterQuery () {
    return [
      'created_at' => FilterQuery::Date
    ];
  }
  
  public function post() {
    return $this->belongsTo(Post::class);
  }
}

# Get all posts that have a comment in June 2017
$filter = [
  'comment' => [
    'created_at_MIN' => '2017-06-01',
    'cerated_at_MAX' => '2017-07-01'
  ]
];
Post::filter($filter)->toSql()
// select * from "posts"
// inner join (
//   select distinct "comments"."post_id" from "comments"
//   where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"

链式过滤器

链式时,过滤器会进行深度合并。

class Post extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  public function getFilterQuery () {
    return [
      'comment' => $this->comments()
    ];
  }
  
  public function comments() {
    return $this->hasMany(Comment::class);
  }
}

class User extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  protected $filterQuery = [
    'id' => FilterQuery::Integer
  ];
}

class Comment extends Model
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  
  public function getFilterQuery () {
    return [
      'created_at' => FilterQuery::Date,
      'author' => $this->author()
    ];
  }
  
  public function post() {
    return $this->belongsTo(Post::class);
  }
  
  public function author() {
    return $this->belongsTo(User::class);
  }
}
$filter1 = [
  'comment' => [
    'created_at_MIN' => '2017-06-01',
    'created_at_MAX' => '2017-07-01'
  ]
];
$filter2 = [
  'comment' => [
    'author' => [
      'id' => 1
    ]
  ]
];

Post::filter($filter1)->filter($filter2)->toSql()
// select * from "posts"
// inner join (
//   select distinct "comments"."post_id"
//   from "comments"
//   inner join (
//     select distinct "users"."id" as "author_id" from "users"
//     where "id" = ?
//   ) as "authors_1" on "comments"."author_id" = "authors_1"."author_id"
//   where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"

为了不进行深度合并,可以使用filterApply()方法。

Post::filter($filter1)->filterApply()->filter($filter2)->toSql()
// select * from "posts"
// inner join (
//   select distinct "comments"."post_id" from "comments"
//   where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"
// inner join (
//   select distinct "comments"."post_id" from "comments"
//   inner join (
//     select distinct "users"."id" as "author_id" from "users"
//     where "id" = ?
//   ) as "authors_1" on "comments"."author_id" = "authors_1"."author_id"
// ) as "comments_2" on "posts"."id" = "comments_2"."post_id"

全文搜索

FilterQuery::FT规则通过tsearch在PostgreSQL中提供了一种基本的全文搜索形式。

为了使用全文规则,应用程序必须提供一个包含搜索数据的表。默认情况下,表名以模型名命名,并以_filterQuery作为后缀,使用与模型表相同的唯一键进行一对一映射。tsearch向量数据存储在具有_vector后缀的列中。表和向量字段名可以通过为filterQueryFtTable和filterQueryFtVector属性提供值来自定义。

-- Content model table
create table posts (id int primary key, body text);
-- Full text search data
create table posts_filterQuery (id int references posts (id), body_vector tsvector);

应用程序必须确保向量字段得到适当的更新(通常通过定义触发器函数)。

class Post
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  protected $filterQuery = [
    'body' => FilterQuery::FT
  ];
}
$filter = [
  'body' => 'fat cats'
];
Post::filter($filter)->orderBy('body_rank', 'desc')->toSql();
// select * from "posts" inner join (
//  select "id", ts_rank("body_vector", query) as "body_rank"
//  from "posts_filterQuery"
//  cross join plainto_tsquery(?) query
//  where "body_vector" @@ "query"
// ) as body_1 on "posts"."id" = "body_1"."id"
// order by "body_rank" desc

模型可能为搜索表、外键和字段名提供自定义值。

class Post
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  protected $filterQuery = [
    'body' => FilterQuery::FT
  ];
  protected $filterQueryFtTable = 'search';
  protected $filterQueryFtKeyName = 'post_id';
  protected $filterQueryFtVector = 'data';
}

这些自定义值也可以通过提供“获取”函数动态评估。

class Post
{
  use \VahidIrn\FilterQuery\FilterQueryTrait;
  protected $filterQuery = [
    'body' => FilterQuery::FT
  ];
  public function getFilterQueryFtTable ($field) {
    return 'search';
  };
  public function getFilterQueryFtKeyName ($field) {
    return 'post_id';
  }
  public function getFilterQueryFtVector ($field) {
    return $field;
  }
}