psenna/lumen-query-builder

轻松从API请求构建Eloquent查询

7.0.0 2020-06-05 16:29 UTC

README

Latest Version on Packagist Build Status StyleCI Quality Score Total Downloads

本包允许您根据请求进行过滤、排序和包含Eloquent关系。本包中使用的QueryBuilder扩展了Laravel默认的Eloquent构建器。这意味着您仍然可以使用所有喜欢的方方法和宏。查询参数名称尽可能遵循JSON API规范

基本用法

过滤API请求:/users?filter[name]=John

use Spatie\QueryBuilder\QueryBuilder;

// ...

$users = QueryBuilder::for(User::class)
    ->allowedFilters('name')
    ->get();
// all `User`s that contain the string "John" in their name

从API请求请求关系:/users?include=posts

$users = QueryBuilder::for(User::class)
    ->allowedIncludes('posts')
    ->get();
// all `User`s with their `posts` loaded

与现有查询很好地协同工作

$query = User::where('active', true);

$user = QueryBuilder::for($query)
    ->allowedIncludes('posts', 'permissions')
    ->where('score', '>', 42) // chain on any of Laravel's query builder methods
    ->first();

对API请求进行排序:/users?sort=name

$users = QueryBuilder::for(User::class)->get();
// all `User`s sorted by name

有关高级示例和功能,请参阅下方的用法部分

安装

您可以通过composer安装此包

composer require spatie/laravel-query-builder

您可以选择使用以下命令发布配置文件:

php artisan vendor:publish --provider="Spatie\QueryBuilder\QueryBuilderServiceProvider" --tag="config"

这是已发布的配置文件的内容

return [

    /*
     * By default the package will use the `include`, `filter`, `sort` and `fields` query parameters.
     *
     * Here you can customize those names.
     */
    'parameters' => [
        'include' => 'include',

        'filter' => 'filter',

        'sort' => 'sort',

        'fields' => 'fields',
    ],

];

用法

包含关系

include查询参数将加载结果集合上的任何Eloquent关系。默认情况下,不允许包含。所有包含都必须使用allowedIncludes()指定。

// GET /users?include=posts
$users = QueryBuilder::for(User::class)
    ->allowedIncludes('posts')
    ->get();

// $users will contain all users with their posts loaded

您可以通过逗号分隔来加载多个关系

// GET /users?include=posts,permissions
$users = QueryBuilder::for(User::class)
    ->allowedIncludes('posts', 'permissions')
    ->get();

// $users will contain all users with their posts and permissions loaded

您还可以将包含的数组传递给allowedIncludes()方法。

// GET /users?include=posts,permissions
$users = QueryBuilder::for(User::class)
    ->allowedIncludes(['posts', 'permissions'])
    ->get();

// $users will contain all users with their posts and permissions loaded

您可以使用点.来加载嵌套关系

// GET /users?include=posts.comments,permissions
$users = QueryBuilder::for(User::class)
    ->allowedIncludes('posts.comments', 'permissions')
    ->get();

// $users will contain all users with their posts, comments on their posts and permissions loaded

当尝试使用allowedIncludes()不允许包含关系时,将抛出InvalidIncludeQuery异常。

在查找模型上相应的关联时,关系/包含名称将被转换为camelCase。这意味着/users?include=blog-posts将尝试在User模型上加载blogPosts()关系。

一旦在结果集合上加载了关系,您就可以使用Eloquent API资源和支持条件关系来在响应中包含它们。

过滤

可以使用filter查询参数通过部分属性值、精确属性值或属性值是否存在于给定值数组中来过滤结果。您还可以为更复杂的查询指定自定义过滤器。

默认情况下,不允许过滤。所有过滤器都必须使用allowedFilters()指定。当尝试在未使用allowedFilters()允许的属性上过滤时,将抛出InvalidFilterQuery异常。

// GET /users?filter[name]=john&filter[email]=gmail
$users = QueryBuilder::for(User::class)
    ->allowedFilters('name', 'email')
    ->get();
// $users will contain all users with "john" in their name AND "gmail" in their email address

您还可以将过滤器数组传递给allowedFilters()方法。

// GET /users?filter[name]=john&filter[email]=gmail
$users = QueryBuilder::for(User::class)
    ->allowedFilters(['name', 'email'])
    ->get();
// $users will contain all users with "john" in their name AND "gmail" in their email address

您可以通过传递逗号分隔的值列表来指定多个匹配的过滤器值

