vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php line 68

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Tools\Pagination;
  4. use Doctrine\DBAL\Platforms\AbstractPlatform;
  5. use Doctrine\DBAL\Platforms\SQLServerPlatform;
  6. use Doctrine\ORM\Query;
  7. use Doctrine\ORM\Query\AST\SelectStatement;
  8. use Doctrine\ORM\Query\Parser;
  9. use Doctrine\ORM\Query\ParserResult;
  10. use Doctrine\ORM\Query\ResultSetMapping;
  11. use Doctrine\ORM\Query\SqlWalker;
  12. use RuntimeException;
  13. use function array_diff;
  14. use function array_keys;
  15. use function count;
  16. use function implode;
  17. use function reset;
  18. use function sprintf;
  19. /**
  20.  * Wraps the query in order to accurately count the root objects.
  21.  *
  22.  * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
  23.  * SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>))
  24.  *
  25.  * Works with composite keys but cannot deal with queries that have multiple
  26.  * root entities (e.g. `SELECT f, b from Foo, Bar`)
  27.  *
  28.  * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL)
  29.  * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery
  30.  * that will most likely be executed next can be read from the native SQL cache.
  31.  *
  32.  * @psalm-import-type QueryComponent from Parser
  33.  */
  34. class CountOutputWalker extends SqlWalker
  35. {
  36.     /** @var AbstractPlatform */
  37.     private $platform;
  38.     /** @var ResultSetMapping */
  39.     private $rsm;
  40.     /**
  41.      * Stores various parameters that are otherwise unavailable
  42.      * because Doctrine\ORM\Query\SqlWalker keeps everything private without
  43.      * accessors.
  44.      *
  45.      * @param Query        $query
  46.      * @param ParserResult $parserResult
  47.      * @param mixed[]      $queryComponents
  48.      * @psalm-param array<string, QueryComponent> $queryComponents
  49.      */
  50.     public function __construct($query$parserResult, array $queryComponents)
  51.     {
  52.         $this->platform $query->getEntityManager()->getConnection()->getDatabasePlatform();
  53.         $this->rsm      $parserResult->getResultSetMapping();
  54.         parent::__construct($query$parserResult$queryComponents);
  55.     }
  56.     /**
  57.      * {@inheritDoc}
  58.      */
  59.     public function walkSelectStatement(SelectStatement $AST)
  60.     {
  61.         if ($this->platform instanceof SQLServerPlatform) {
  62.             $AST->orderByClause null;
  63.         }
  64.         $sql parent::walkSelectStatement($AST);
  65.         if ($AST->groupByClause) {
  66.             return sprintf(
  67.                 'SELECT COUNT(*) AS dctrn_count FROM (%s) dctrn_table',
  68.                 $sql
  69.             );
  70.         }
  71.         // Find out the SQL alias of the identifier column of the root entity
  72.         // It may be possible to make this work with multiple root entities but that
  73.         // would probably require issuing multiple queries or doing a UNION SELECT
  74.         // so for now, It's not supported.
  75.         // Get the root entity and alias from the AST fromClause
  76.         $from $AST->fromClause->identificationVariableDeclarations;
  77.         if (count($from) > 1) {
  78.             throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
  79.         }
  80.         $fromRoot       reset($from);
  81.         $rootAlias      $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
  82.         $rootClass      $this->getMetadataForDqlAlias($rootAlias);
  83.         $rootIdentifier $rootClass->identifier;
  84.         // For every identifier, find out the SQL alias by combing through the ResultSetMapping
  85.         $sqlIdentifier = [];
  86.         foreach ($rootIdentifier as $property) {
  87.             if (isset($rootClass->fieldMappings[$property])) {
  88.                 foreach (array_keys($this->rsm->fieldMappings$propertytrue) as $alias) {
  89.                     if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
  90.                         $sqlIdentifier[$property] = $alias;
  91.                     }
  92.                 }
  93.             }
  94.             if (isset($rootClass->associationMappings[$property])) {
  95.                 $joinColumn $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
  96.                 foreach (array_keys($this->rsm->metaMappings$joinColumntrue) as $alias) {
  97.                     if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
  98.                         $sqlIdentifier[$property] = $alias;
  99.                     }
  100.                 }
  101.             }
  102.         }
  103.         if (count($rootIdentifier) !== count($sqlIdentifier)) {
  104.             throw new RuntimeException(sprintf(
  105.                 'Not all identifier properties can be found in the ResultSetMapping: %s',
  106.                 implode(', 'array_diff($rootIdentifierarray_keys($sqlIdentifier)))
  107.             ));
  108.         }
  109.         // Build the counter query
  110.         return sprintf(
  111.             'SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
  112.             implode(', '$sqlIdentifier),
  113.             $sql
  114.         );
  115.     }
  116. }