1
0
mirror of synced 2025-02-02 21:41:45 +03:00
doctrine2/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php
Luís Cobucci 441c5d138c
Reduce complexity of LimitSubqueryWalker#walkSelectStatement()
Ensuring that the code follows our code standards.
2017-12-16 23:31:50 +01:00

177 lines
6.8 KiB
PHP

<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Tools\Pagination;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Query\AST\Functions\IdentityFunction;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\AST\SelectExpression;
use Doctrine\ORM\Query\AST\SelectStatement;
/**
* Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent.
*
* @category DoctrineExtensions
* @package DoctrineExtensions\Paginate
* @author David Abdemoulaie <dave@hobodave.com>
* @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
* @license http://hobodave.com/license.txt New BSD License
*/
class LimitSubqueryWalker extends TreeWalkerAdapter
{
/**
* ID type hint.
*/
const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
/**
* Counter for generating unique order column aliases.
*
* @var int
*/
private $_aliasCounter = 0;
/**
* Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids
* of the root Entity.
*
* @param SelectStatement $AST
*
* @return void
*
* @throws \RuntimeException
*/
public function walkSelectStatement(SelectStatement $AST)
{
$queryComponents = $this->_getQueryComponents();
// Get the root entity and alias from the AST fromClause
$from = $AST->fromClause->identificationVariableDeclarations;
$fromRoot = reset($from);
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
$rootClass = $queryComponents[$rootAlias]['metadata'];
$this->validate($AST);
$identifier = $rootClass->getSingleIdentifierFieldName();
if (isset($rootClass->associationMappings[$identifier])) {
throw new \RuntimeException("Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.");
}
$this->_getQuery()->setHint(
self::IDENTIFIER_TYPE,
Type::getType($rootClass->fieldMappings[$identifier]['type'])
);
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
$rootAlias,
$identifier
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')];
$AST->selectClause->isDistinct = true;
if ( ! isset($AST->orderByClause)) {
return;
}
foreach ($AST->orderByClause->orderByItems as $item) {
if ($item->expression instanceof PathExpression) {
$AST->selectClause->selectExpressions[] = new SelectExpression(
$this->createSelectExpressionItem($item->expression), '_dctrn_ord' . $this->_aliasCounter++
);
continue;
}
if (is_string($item->expression) && isset($queryComponents[$item->expression])) {
$qComp = $queryComponents[$item->expression];
if (isset($qComp['resultVariable'])) {
$AST->selectClause->selectExpressions[] = new SelectExpression(
$qComp['resultVariable'],
$item->expression
);
}
}
}
}
/**
* Validate the AST to ensure that this walker is able to properly manipulate it.
*
* @param SelectStatement $AST
*/
private function validate(SelectStatement $AST)
{
// Prevent LimitSubqueryWalker from being used with queries that include
// a limit, a fetched to-many join, and an order by condition that
// references a column from the fetch joined table.
$queryComponents = $this->getQueryComponents();
$query = $this->_getQuery();
$from = $AST->fromClause->identificationVariableDeclarations;
$fromRoot = reset($from);
if ($query instanceof Query
&& null !== $query->getMaxResults()
&& $AST->orderByClause
&& count($fromRoot->joins)) {
// Check each orderby item.
// TODO: check complex orderby items too...
foreach ($AST->orderByClause->orderByItems as $orderByItem) {
$expression = $orderByItem->expression;
if ($orderByItem->expression instanceof PathExpression
&& isset($queryComponents[$expression->identificationVariable])) {
$queryComponent = $queryComponents[$expression->identificationVariable];
if (isset($queryComponent['parent'])
&& $queryComponent['relation']['type'] & ClassMetadataInfo::TO_MANY) {
throw new \RuntimeException("Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers.");
}
}
}
}
}
/**
* Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name).
*
* @param \Doctrine\ORM\Query\AST\PathExpression $pathExpression
*
* @return \Doctrine\ORM\Query\AST\Functions\IdentityFunction
*/
private function createSelectExpressionItem(PathExpression $pathExpression)
{
if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
$identity = new IdentityFunction('identity');
$identity->pathExpression = clone $pathExpression;
return $identity;
}
return clone $pathExpression;
}
}