// GET /users?filter[name]=seb,freek
$users = QueryBuilder::for(User::class)
    ->allowedFilters('name')
    ->get();
// $users will contain all users that contain "seb" OR "freek" in their name

属性列别名

对于公开用于过滤的属性,这些属性不与数据库列的精确命名共享,这可能很有用。如果您想允许对数据库中可能具有前缀的列进行过滤,可以使用以下表示法。

use Spatie\QueryBuilder\Filter;

// GET /users?filter[name]=John
$users = QueryBuilder::for(User::class)
	->allowedFilters(Filter::exact('name', 'user_name')) // public filter, column name
    ->get();
// filter by the column 'user_name'

精确过滤器

当根据其ID、布尔值或字面字符串过滤模型时,您将想要使用精确过滤器。这样,/users?filter[id]=1不会匹配所有ID中包含数字1的用户。

您可以使用Spatie\QueryBuilder\Filter::exact('property_name')allowedFilters()方法中添加精确过滤器。

use Spatie\QueryBuilder\Filter;

// GET /users?filter[name]=John%20Doe
$users = QueryBuilder::for(User::class)
    ->allowedFilters(Filter::exact('name'))
    ->get();
// all users with the exact name "John Doe"

查询构建器将自动将'true''false'映射为布尔值,并将逗号分隔的值列表映射为数组

use Spatie\QueryBuilder\Filter;

// GET /users?filter[id]=1,2,3,4,5&filter[admin]=true
$users = QueryBuilder::for(User::class)
    ->allowedFilters(Filter::exact('id'), Filter::exact('admin'))
    ->get();
// $users will contain all admin users with id 1, 2, 3, 4 or 5

作用域过滤器

有时您可能想要构建更高级的过滤查询。这时,作用域过滤器(scope filters)和自定义过滤器(custom filters)就派上用场了。

作用域过滤器允许您通过在URL中添加过滤器,轻松地将本地作用域(local scopes)添加到查询中。

考虑以下模型上的作用域

public function scopeStartsBefore(Builder $query, $date): Builder
{
    return $query->where('starts_at', '<=', Carbon::parse($date));
}

要基于startsBefore作用域进行过滤,只需将其添加到查询构建器的allowedFilters中。

QueryBuilder::for(Event::class)
    ->allowedFilters([
        Filter::scope('starts_before'),
    ])
    ->get();

以下过滤器现在将startsBefore作用域添加到底层查询中

GET /events?filter[starts_before]=2018-01-01

您甚至可以通过将逗号分隔的列表传递给过滤器来传递多个参数到作用域

GET /events?filter[starts_between]=2018-01-01,2018-12-31

自定义过滤器

您可以使用Filter::custom()方法指定自定义过滤器。自定义过滤器是简单的可调用类,实现了\Spatie\QueryBuilder\Filters\Filter接口。这样,您可以创建任何您想要查询。

例如

use Spatie\QueryBuilder\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;

class FiltersUserPermission implements Filter
{
    public function __invoke(Builder $query, $value, string $property) : Builder
    {
        return $query->whereHas('permissions', function (Builder $query) use ($value) {
            $query->where('name', $value);
        });
    }
}

use Spatie\QueryBuilder\Filter;

// GET /users?filter[permission]=createPosts
$users = QueryBuilder::for(User::class)
    ->allowedFilters(Filter::custom('permission', FiltersUserPermission::class))
    ->get();
// $users will contain all users that have the `createPosts` permission

忽略过滤器值

您可以为每个过滤器指定一组忽略值。这允许在提交这些值时不对过滤器应用过滤。

QueryBuilder::for(User::class)
    ->allowedFilters(Filter::exact('name')->ignore(null))
    ->get();

ignore()方法接受一个或多个值,每个值可以是一个忽略值的数组。以下所有调用都是有效的

  • ignore('should_be_ignored')
  • ignore(null, '-1')
  • ignore([null, 'ignore_me'], ['also_ignored'])

给定一个要过滤的值的数组,只有非忽略值的子集被传递到过滤器中。如果所有值都被忽略,则不会应用过滤器。

// GET /user?name=forbidden,John Doe
QueryBuilder::for(User::class)
    ->allowedFilters(Filter::exact('name')->ignore('forbidden'))
    ->get();

// Only users where name matches 'John Doe'

// GET /user?name=ignored,ignored_too
QueryBuilder::for(User::class)
    ->allowedFilters(Filter::exact('name')->ignore(['ignored', 'ignored_too']))
    ->get();

// Filter does not get applied

排序

