liqueurdetoile / cakephp-apie
这个 CakePHP 插件允许在控制器中轻松处理 API 调用,并支持扩展语法,通过 URL 参数控制数据查询
Requires
- php: ^7.2|^8.0
- cakephp/cakephp: ^4|^5
Requires (Dev)
- cakedc/cakephp-phpstan: ^2.0
- cakephp/cakephp-codesniffer: ^4.5
- cakephp/migrations: ^2.4|^3.2
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^0.10|^1.8
- phpstan/phpstan-phpunit: ^1.2
- phpunit/phpunit: ^6|^8.5|^9.3
- vierge-noire/cakephp-fixture-factories: ^2.7
README
Cake as a Pie
这是 CakePHP 4.x/5.x 分支的插件。
该插件的主要思想是通过远程利用 CakePHP 查询构建器的功能,通过传入的 URL 参数配置高级数据查询。除了分页外,CakePHP 框架缺乏快速实现这些目标的功能。该插件本身对端点路由逻辑相当中立。
快速概述
假设你想获取 2020 年写的一篇关于某个作者的文章的列表,并根据其名称和编写日期进行排序。这是你可以在端点中不使用插件时可能采取的做法 不使用 插件
// In your index method of your ArticlesController endpoint // URL may be /api/v1/articles?author_name=smith&from=2020-01-01&to=2020-12-31 in a REST context for instance $name = $this->request->getQuery('author_name'); $from = $this->request->getQuery('from'); $to = $this->request->getQuery('to'); $query = $this->Articles ->find() ->innerJoinWith('Authors', function($q) use ($name) { return $q->where(['Authors.name' => $name]); }) ->where(function ($exp) { return $exp->between('written', $from, $to); }) ->order(['written' => 'ASC']); $this->set('articles', $this->paginate($query));
很好,但为了提供额外的功能,您必须手动处理每个端点的路由和/或参数。尽管在某些情况下出于安全原因可能需要这样做,但大多数时候,能够从客户端精炼查询将非常出色。使用此插件,您可以简单地做到这一点
// In your index method of your ArticlesController endpoint // URL may be /api/v1/articles?q=%7B%22innerJoinWith%22%3A%7B%22Authors%22%3A%7B%22where%22%3A%7B%22Authors.name%22%3A%22Smith%22%7D%7D%7D%2C%22where%22%3A%7B%22between%22%3A%5B%22written%22%2C%222020-01-01%22%2C%222020-12-31%22%5D%7D%2C%22order%22%3A%7B%22written%22%3A%22ASC%22%7D%7D $query = $this->Api ->use($this->Articles) ->allow(['Authors']) // This is needed to allow access to Authors model from Articles endpoint side ->find(); $this->set('articles', $this->paginate($query));
当 URL 和 json 解码后,q
查询参数变成了这个 PHP 数组,它是查询描述符
[ 'innerJoinWith' => ['Authors', "()" => ['where' => [['Author.name' => 'Smith']]]], 'where' => ["()" => ['between' => ['written','2020-01-01','2020-12-31']]], 'order' => [['written' => 'ASC']], ]
在 JSON 格式下可能更全面
{ "innerJoinWith": { "0": "Authors", "()": { "where": [{ "Author.name": "Smith" }] } }, "where": { "()": { "between": ["written", "2020-01-01", "2020-12-31"] } }, "order": [{ "written": "ASC" }] }
依靠查询构建器的链式调用和提供回调的能力,您可以通过这种方式远程配置大量高级查询。有关构建可用的 查询描述符 的更多信息,请参见下文。有关可用功能的更多信息,请参阅 CakePHP 查询构建器。
安装
插件通过 composer 提供
composer require liqueurdetoile/cakephp-apie
该插件中只有一个组件,因此您不需要在引导步骤中显式添加插件。
在将使用组件的控制器中,只需在 initialize
钩子期间加载它
// In your controller public function initialize(): void { parent::initialize(); $this->loadComponent('Lqdt/CakephpApie.Api'); } // And use it through Api method public function index() { $articles = $this->Api ->use('Articles') ->find() ->all() // ... }
组件选项
在初始化组件时,您可以通过提交映射的默认值永久更改使用的查询参数的名称
// In your controller $this->loadComponent('Lqdt/CakephpApie.Api', [ 'q' => 'whatever', // Permanently remap the query parameter monitored in URL to this value 'allowAll' => true, // Allow any associated data to be requested. Not recommended unless you know what you're doing ]);
使用此配置,组件现在将查找 whatever
查询参数以找到查询描述符,而不会在请求关联数据时进行检查。
组件方法
ApiComponent::use(\Cake\ORM\Table\|string $model) : self
指示组件使用给定的表作为请求的基础。您可以选择提供 Table
实例或其在 TableRegistry
中的名称。
ApiComponent::setQueryParam(string $name) : self
组件将尝试从查询字符串中的 $name
键中查找查询描述符,而不是默认的 q
键。
ApiComponent::allow(string|string[] $associations) : self
配置组件以允许使用给定的关联名称。允许点路径。
提示:允许嵌套关联将自动允许路径中的所有中间关联
ApiComponent::allowAll() : self
禁用当前请求的关联检查。
提示:允许获取任何关联数据在大多数情况下可能是一个非常糟糕的想法
ApiComponent::find() : \Cake\ORM\Query
根据URL中提供的描述符初始化查询。它接受与任何常规 find
调用相同的参数。
ApiComponent::configure(\Cake\ORM\Query $query, array $descriptor) : \Cake\ORM\Query
返回一个查询对象,其子句已根据描述符初始化。
提示 如果您不打算使用URL查询参数作为描述符源,则可以使用此功能。
查询描述符语法
查询描述符是一个数组,用于描述如何配置查询。可以查询上可用的任何可调用函数。
一个非常基本的描述符看起来像
[ "query_method1" => [/** arguments */] "query_method2" => [/** arguments */] ] // This will be used this way : call_user_func_array([$query, "query-method1"], [/** arguments */]; call_user_func_array([$query, "query-method2"], [/** arguments */];
插件期望提供的参数可以直接用于 call_user_func_array
,因此它们必须被包裹在一个数组中。
如果您需要多次调用相同的方法,只需按需添加 +
。它们将在解析过程中被修剪。
[ "query_method" => [/** arguments */] "+query_method" => [/** arguments */] "++query_method" => [/** arguments */] ] // This will be used this way : call_user_func_array([$query, "query-method"], [/** arguments */]; call_user_func_array([$query, "query-method"], [/** arguments */]; call_user_func_array([$query, "query-method"], [/** arguments */];
查询表达式
您可以使用 newExpr()
特殊键告诉插件您愿意使用查询表达式。
[ 'where' => [ 'newExpr()' => [ 'between' => ['date', '2020-01-01', '2020-12-31'] ] ] ]; // turns into call_user_func_array([$query, 'where'], [ call_user_func_array([$query->newExpr(), 'between'], ['date', '2020-01-01', '2020-12-31']) ]);
SQL 函数
您还可以使用 func()
特殊键使用SQL函数。
[ 'select' => [ ['count' => [ 'func()' => [ 'count' => ['*'], ], ]], ], ]; // turns into call_user_func_array([$query, 'select'], [ call_user_func_array([$query->func(), 'count'], ['*']) ]);
闭包
最后,您可以使用 ()
特殊键告诉插件您想使用闭包。对于关联闭包之外,查询表达式将可用并用于处理嵌套描述符。
[ 'where' => [ '()' => [ 'between' => ['date', '2020-01-01', '2020-12-31'] ] ] ]; // turns into call_user_func_array([$query, 'where', [ function (QueryExpression, $e) { call_user_func_array([$e, 'between'], ['date', '2020-01-01', '2020-12-31']); return $e; } ]]);
对于关联,插件将嵌套描述符应用于闭包中的子查询。
[ 'contain' => [ 'Childs', '()' => [ 'where' => [['Childs.is_great' => true]] ] ] ]; // turns into call_user_func_array([$query, 'contain', [ 'Childs', function (Query $q) { call_user_func_array([$q, 'where'], [['Childs.is_great' => true]]); return $q; } ]]);