2012-01-22 13:35:06 +01:00
< ? php
2016-10-25 23:58:42 -03:00
/*
* 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 .
2012-01-22 13:35:06 +01:00
*
2016-10-25 23:58:42 -03:00
* 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 >.
2012-01-22 13:35:06 +01:00
*/
namespace Doctrine\ORM\Tools\Pagination ;
2012-10-12 13:53:20 +02:00
use Doctrine\DBAL\Types\Type ;
2015-03-30 14:56:28 -04:00
use Doctrine\ORM\Mapping\ClassMetadataInfo ;
use Doctrine\ORM\Query ;
2012-10-12 13:53:20 +02:00
use Doctrine\ORM\Query\TreeWalkerAdapter ;
2014-04-17 19:29:18 +00:00
use Doctrine\ORM\Query\AST\Functions\IdentityFunction ;
2012-10-12 13:53:20 +02:00
use Doctrine\ORM\Query\AST\PathExpression ;
2014-04-17 19:29:18 +00:00
use Doctrine\ORM\Query\AST\SelectExpression ;
use Doctrine\ORM\Query\AST\SelectStatement ;
2012-01-22 13:35:06 +01:00
/**
2012-12-14 13:13:22 +00:00
* Replaces the selectClause of the AST with a SELECT DISTINCT root . id equivalent .
2012-01-22 13:35:06 +01:00
*
* @ 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
{
/**
2012-12-14 13:13:22 +00:00
* ID type hint .
2012-01-22 13:35:06 +01:00
*/
const IDENTIFIER_TYPE = 'doctrine_paginator.id.type' ;
/**
2012-12-14 13:13:22 +00:00
* Counter for generating unique order column aliases .
*
* @ var int
2012-01-22 13:35:06 +01:00
*/
private $_aliasCounter = 0 ;
/**
* Walks down a SelectStatement AST node , modifying it to retrieve DISTINCT ids
2012-12-14 13:13:22 +00:00
* of the root Entity .
2012-01-22 13:35:06 +01:00
*
* @ param SelectStatement $AST
2012-12-14 13:13:22 +00:00
*
2012-01-22 13:35:06 +01:00
* @ return void
2012-12-14 13:13:22 +00:00
*
* @ throws \RuntimeException
2012-01-22 13:35:06 +01:00
*/
public function walkSelectStatement ( SelectStatement $AST )
{
2014-07-30 15:55:14 +02:00
$queryComponents = $this -> _getQueryComponents ();
// Get the root entity and alias from the AST fromClause
2014-08-28 13:17:25 +02:00
$from = $AST -> fromClause -> identificationVariableDeclarations ;
2014-09-02 08:39:29 +02:00
$fromRoot = reset ( $from );
$rootAlias = $fromRoot -> rangeVariableDeclaration -> aliasIdentificationVariable ;
2014-07-30 15:55:14 +02:00
$rootClass = $queryComponents [ $rootAlias ][ 'metadata' ];
2012-01-22 13:35:06 +01:00
2015-03-30 14:56:28 -04:00
$this -> validate ( $AST );
2014-07-30 15:55:14 +02:00
$identifier = $rootClass -> getSingleIdentifierFieldName ();
2016-12-08 00:31:12 +01:00
2014-07-30 15:55:14 +02:00
if ( isset ( $rootClass -> associationMappings [ $identifier ])) {
2012-05-27 18:33:35 +02:00
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. " );
}
2012-01-22 13:35:06 +01:00
$this -> _getQuery () -> setHint (
self :: IDENTIFIER_TYPE ,
2015-11-06 15:23:16 +00:00
Type :: getType ( $rootClass -> fieldMappings [ $identifier ][ 'type' ])
2012-01-22 13:35:06 +01:00
);
$pathExpression = new PathExpression (
PathExpression :: TYPE_STATE_FIELD | PathExpression :: TYPE_SINGLE_VALUED_ASSOCIATION ,
2014-07-30 15:55:14 +02:00
$rootAlias ,
2012-01-22 13:35:06 +01:00
$identifier
);
2015-11-06 15:23:16 +00:00
2012-01-22 13:35:06 +01:00
$pathExpression -> type = PathExpression :: TYPE_STATE_FIELD ;
2017-12-16 23:31:50 +01:00
$AST -> selectClause -> selectExpressions = [ new SelectExpression ( $pathExpression , '_dctrn_id' )];
$AST -> selectClause -> isDistinct = true ;
2017-11-09 14:10:36 +01:00
2017-12-16 23:31:50 +01:00
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' ])) {
2017-11-09 14:10:36 +01:00
$AST -> selectClause -> selectExpressions [] = new SelectExpression (
2017-12-16 23:31:50 +01:00
$qComp [ 'resultVariable' ],
$item -> expression
2017-11-09 14:10:36 +01:00
);
2012-01-22 13:35:06 +01:00
}
}
}
}
2015-03-30 14:56:28 -04:00
/**
* 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
2017-09-04 10:11:43 +01:00
&& null !== $query -> getMaxResults ()
2015-03-30 14:56:28 -04:00
&& $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. " );
}
}
}
}
}
2016-12-08 00:31:12 +01:00
2014-04-17 19:29:18 +00:00
/**
* Retrieve either an IdentityFunction ( IDENTITY ( u . assoc )) or a state field ( u . name ) .
2016-12-08 00:31:12 +01:00
*
2014-04-17 19:29:18 +00:00
* @ param \Doctrine\ORM\Query\AST\PathExpression $pathExpression
2016-12-08 00:31:12 +01:00
*
2014-04-17 19:29:18 +00:00
* @ return \Doctrine\ORM\Query\AST\Functions\IdentityFunction
*/
private function createSelectExpressionItem ( PathExpression $pathExpression )
{
if ( $pathExpression -> type === PathExpression :: TYPE_SINGLE_VALUED_ASSOCIATION ) {
$identity = new IdentityFunction ( 'identity' );
2016-12-08 00:31:12 +01:00
2014-04-17 19:29:18 +00:00
$identity -> pathExpression = clone $pathExpression ;
2016-12-08 00:31:12 +01:00
2014-04-17 19:29:18 +00:00
return $identity ;
}
2016-12-08 00:31:12 +01:00
2014-04-17 19:29:18 +00:00
return clone $pathExpression ;
}
2012-01-22 13:35:06 +01:00
}