【问题标题】:CakePHP3: How to change an association strategy on-the-fly?CakePHP3:如何即时更改关联策略?
【发布时间】:2019-04-13 13:48:04
【问题描述】:

我想即时将关联策略 (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 仍然使用“in”而不是“分隔查询”来运行查询,结果只有 5 个出版商(并不是所有出版商都有前 5 本书)。

是否可以即时更改策略? 提前致谢!

【问题讨论】:

    标签: php cakephp cakephp-3.0 has-many


    【解决方案1】:

    hasMany 关联将始终使用单个单独的查询,而不是多个单独的查询。 selectsubquery 策略之间的区别在于,一个将直接与主键数组进行比较,而另一个将与将匹配所选父记录的连接子查询进行比较。

    您正在尝试的是选择,这对于内置关联加载程序是不可能的,并且根据您使用的DBMS,这可能有点棘手,例如How to limit contained associations per record/group? 使用自定义关联和加载器的 MySQL

    对于支持它的 DBMS,请查看窗口函数。这是一个使用本机窗口函数的加载器示例,应该可以简单地用它替换链​​接示例中的那个,但请记住,它没有经过真正的测试或任何东西,我只是从一些实验中得到它:

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

    【讨论】:

      【解决方案2】:

      感谢@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;
                              });
                  });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-20
        • 2021-07-15
        • 1970-01-01
        相关资源
        最近更新 更多