mblarsen / laravel-repository
增强查询构建器和仓库功能,以减少样板代码并使控制器保持精简。
Requires
- php: ^7.3
- illuminate/support: ^7.0
Requires (Dev)
- orchestra/testbench: ^5.0
- phpunit/phpunit: ^8.0
README
增强查询构建器和仓库功能,以减少样板代码并使控制器保持精简
特性
- 零配置开始。 查看基本示例
- 常用功能:
all
、find
、create
、update
、destroy
。 - 加上
allResources
、findResource
等用于资源包装。 list()
函数对<select>
和自动完成很有用。- 前端和用户驱动。请求是包含内容的上下文。
- 在模型和关系上过滤查询
- 包含关系(默认全部阻止)
- 透明地处理分页
- 按模型的属性或其关系的属性对模型进行排序(无需自定义 SQL)
- 100% 测试覆盖率
此包的目标
- 避免分页、过滤和排序资源的样板代码
- 以安全的方式让客户端决定要获取的内容
- 将控制器逻辑与模型逻辑分离
- 让你能够控制特殊情况的查询
实际上,仓库类是查询构建器和仓库的结合,重点在检索上。
此包包括一个接口和三个类,供你构建
Repository
类,可以直接使用或扩展以满足你的模型需求。ResourceContext
接口,为仓库提供数据。RequestResourceContext
,从传入的请求对象中提取数据。ArrayResourceContext
,你提供数据。适用于测试。
安装
您可以通过 composer 安装此包
composer require mblarsen/laravel-repository
仓库依赖于一个 ResourceContext
来提供必要的值,以便能够排序、过滤、分页等。这是自动处理的,但这意味着你应该让 Laravel 的依赖注入为你提供仓库。如果你扩展了基本仓库类,这将特别强大。
默认使用的上下文是 RequestResourceContext
,它自动注入到仓库中。这特别适用于构建用于服务于前端的开源或私有 API。然而,还提供了一个允许你提供更高控制度的实现。这是 ArrayResourceContext
。此实现适用于测试或当你构建使用 Blade 视图的传统 Laravel 应用程序时。
以下示例主要针对 RequestResourceContext
的使用。
基本示例
基本仓库对您的模型一无所知,因此除非您子类化仓库,否则您必须指定您正在查询的模型。
// Using Laravel's resolve() helper $repository = resolve(Repository::class)->setModel(Post::class); // Using static factory $repository = Repository::for(Post::class);
在控制器中使用时,建议您让 Laravel 帮您处理
public function index(Repository $repository) { return $repository->setModel(Post::class)->all(); }
... 或者在自定义仓库的情况下
public function index(PostRepository $repository) { return $repository->all(); }
现在结果将根据请求自动 排序、过滤 和 分页。
您可以提供的示例请求
/posts?sort_by=created_at&page=2&filters[title]=laravel
也就是说
/posts
,请求帖子sort_by=created_at
,按 created_at 排序sort_by=desc
,按降序排序(默认:asc
)page=2
,分页结果,请求第 2 页(默认:null
表示不分页)filters[title]=laravel
在帖子名称中搜索标题
您也可以通过关系进行过滤。例如 filters[address.zip]=1227
。
由于默认情况下不允许关系,所以没有请求要包含的关系将被忽略。但一旦我们设置好,你也将能够请求关系。
with[]=ads&with[]=comments
,将包含关系ads
和comments
。
仓库功能
您可以使用链式API来定义仓库的行为。请确保查看下面的完整API。
控制要包含哪些关系
查询:with
或with[]
根据允许的内容,仓库将包含在with
中指定的任何关系。这确保了客户端不能请求您未允许的数据。
$repository->setAllowedWith(['comments']);
有时您想默认包含某些关系。除了直接在模型上按照Laravel的方式操作外,您还可以在仓库上设置。这允许您控制控制器中每个动作的关系列表。
$relation->setDefaultWith(['comments']);
旁白:如果您正在构建公开API(用于您的SPA或第三方),建议您将结果包装在JsonResource
中。这将让您控制公开哪些属性,并允许您进一步转换数据。请参阅资源部分。
过滤
查询:filter[key]=value
此包通过其过滤器提供类似搜索的功能。底层使用LIKE
,即%value%
。
如果您想要精确匹配,即=
,则可以在键名末尾添加一个感叹号。
键不必是主模型的属性。它也可以是关系属性。以下是一些示例
// Search on model property title=cra // Search on model property exact match code_name!=wowcrab // Seacch on relation property address.city=mass
您可以在搜索中组合属性
// Search in full name
first_name+middle_name+last_name=cra
最后,您可以选择在多个属性中搜索相同的值
// Search in both title, name, and email
title|name|email=cra
另一种过滤方式是向all()
和find()
提供查询构建器。请参阅API中的示例。
转换RequestResourceContext
您不能直接修改请求上下文,但您可以选择将其转换为ArrayResourceContext
。
public function index(UserRepository $repository) { $repository->setContext( ArrayResourceContext::create( $repository->getContext()->toArray() ) ->merge([ 'filters' => ['status' => 'approved'] ]) ->exclude(['search_by']) ); }
自定义仓库
您的许多模型可能不需要自定义(子类化)仓库。但通常您的核心模型与它们相关的逻辑更多。在这种情况下,建议扩展基本仓库。
所有属性(除了model
)都可以省略。好吧,您也可以省略模型,但这有点没有意义。
免责声明:示例的目的是演示灵活性,而不是非常现实。
class PostRepository extends Repository { // We serve you Posts protected $model = Post::class; // The client can request to include the following relations protected $allowed_with = ['ads', 'comments']; // However, we will include these automatically protected $default_with = ['comments']; // We change the default sort key to created_at ... protected $default_sort_by = 'created_at'; // ... in descending order protected $default_sort_order = 'desc'; // We override modelQuery to ensure only // published posts will be returned protected function modelQuery($query = null) { // It is perfectly okay to not invoke the parent. // It simly defaults to an empty query of the current // model. These are identical: // // $query = parent::modelQuery($query); // $query = $query ?: Post::query() $query = parent::modelQuery($query); $query->whereNotNul('published_at'); return $query; } }
您可以用基本仓库实现相同的功能,但当然,每次都需要重复设置。
public function index(Repository $repository) { $only_published = Post::query()->whereNotNul('published_at'); $repository ->setModel(Post::class) ->setDefaulSort('created_at', 'desc') ->setAllowedWith(['ads', 'comments']) ->setDefaultWith(['comments']) ; return $repository->all($only_published); }
与...
public function index(PostRepository $repository) { return $repository->all(); }
资源
如果您正在构建公开API(用于您的SPA或第三方),建议您将结果包装在JsonResource
中。这将让您控制公开哪些属性,并允许您进一步转换数据。
做起来非常简单。
// using base repository $repository->setResource(UserResource::class); // or extending protected $resource = UserResource::class;
然后您只需使用任何API方法,如这里所示,在前面加上Resource
或Resources
$repository->allResources(); $repository->findResource(3); $repository->createResource(['name' => 'foo']); $repository->updateResource($user, ['name' => 'foo']);
如果您实现了一个集合资源,可以将它作为setResource
方法调用中的第二个参数
$repository->setResource(UserResource::class, UserResourceCollection::class); // or in class protected $resource_collection = UserResourceCollection::class
注意:list()
不支持资源。这对我来说没有意义。欢迎改变我的看法。
自定义RequestResourceContext
RequestResourceContext
在传入请求中默认寻找的值是
过滤器
页面
每页
排序
排序顺序
包含
如果您不喜欢这些或由于某些原因无法使用它们,您可以选择使用上下文中的mapKeys()
提供自己的键。 mapKeys
允许您将一个或多个键映射到您首选的方案。
到目前为止,最简单的方法是在您的AppServiceProvider
中添加以下代码片段。
public function register() { $this->app->resolving(RequestResourceContext::class, function ($context) { $context->mapKeys([ 'filters' => 'filter' 'sort_by' => 'order_by', 'sort_order' => 'direction', 'per_page' => 'take', ]); // or if you simply prefer camelCase $context->mapKeys([ 'sort_by' => 'sortBy', 'sort_order' => 'sortOrder', 'per_page' => 'perPage', ]); }); }
所以这将是旧的方式
/posts?sort_by=title&per_page=10&with=comments
您的客户端现在应使用此方案
/posts?sortBy=title&perPage=10&with=comments
测试
在测试 ArrayResourceContext
时,它会派上用场。它允许您直接传递上下文值。
$repository = Repository::for(User::class, ArrayResourceContext::create([ 'filters' => [ 'status' => 'approved', ] ])); // or shorter $repository = Repository::for(User::class, [ 'filters' => [ 'status' => 'approved', ] ]);
API
all($query = null)
list(string|callable $column, $query = null)
find($id, $query = null)
create(array $data)
update(Model $model, array $data)
destroy(Model $model)
setContext(ResourceContext|array $resource_context)
setModel(string $model)
setAllowedWith(array $allowed)
setDefaultSort(string $by, string $order = 'asc')
setDefaultWith(array $with)
shouldAuthorize(bool $value = true)
当扩展基本仓库时,您可能希望查看这些附加功能
all($query = null)
allQuery($query = null)
allResources($query = null)
根据当前资源上下文返回所有模型。
public function index(UserRepository $user_repository) { // To get only users with status 'pending' $query = User::where('status', 'pending'); $users = $user_repository->all($query); // Of course in this simple case we could have achived the same using a // filter: GET /users?filters[status]=pending }
使用 Query
后缀作为查询构建器返回。
使用 Resources
后缀作为资源集合返回。
list(string|callable $column = null, $query = null)
listQuery(string|callable $column = null, $query = null)
产生适合选择、列表和自动完成的结果。所有条目都包含 'value' 和 'label' 键。
如果省略 $column
,则使用默认排序。在许多情况下,它们可能是一样的。
使用 Query
后缀作为查询构建器返回。
注意:如果使用可调用函数,映射将在内存中执行,而字符串将在数据库层执行。另外,请注意,在使用 listQuery 变体时,可调用函数不会反映。
find($id, $query = null)
findQuery($id, $query = null)
findResource($id, $query = null)
获取单个模型。您可以通过提供一个起始查询来进一步缩小结果。请参阅 all()
中的示例。
使用 Query
后缀作为查询构建器返回。
使用 Resource
后缀作为资源返回。
create(array $data)
createResource(array $data)
典型的 crUd。使用 Resource
后缀作为资源返回。
update(Model $model, array $data)
典型的 crUd。
使用 Resource
后缀作为资源返回。
destroy(Model $model)
典型的 cruD。
setContext(ResourceContext|array $resource_context)
此方法允许您在创建仓库后设置或更改上下文。
setModel(string $model)
请参阅示例代码。
setAllowedWith(array $allowed)
请参阅示例代码。
setDefaultSort(string $by, string $order = 'asc')
setDefaultWith(array $with)
请参阅示例代码。
modelQuery($query = null)
请参阅示例代码。
register()
在创建仓库时调用。这对于为子类仓库设置此 default_list_column
函数很有用。
protected function register() { $this->default_list_column = function ($model) { return $model->full_name; }; }
shouldAuthorize(bool $value = true)
当设置为 true 时,在执行任何查询之前执行授权。
示例
Repository::for(Post::class) ->shouldAuthorize() ->update($post, ['author' => 'me');
interface ResourceContext
请参阅 ResourceContext 实现。
变更日志
请参阅 CHANGELOG 了解最近更改的更多信息。
安全
如果您发现任何与安全相关的问题,请通过电子邮件 m19n@pm.me 而不是使用问题跟踪器。
先前工作
致谢
许可协议
MIT 许可协议 (MIT)。有关更多信息,请参阅 许可文件。
Laravel 包模板
此包是使用 Laravel 包模板 生成的。
贡献
请参阅 CONTRIBUTING 了解详细信息。
请参阅 DEVELOPING 了解入门帮助。
贡献者 ✨
感谢以下这些出色的人们 (emoji key)
此项目遵循 all-contributors 规范。欢迎任何形式的贡献!