CakePHP3: Как изменить стратегию ассоциации на лету? - PullRequest
1 голос
/ 13 апреля 2019

Я бы хотел на лету изменить ассоциацию стратегия ( hasMany ) на «in» (по умолчанию) на «select».Потому что это исправит результат для этой ситуации:

" Получить всех издателей и только первые пять книг ":

$publishersTable = TableRegistry::getTableLocator()->get('Publishers');

$publishersTable->getAssociation('Books')->setStrategy('select');       
        $query = $publishersTable->find()
                ->contain(['Books'=> function(Query $q){

                    return $q->limit(5);

                }]);

К сожалению, Cake все еще использует "в«выполнить запрос, а не« разделенные запросы », и в результате получается только 5 издателей (и не все издатели с первыми 5 книгами).

Можно ли изменить стратегию на лету?Заранее спасибо!

Ответы [ 2 ]

1 голос
/ 13 апреля 2019
Ассоциация

A hasMany всегда будет использовать отдельный отдельный запрос, никогда несколько отдельных запросов.Разница между стратегиями select и subquery заключается в том, что одна будет напрямую сравниваться с массивом первичных ключей, а другая - с объединенным подзапросом, который будет соответствовать выбранным родительским записям.

То, что вы пытаетесьсостоит в том, чтобы выбрать , что невозможно со встроенными загрузчиками ассоциаций, и это может быть немного сложнее в зависимости от используемой СУБД, проверьте, например, Как ограничить содержащиеся ассоциации для каждой записи / группы? для примера для MySQL <8.x с использованием настраиваемой ассоциации и загрузчика. </p>

Для СУБД, которые его поддерживают, просмотритеоконные функции.Вот пример загрузчика, который использует встроенные оконные функции, можно просто заменить его в связанном примере с ним, но имейте в виду, что он на самом деле не тестировался или что-то в этом роде, я просто отложил его в некоторых экспериментах:

namespace App\ORM\Association\Loader;

use Cake\Database\Expression\OrderByExpression;
use Cake\ORM\Association\Loader\SelectLoader;

class GroupLimitedSelectLoader extends SelectLoader
{
    /**
     * The group limit.
     *
     * @var int
     */
    protected $limit;

    /**
     * The target table.
     *
     * @var \Cake\ORM\Table
     */
    protected $target;

    /**
     * {@inheritdoc}
     */
    public function __construct(array $options)
    {
        parent::__construct($options);

        $this->limit = $options['limit'];
        $this->target = $options['target'];
    }

    /**
     * {@inheritdoc}
     */
    protected function _defaultOptions()
    {
        return parent::_defaultOptions() + [
            'limit' => $this->limit,
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function _buildQuery($options)
    {
        $key = $this->_linkField($options);
        $keys = (array)$key;
        $filter = $options['keys'];
        $finder = $this->finder;

        if (!isset($options['fields'])) {
            $options['fields'] = [];
        }

        /* @var \Cake\ORM\Query $query */
        $query = $finder();
        if (isset($options['finder'])) {
            list($finderName, $opts) = $this->_extractFinder($options['finder']);
            $query = $query->find($finderName, $opts);
        }

        $rowNumberParts = ['ROW_NUMBER() OVER (PARTITION BY'];
        for ($i = 0; $i < count($keys); $i ++) {
            $rowNumberParts[] = $query->identifier($keys[$i]);
            if ($i < count($keys) - 1) {
                $rowNumberParts[] = ',';
            }
        }
        $rowNumberParts[] = new OrderByExpression($options['sort']);
        $rowNumberParts[] = ')';

        $rowNumberField = $query
            ->newExpr()
            ->add($rowNumberParts)
            ->setConjunction('');

        $rowNumberSubQuery = $this->target
            ->query()
            ->select(['__row_number' => $rowNumberField])
            ->where($options['conditions']);

        $columns = $this->target->getSchema()->columns();
        $rowNumberSubQuery->select(array_combine($columns, $columns));

        $rowNumberSubQuery = $this->_addFilteringCondition($rowNumberSubQuery, $key, $filter);

        $fetchQuery = $query
            ->select($options['fields'])
            ->from([$this->targetAlias => $rowNumberSubQuery])
            ->where([$this->targetAlias . '.__row_number <=' => $options['limit']])
            ->eagerLoaded(true)
            ->enableHydration($options['query']->isHydrationEnabled());

        if (!empty($options['contain'])) {
            $fetchQuery->contain($options['contain']);
        }

        if (!empty($options['queryBuilder'])) {
            $fetchQuery = $options['queryBuilder']($fetchQuery);
        }

        $this->_assertFieldsPresent($fetchQuery, $keys);

        return $fetchQuery;
    }
}
0 голосов
/ 13 апреля 2019

Спасибо @ndm, но я нашел другое более короткое решение:

$publishersTable->find()
                    ->formatResults(function ($results) use ($publishersTable) {
                       return $results->map(function ($row) use ($publishersTable) {
                            $row['books'] = $publishersTable->Books->find()
                                                ->where(['publisher_id'=>$row['id']])
                                                ->limit(5)
                                                ->toArray();
                            return $row;
                        });
            });
...