使用sort查询参数来决定结果集合将按哪个属性排序。默认情况下,排序是升序。在属性名称的开头添加一个连字符(-)将反转结果集合。

// GET /users?sort=-name
$users = QueryBuilder::for(User::class)->get();

// $users will be sorted by name and descending (Z -> A)

默认情况下,可以使用所有模型属性来排序结果。但是,您可以使用allowedSorts方法限制在请求中允许使用的属性。

当尝试按未在allowedSorts()中指定的属性排序时,将抛出InvalidSortQuery异常。

// GET /users?sort=password
$users = QueryBuilder::for(User::class)
    ->allowedSorts('name')
    ->get();

// Will throw an `InvalidSortQuery` exception as `password` is not an allowed sorting property

要定义一个默认排序参数,该参数应在请求中不明确添加时应用,您可以使用defaultSort方法。

// GET /users
$users = QueryBuilder::for(User::class)
    ->defaultSort('name')
    ->allowedSorts('name', 'street')
    ->get();

// Will retrieve the users sorted by name

您还可以将排序数组传递给allowedSorts()方法。

// GET /users?sort=name
$users = QueryBuilder::for(User::class)
    ->allowedSorts(['name', 'street'])
    ->get();

// Will retrieve the users sorted by name

您可以通过逗号分隔多个属性来按多个属性排序

// GET /users?sort=name,-street
$users = QueryBuilder::for(User::class)
    ->allowedSorts('name', 'street')
    ->get();

// $users will be sorted by name in ascending order with a secondary sort on street in descending order.

选择特定列

有时您可能只想获取几个字段以减少SQL查询的整体大小。这可以通过使用fields查询参数来完成。以下查询仅获取用户的idname

GET /users?fields[users]=id,name

SQL查询看起来是这样的

SELECT "id", "name" FROM "users"

使用allowedFields方法,您可以限制在请求中允许查询的字段(列)。

当尝试选择未在allowedFields()中指定的列时,将抛出InvalidFieldQuery异常。

$users = QueryBuilder::for(User::class)
    ->allowedFields('name')
    ->get();

// GET /users?fields[users]=email will throw an `InvalidFieldQuery` exception as `email` is not an allowed field.

选择包含模型的字段的工作方式相同。这在您只需要几个列时包含整个关系时尤其有用。考虑以下示例

GET /posts?include=author&fields[author]=name

将获取所有帖子,包括作者的名字。

附加属性

有时您可能想将一些自定义属性附加到来自模型的查询结果中。这可以通过使用append参数来完成。

class User extends Model{

    public function getFullnameAttribute()
    {
        return $this->firstname.' '.$this->lastname;
    }
}
// GET /users?append=fullname

$users = QueryBuilder::for(User::class)
    ->allowedAppends('fullname')
    ->get();

当然,您可以传递要附加的属性列表。

// GET /users?append=fullname,ranking

其他查询方法

由于QueryBuilder扩展了Laravel的默认Eloquent查询构建器,因此您可以使用任何您喜欢的函数或宏。您还可以指定一个基本查询而不是模型FQCN。

QueryBuilder::for(User::where('id', 42)) // base query instead of model
    ->allowedIncludes('posts')
    ->where('activated', true) // chain on any of Laravel's query methods
    ->first(); // we only need one specific user

分页

该软件包不提供任何帮助您分页响应的方法。然而,如上所述,您可以使用Laravel的默认paginate()方法

如果您想完全遵循JSON API规范,也可以使用我们自己的spatie/json-api-paginate

前端构建查询

如果您使用Vue,您可能会对Robson Tenório的vue-api-query包感兴趣。

测试

composer test

变更日志

请参阅CHANGELOG以获取最近更改的更多信息。

贡献

请参阅CONTRIBUTING以获取详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件freek@spatie.be联系我们,而不是使用问题跟踪器。

明信片软件

您可以使用这个包,但如果它进入了您的生产环境,我们非常希望您能从您家乡给我们寄一张明信片,并说明您正在使用我们哪个包。

我们的地址是:Spatie,Samberstraat 69D,2060 安特卫普,比利时。

我们将所有收到的明信片发布在我们的公司网站上

致谢

支持我们

Spatie是一家位于比利时安特卫普的网络设计公司。您可以在我们的网站上找到我们所有开源项目的概述在这里

您的业务依赖于我们的贡献吗?在Patreon上联系我们并支持我们。所有承诺都将专门用于分配人力资源以维护和新奇事物。

许可证

MIT许可证(MIT)。请参阅许可证文件以获取更多信息。