liqueurdetoile/cakephp-apie

这个 CakePHP 插件允许在控制器中轻松处理 API 调用,并支持扩展语法,通过 URL 参数控制数据查询

1.1.2 2024-04-12 14:17 UTC

This package is auto-updated.

Last update: 2024-09-12 15:13:45 UTC


README

CI Coverage Status License: MIT

Liqueur de Toile

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;
    }
]]);