icings/partitionable

CakePHP ORM 的可分区关联,允许对每组进行基本限制。

安装: 1,737

依赖项: 0

建议者: 0

安全: 0

星星: 16

观察者: 3

分支: 3

开放问题: 0

类型:cakephp-plugin

2.0.1 2024-06-18 11:21 UTC

This package is auto-updated.

Last update: 2024-09-03 11:24:47 UTC


README

Build Status Coverage Status Latest Version Software License

一组针对 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

我不明白,这究竟有什么好处?

这些关联究竟有什么好处,"每组限制"到底是指什么?

基本上,此库中提供的关联允许对 hasManybelongsToMany 类型的关联应用限制,例如,对于 Articles hasMany Comments 关联,可以接收每篇文章最多 n 个评论。

使用

请务必先查看 已知问题/限制 部分!

然后将 \Icings\Partitionable\ORM\AssociationsTrait 特性添加到您的表类中,使用其 partitionableHasMany()partitionableBelongsToMany() 方法添加 hasManybelongsToMany 关联,配置限制和排序顺序,然后完成最小设置,您可以像处理其他关联一样处理可分区的关联。

请注意,配置排序顺序是强制性的,因为没有明确的排序顺序,无法可靠地对结果进行分区,省略它将导致错误!

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\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 策略。实际上,使用任何其他策略也无法在关联查询中使用自定义的公用表表达式,因为这会导致表达式在子查询中使用,或者在另一个公用表表达式中嵌套,这也不受支持。