icings / partitionable
CakePHP ORM 的可分区关联,允许对每组进行基本限制。
Requires
- php: >=8.1
- cakephp/orm: ^5.0
Requires (Dev)
- cakephp/cakephp: ^5.0
- cakephp/cakephp-codesniffer: ^5.0
- phpunit/phpunit: ^10.1
README
一组针对 CakePHP ORM 的可分区关联,允许对每组进行基本限制。
要求
- CakePHP ORM 5.0+(如果您需要 CakePHP 4 兼容性,请使用此插件的 1.x 分支)。
- 支持窗口函数的 CakePHP 支持的 DBMS(MySQL 8,MariaDB 10.2,Postgres 9.4,SQL Server 2017,Sqlite 3.25)。
安装
使用 Composer 将库添加到您的项目中
composer require icings/partitionable
我不明白,这究竟有什么好处?
这些关联究竟有什么好处,"每组限制"到底是指什么?
基本上,此库中提供的关联允许对 hasMany
和 belongsToMany
类型的关联应用限制,例如,对于 Articles hasMany Comments
关联,可以接收每篇文章最多 n 个评论。
使用
请务必先查看 已知问题/限制 部分!
然后将 \Icings\Partitionable\ORM\AssociationsTrait
特性添加到您的表类中,使用其 partitionableHasMany()
和 partitionableBelongsToMany()
方法添加 hasMany
和 belongsToMany
关联,配置限制和排序顺序,然后完成最小设置,您可以像处理其他关联一样处理可分区的关联。
请注意,配置排序顺序是强制性的,因为没有明确的排序顺序,无法可靠地对结果进行分区,省略它将导致错误!
Has Many
// ... use Icings\Partitionable\ORM\AssociationsTrait; class ArticlesTable extends \Cake\ORM\Table { use AssociationsTrait; public function initialize(array $config): void { // ... $this ->partitionableHasMany('TopComments') ->setClassName('Comments') ->setLimit(3) ->setSort([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]); } }
$articlesQuery = $this->Articles ->find() ->contain('TopComments');
这将查询每篇文章最高票的 3 个评论,例如,结果看起来可能像这样
[ 'title' => 'Some Article', 'top_comments' => [ [ 'votes' => 10, 'body' => 'Some Comment', ], [ 'votes' => 9, 'body' => 'Some Other Comment', ], [ 'votes' => 8, 'body' => 'And Yet Another Comment', ], ], ]
Belongs To Many
// ... use Icings\Partitionable\ORM\AssociationsTrait; class StudentsTable extends \Cake\ORM\Table { use AssociationsTrait; public function initialize(array $config): void { // ... $this ->partitionableBelongsToMany('TopGraduatedCourses') ->setClassName('Courses') ->setThrough('CourseMemberships') ->setLimit(3) ->setSort([ 'CourseMemberships.grade' => 'ASC', 'CourseMemberships.id' => 'ASC', ]) ->setConditions([ 'CourseMemberships.grade IS NOT' => null, ]); } }
$studentsQuery = $this->Students ->find() ->contain('TopGraduatedCourses');
这将查询每个学生最高毕业的 3 门课程,例如,结果看起来可能像这样
[ 'name' => 'Some Student', 'top_graduated_courses' => [ [ 'name' => 'Some Course', '_joinData' => [ 'grade' => 1, ], ], [ 'body' => 'Some Other Course', '_joinData' => [ 'grade' => 2, ], ], [ 'body' => 'And Yet Another Course', '_joinData' => [ 'grade' => 3, ], ], ], ]
使用选项配置关联
除了链式方法调用语法外,还支持从内置关联中了解的选项,具体支持以下选项用于 partitionableHasMany()
和 partitionableBelongsToMany()
limit
(int|null
)singleResult
(bool
)filterStrategy
(string
)
$this ->partitionableHasMany('TopComments', [ 'className' => 'Comments', 'limit' => 1, 'singleResult' => false, 'filterStrategy' => \Icings\Partitionable\ORM\Association\PartitionableHasMany::FILTER_IN_SUBQUERY_TABLE, 'sort' => [ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ], ]);
动态更改设置
限制和排序顺序可以在包含查询构建器中动态应用/更改
$articlesQuery = $this->Articles ->find() ->contain('TopComments', function (\Cake\ORM\Query\SelectQuery $query) { return $query ->limit(10) ->order([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]); });
以及通过 Model.beforeFind
,其中需要修改的可分区的获取器查询可以通过选项 partitionableQueryType
识别,它将保留值 fetcher
$this->Articles->TopComments ->getEventManager() ->on('Model.beforeFind', function ($event, \Cake\ORM\Query\SelectQuery $query, \ArrayObject $options) { if (($options['partitionableQueryType'] ?? null) === 'fetcher') { $query ->limit(10) ->order([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]); } return $query; });
限制为单个结果
当将限制设置为 1
时,关联将自动切换到使用单个属性名(如果尚未设置属性名),以及非嵌套结果。
例如,将此关联限制为 1
$this ->partitionableHasMany('TopComments') ->setClassName('Comments') ->setLimit(1) ->setSort([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]);
将返回这样的结果
[ 'title' => 'Some Article', 'top_comment' => [ 'votes' => 10, 'body' => 'Some Comment', ], ]
而大于或等于 2
的限制将返回这样的结果
[ 'title' => 'Some Article', 'top_comments' => [ [ 'votes' => 10, 'body' => 'Some Comment', ], [ 'votes' => 5, 'body' => 'Some Other Comment', ], ], ]
此行为可以使用关联的 disableSingleResult()
方法来禁用,同样也可以使用 enableSingleResult()
来启用。调用后者还会将限制设置为 1
。此外,将限制设置为大于或等于 2
,将自动禁用单结果模式。
单结果模式禁用后
$this ->partitionableHasMany('TopComments') ->setClassName('Comments') ->setLimit(1) ->disableSingleResult() ->setSort([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]);
限制为 1
将返回类似这样的结果
[ 'title' => 'Some Article', 'top_comments' => [ [ 'votes' => 10, 'body' => 'Some Comment', ], ], ]
过滤策略
当前提供的关联有一些不同的过滤策略,这些策略会影响获取关联数据时的查询过滤方式。
并非所有查询都相同,某种策略可能适用于一个查询,但可能对另一个查询造成问题。
可以使用关联的 setFilterStrategy()
方法来设置策略
use Icings\Partitionable\ORM\Association\PartitionableHasMany; // ... $this ->partitionableHasMany('TopComments') ->setClassName('Comments') ->setFilterStrategy(PartitionableHasMany::FILTER_IN_SUBQUERY_TABLE) ->setLimit(3) ->setSort([ 'TopComments.votes' => 'DESC', 'TopComments.id' => 'ASC', ]);
请参阅API文档,了解不同策略的SQL示例
\Icings\Partitionable\ORM\Association\Loader\PartitionableSelectLoader
\Icings\Partitionable\ORM\Association\Loader\PartitionableSelectWithPivotLoader
目前可用的策略有
\Icings\Partitionable\ORM\Association\PartitionableAssociationInterface::FILTER_IN_SUBQUERY_CTE
\Icings\Partitionable\ORM\Association\PartitionableAssociationInterface::FILTER_IN_SUBQUERY_JOIN
\Icings\Partitionable\ORM\Association\PartitionableAssociationInterface::FILTER_IN_SUBQUERY_TABLE
(默认)\Icings\Partitionable\ORM\Association\PartitionableAssociationInterface::FILTER_INNER_JOIN_CTE
\Icings\Partitionable\ORM\Association\PartitionableAssociationInterface::FILTER_INNER_JOIN_SUBQUERY
已知问题/限制
-
这些关联不适用于保存或删除操作,仅适用于读取操作!
-
MySQL 5不受支持,因为它不支持用于行编号的必需的窗口函数。虽然可以模拟所需的行编号,但这些结构相当脆弱,并且存在许多情况会导致它们崩溃,从而产生错误结果。
-
当使用具有
onlyTranslated
启用的翻译行为时,MariaDB >= 11.0 可能会因FILTER_IN_SUBQUERY_CTE
和默认的FILTER_IN_SUBQUERY_TABLE
策略而崩溃(https://jira.mariadb.org/browse/MDEV-31793)。强烈建议在此版本和翻译行为配置组合中使用不同的过滤策略! -
较旧的MariaDB版本在
ONLY_FULL_GROUP_BY
模式下运行时,错误地需要在使用行编号(如用于行编号的行编号)时存在GROUP BY
子句(https://jira.mariadb.org/browse/MDEV-17785)。如果您不能使用修复了该错误的版本,您必须禁用ONLY_FULL_GROUP_BY
,或者相应地添加分组到关联查询中。 -
SQL Server 不支持子查询中的公用表表达式,因此不能使用
FILTER_IN_SUBQUERY_CTE
策略。实际上,使用任何其他策略也无法在关联查询中使用自定义的公用表表达式,因为这会导致表达式在子查询中使用,或者在另一个公用表表达式中嵌套,这也不受支持。