apielf / hyperf-query-builder
Build Hyperf database queries from API requests
Requires
- php: >=8.1
- hyperf/collection: ^3.0
- hyperf/config: ^3.0
- hyperf/database: ^3.0
- hyperf/di: ^3.0
- hyperf/http-server: ^3.0
- hyperf/stringable: ^3.0
README
基于 API 请求构建 Hyperf 数据库查询。这个包允许你基于请求过滤、排序和包含 Eloquent 关联。QueryBuilder
继承了 Hyperf 的默认 Eloquent 构建器,这意味着你所有喜欢的方法和宏仍然可用。查询参数名称尽可能地遵循JSON API 规范。
目录
安装
你可以通过 composer 安装此包:
composer require apielf/hyperf-query-builder
发布配置文件:
php bin/hyperf.php vendor:publish apielf/hyperf-query-builder
配置
配置文件位于config/autoload/query-builder.php
,你可以在其中自定义查询参数名称:
return [
/*
* 默认查询参数名称
*/
'parameters' => [
'include' => 'include',
'filter' => 'filter',
'sort' => 'sort',
'fields' => 'fields',
'append' => 'append',
],
/*
* 是否禁用无效过滤器查询异常
*/
'disable_invalid_filter_query_exception' => false,
];
基本用法
基于请求过滤查询:/users?filter[name]=John
use ApiElf\QueryBuilder\QueryBuilder;
$users = QueryBuilder::for(User::class)
->allowedFilters('name')
->get();
// 所有名称中包含"John"的`User`
基于请求包含关联:/users?include=posts
$users = QueryBuilder::for(User::class)
->allowedIncludes('posts')
->get();
// 所有`User`及其`posts`关联
基于请求排序查询:/users?sort=id
$users = QueryBuilder::for(User::class)
->allowedSorts('id')
->get();
// 所有`User`按id升序排序
选择查询字段:/users?fields[users]=id,email
$users = QueryBuilder::for(User::class)
->allowedFields(['id', 'email'])
->get();
// 获取的`User`只会有id和email字段
过滤
基本过滤
默认情况下,filter
参数将使用部分匹配(LIKE 查询)进行过滤:
// GET /users?filter[name]=John
$users = QueryBuilder::for(User::class)
->allowedFilters('name')
->get();
// 等同于 $query->where('name', 'LIKE', '%John%')
你可以指定多个允许的过滤器:
$users = QueryBuilder::for(User::class)
->allowedFilters('name', 'email', 'age')
->get();
精确过滤
如果你需要精确匹配而不是部分匹配,可以使用exact
过滤器:
use ApiElf\QueryBuilder\AllowedFilter;
// GET /users?filter[email]=john@example.com
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::exact('email'))
->get();
// 等同于 $query->where('email', 'john@example.com')
开头匹配过滤
使用beginsWithStrict
过滤器进行以指定字符串开头的匹配:
// GET /users?filter[username]=admin
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::beginsWithStrict('username'))
->get();
// 等同于 $query->where('username', 'LIKE', 'admin%')
结尾匹配过滤
使用endsWithStrict
过滤器进行以指定字符串结尾的匹配:
// GET /users?filter[email]=@company.com
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::endsWithStrict('email'))
->get();
// 等同于 $query->where('email', 'LIKE', '%@company.com')
操作符过滤
使用operator
过滤器支持各种比较操作符:
use ApiElf\QueryBuilder\Enums\FilterOperator;
// GET /users?filter[age]=25 (大于等于25)
$users = QueryBuilder::for(User::class)
->allowedFilters(
AllowedFilter::operator('age', FilterOperator::GREATER_THAN_OR_EQUAL)
)
->get();
// GET /users?filter[score]=100 (小于100)
$users = QueryBuilder::for(User::class)
->allowedFilters(
AllowedFilter::operator('score', FilterOperator::LESS_THAN)
)
->get();
// 支持的操作符:EQUAL, LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL, NOT_EQUAL
BelongsTo 关联过滤
使用belongsTo
过滤器专门处理 BelongsTo 关联:
// GET /posts?filter[author]=1
$posts = QueryBuilder::for(Post::class)
->allowedFilters(AllowedFilter::belongsTo('author'))
->get();
// 自动处理外键关联
你也可以为过滤器指定一个内部名称,这在字段名称与过滤器名称不同时很有用:
// GET /users?filter[email_address]=john@example.com
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::exact('email_address', 'email'))
->get();
// 等同于 $query->where('email', 'john@example.com')
部分匹配过滤
虽然默认过滤器已经是部分匹配,但你也可以显式使用partial
过滤器:
// GET /users?filter[name]=John
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::partial('name'))
->get();
// 等同于 $query->where('name', 'LIKE', '%John%')
关联表过滤
你可以使用点符号语法过滤关联表中的属性:
// 假设 User 模型有一个 posts 关联
// GET /users?filter[posts.title]=Hyperf
$users = QueryBuilder::for(User::class)
->allowedFilters('posts.title')
->get();
// 等同于
// $query->whereHas('posts', function ($query) {
// $query->where('title', 'LIKE', '%Hyperf%');
// })
你也可以使用精确匹配过滤器:
// GET /users?filter[posts.id]=1
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::exact('posts.id'))
->get();
关联过滤功能支持多层嵌套关联:
// GET /users?filter[posts.comments.author.name]=John
$users = QueryBuilder::for(User::class)
->allowedFilters('posts.comments.author.name')
->get();
如果你不想允许关联过滤,可以在创建过滤器时禁用它:
$users = QueryBuilder::for(User::class)
->allowedFilters(
AllowedFilter::exact('name', null, null, false, false)
)
->get();
范围过滤
你可以使用模型上定义的查询范围进行过滤:
// 在User模型中定义范围
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
// GET /users?filter[popular]=true
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::scope('popular'))
->get();
自定义过滤器
你可以使用回调函数创建完全自定义的过滤器:
// GET /users?filter[name]=John
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::callback('name', function ($query, $value) {
$query->where('first_name', 'LIKE', "%{$value}%")
->orWhere('last_name', 'LIKE', "%{$value}%");
}))
->get();
软删除过滤
如果你的模型使用了软删除,你可以使用trashed
过滤器:
// GET /users?filter[trashed]=with
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::trashed())
->get();
// 可能的值:
// 'with': withTrashed()
// 'only': onlyTrashed()
// 'without': withoutTrashed()
默认过滤器
你可以为过滤器设置默认值,当请求中没有提供该过滤器时使用:
// 当请求中没有filter[name]参数时,默认过滤name为'John'
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::partial('name')->default('John'))
->get();
忽略特定值
你可以配置过滤器忽略特定值:
// 忽略空字符串和null值
$users = QueryBuilder::for(User::class)
->allowedFilters(AllowedFilter::exact('name')->ignore(['', null]))
->get();
排序
基本排序
你可以使用sort
参数对结果进行排序:
// GET /users?sort=name
$users = QueryBuilder::for(User::class)
->allowedSorts('name')
->get();
// 升序排序
// GET /users?sort=-name
$users = QueryBuilder::for(User::class)
->allowedSorts('name')
->get();
// 降序排序(注意前缀'-')
你可以指定多个允许的排序字段:
$users = QueryBuilder::for(User::class)
->allowedSorts('name', 'email', 'created_at')
->get();
默认排序
当请求中没有指定排序参数时,你可以设置默认排序:
// 默认按创建时间降序排序
$users = QueryBuilder::for(User::class)
->allowedSorts('name', 'email', 'created_at')
->defaultSort('-created_at')
->get();
你也可以使用AllowedSort
对象设置默认排序:
use ApiElf\QueryBuilder\AllowedSort;
// 默认按创建时间降序排序
$users = QueryBuilder::for(User::class)
->allowedSorts('name', 'email', 'created_at')
->defaultSort(
AllowedSort::field('created_at')
->defaultDirection(AllowedSort::DESCENDING)
)
->get();
你可以设置多个默认排序:
// 默认先按状态升序,再按创建时间降序排序
$users = QueryBuilder::for(User::class)
->allowedSorts('name', 'email', 'status', 'created_at')
->defaultSorts('status', '-created_at')
->get();
自定义排序
你可以使用自定义排序逻辑:
use ApiElf\QueryBuilder\AllowedSort;
use ApiElf\QueryBuilder\Sorts\Sort;
class RandomSort implements Sort
{
public function __invoke(QueryBuilder $query, bool $descending, string $property)
{
$query->inRandomOrder();
}
}
// GET /users?sort=random
$users = QueryBuilder::for(User::class)
->allowedSorts(AllowedSort::custom('random', new RandomSort()))
->get();
你也可以使用回调函数创建自定义排序:
use ApiElf\QueryBuilder\AllowedSort;
// GET /users?sort=full_name
$users = QueryBuilder::for(User::class)
->allowedSorts(
AllowedSort::callback('full_name', function ($query, $descending, $property) {
$direction = $descending ? 'desc' : 'asc';
$query->orderBy('first_name', $direction)
->orderBy('last_name', $direction);
})
)
->get();
包含关联
基本关联
你可以使用include
参数加载关联:
// GET /users?include=posts
$users = QueryBuilder::for(User::class)
->allowedIncludes('posts')
->get();
// 等同于 $query->with('posts')
你可以指定多个允许的关联:
$users = QueryBuilder::for(User::class)
->allowedIncludes('posts', 'permissions', 'roles')
->get();
嵌套关联
你可以使用点符号包含嵌套关联:
// GET /users?include=posts.comments
$users = QueryBuilder::for(User::class)
->allowedIncludes('posts.comments')
->get();
// 等同于 $query->with('posts.comments')
关联计数
你可以使用count
方法包含关联计数:
use ApiElf\QueryBuilder\AllowedInclude;
// GET /users?include=posts_count
$users = QueryBuilder::for(User::class)
->allowedIncludes(AllowedInclude::count('posts_count', 'posts'))
->get();
// 等同于 $query->withCount('posts')
自定义关联
你可以创建自定义的关联包含逻辑:
use ApiElf\QueryBuilder\AllowedInclude;
use ApiElf\QueryBuilder\Includes\IncludeInterface;
class LatestPostsInclude implements IncludeInterface
{
public function __invoke(QueryBuilder $query)
{
$query->with(['posts' => function ($query) {
$query->latest()->limit(5);
}]);
}
}
// GET /users?include=latest_posts
$users = QueryBuilder::for(User::class)
->allowedIncludes(AllowedInclude::custom('latest_posts', new LatestPostsInclude()))
->get();
选择字段
你可以使用fields
参数选择要返回的字段:
// GET /users?fields[users]=id,name,email
$users = QueryBuilder::for(User::class)
->allowedFields(['id', 'name', 'email'])
->get();
// 只返回id、name和email字段
你也可以为关联选择字段:
// GET /users?fields[users]=id,name&fields[posts]=title,content
$users = QueryBuilder::for(User::class)
->allowedFields(['id', 'name', 'posts.title', 'posts.content'])
->allowedIncludes('posts')
->get();
追加属性
你可以使用append
参数追加模型访问器:
// 在User模型中定义访问器
public function getFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
// GET /users?append=full_name
$users = QueryBuilder::for(User::class)
->allowedAppends('full_name')
->get();
分页
QueryBuilder 与 Hyperf 的分页功能完全兼容:
// GET /users?page=2&page_size=10
$users = QueryBuilder::for(User::class)
->paginate($request->input('page_size', 15));
// 或者使用简单分页
$users = QueryBuilder::for(User::class)
->simplePaginate($request->input('page_size', 15));
高级用法
与现有查询结合
你可以将 QueryBuilder 与现有查询结合使用:
$query = User::where('active', true);
$users = QueryBuilder::for($query) // 从现有的Builder实例开始
->withTrashed() // 使用你现有的作用域
->allowedIncludes('posts', 'permissions')
->where('score', '>', 42) // 链接任何Hyperf查询构建器方法
->get();
条件查询
你可以根据条件添加过滤器、排序和包含:
$users = QueryBuilder::for(User::class)
->when($request->has('filter_active'), function ($query) {
$query->allowedFilters('active');
})
->when($request->has('include_posts'), function ($query) {
$query->allowedIncludes('posts');
})
->get();
异常处理
默认情况下,当请求包含未允许的过滤器、排序或包含时,QueryBuilder 会抛出异常。你可以在配置文件中禁用这些异常:
// config/autoload/query-builder.php
return [
// ...
'disable_invalid_filter_query_exception' => true,
];
或者你可以在运行时禁用它们:
$users = QueryBuilder::for(User::class)
->allowedFilters('name')
->allowedSorts('name')
->allowedIncludes('posts')
->disableInvalidFilterQuery()
->get();
最佳实践
在控制器中使用
class UserController extends AbstractController
{
public function index()
{
return QueryBuilder::for(User::class)
->allowedFilters(['name', 'email', AllowedFilter::exact('type')])
->allowedSorts(['name', 'created_at'])
->allowedIncludes(['posts', 'permissions'])
->allowedFields(['id', 'name', 'email', 'created_at'])
->paginate()
->toArray();
}
}
创建专用的查询构建器类
对于复杂的查询,你可以创建专用的查询构建器类:
class UserQueryBuilder extends QueryBuilder
{
public function __construct()
{
parent::__construct(User::query());
$this->allowedFilters(['name', 'email', AllowedFilter::exact('type')])
->allowedSorts(['name', 'created_at'])
->allowedIncludes(['posts', 'permissions'])
->allowedFields(['id', 'name', 'email', 'created_at']);
}
}
// 在控制器中使用
public function index()
{
return (new UserQueryBuilder())->paginate();
}
所有功能未完全经过测试,如果遇到 bug 请到 github 中提交 issues
致谢
这个包是基于spatie/laravel-query-builder改写的,感谢原作者的出色工作。