From 186147f6462c7b94652f478b15fe0ebec58e7ec6 Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:08:49 +0700 Subject: [PATCH] test: test ORM 2.x and 3.x (#264) * test: test ORM 2.x and 3.x * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix: compatibility with ORM 3.0, 3.1 and 3.2 --- .github/workflows/ci.yml | 14 ++- CHANGELOG.md | 5 + .../src/Internal/CountOutputWalker30.php | 119 ++++++++++++++++++ ...tputWalker.php => CountOutputWalker33.php} | 2 +- .../src/Internal/QueryCounter.php | 6 +- phpstan.neon.dist | 3 +- psalm.xml | 3 +- 7 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker30.php rename packages/rekapager-doctrine-orm-adapter/src/Internal/{CountOutputWalker.php => CountOutputWalker33.php} (98%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdbd593a..b514715f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,10 @@ jobs: operating-system: [ubuntu-latest] php: [ '8.2', '8.3', '8.4' ] symfony: [ '6.*', '7.*' ] + orm: ['^2.19', '^3.0'] dep: [highest,lowest] - name: Symfony ${{ matrix.symfony }}, ${{ matrix.dep }} deps, PHP ${{ matrix.php }}, ${{ matrix.operating-system }} + name: SF ${{ matrix.symfony }}, ${{ matrix.dep }} deps, ORM ${{ matrix.orm }}, PHP ${{ matrix.php }}, ${{ matrix.operating-system }} steps: - uses: actions/checkout@v4 @@ -34,6 +35,9 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate --strict + - name: Change ORM version + run: jq '.require["doctrine/orm"] = "${{ matrix.orm }}"' composer.json > composer.json.tmp && mv composer.json.tmp composer.json + - name: Cache Composer packages id: composer-cache uses: actions/cache@v4 @@ -56,19 +60,19 @@ jobs: - name: Run psalm run: vendor/bin/psalm - if: matrix.dep == 'highest' && matrix.symfony == '7.*' + if: matrix.dep == 'highest' && matrix.symfony == '7.*' && matrix.orm == '^3.0' - name: Run phpstan run: vendor/bin/phpstan analyse - if: matrix.dep == 'highest' && matrix.symfony == '7.*' + if: matrix.dep == 'highest' && matrix.symfony == '7.*' && matrix.orm == '^3.0' # - name: Lint container # run: tests/bin/console lint:container - name: Validate monorepo - run: vendor/bin/monorepo-builder validate + run: git reset --hard ; vendor/bin/monorepo-builder validate - # - name: Rector + # - name: Rector # run: vendor/bin/rector process --no-progress-bar --no-diffs --dry-run --no-ansi - name: Validate php-cs-fixer diff --git a/CHANGELOG.md b/CHANGELOG.md index f490fcde..267e292b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +# 0.22.3 + +* test: test ORM 2.x and 3.x +* fix: compatibility with ORM 3.0, 3.1 and 3.2 + # 0.22.2 * fix: fix compatibility problem with ORM 2.20 diff --git a/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker30.php b/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker30.php new file mode 100644 index 00000000..e4822874 --- /dev/null +++ b/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker30.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Rekapager\Doctrine\ORM\Internal; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; +use Doctrine\ORM\Query; +use Doctrine\ORM\Query\AST\SelectStatement; +use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\Query\SqlWalker; + +/** + * Borrowed from the class of the same name in Doctrine ORM. + * + * @phpstan-import-type QueryComponent from Parser + */ +class CountOutputWalker30 extends SqlWalker +{ + private readonly AbstractPlatform $platform; + private readonly ResultSetMapping $rsm; + + /** + * {@inheritDoc} + */ + public function __construct($query, $parserResult, array $queryComponents) + { + $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); + $this->rsm = $parserResult->getResultSetMapping(); + + parent::__construct($query, $parserResult, $queryComponents); + } + + public function walkSelectStatement(SelectStatement $AST): string + { + if ($this->platform instanceof SQLServerPlatform) { + $AST->orderByClause = null; + } + + $sql = parent::walkSelectStatement($AST); + + // modification start + $maxResults = $this->getQuery()->getHint('maxResults'); + \assert(\is_int($maxResults)); + $firstResult = $this->getQuery()->getHint('firstResult'); + \assert(\is_int($firstResult)); + $sql = $this->platform->modifyLimitQuery($sql, $maxResults, $firstResult); + // modification end + + if ($AST->groupByClause) { + return \sprintf( + 'SELECT COUNT(*) AS dctrn_count FROM (%s) dctrn_table', + $sql, + ); + } + + // Find out the SQL alias of the identifier column of the root entity + // It may be possible to make this work with multiple root entities but that + // would probably require issuing multiple queries or doing a UNION SELECT + // so for now, It's not supported. + + // Get the root entity and alias from the AST fromClause + $from = $AST->fromClause->identificationVariableDeclarations; + if (\count($from) > 1) { + throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); + } + + $fromRoot = reset($from); + $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; + $rootClass = $this->getMetadataForDqlAlias($rootAlias); + $rootIdentifier = $rootClass->identifier; + + // For every identifier, find out the SQL alias by combing through the ResultSetMapping + $sqlIdentifier = []; + foreach ($rootIdentifier as $property) { + if (isset($rootClass->fieldMappings[$property])) { + foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + + if (isset($rootClass->associationMappings[$property])) { + $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; + + foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) { + if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) { + $sqlIdentifier[$property] = $alias; + } + } + } + } + + if (\count($rootIdentifier) !== \count($sqlIdentifier)) { + throw new RuntimeException(\sprintf( + 'Not all identifier properties can be found in the ResultSetMapping: %s', + implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))), + )); + } + + // Build the counter query + return \sprintf( + 'SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table', + implode(', ', $sqlIdentifier), + $sql, + ); + } +} diff --git a/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker.php b/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker33.php similarity index 98% rename from packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker.php rename to packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker33.php index d248156a..df77c91f 100644 --- a/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker.php +++ b/packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker33.php @@ -27,7 +27,7 @@ * * @phpstan-import-type QueryComponent from Parser */ -class CountOutputWalker extends SqlOutputWalker +class CountOutputWalker33 extends SqlOutputWalker { private readonly AbstractPlatform $platform; private readonly ResultSetMapping $rsm; diff --git a/packages/rekapager-doctrine-orm-adapter/src/Internal/QueryCounter.php b/packages/rekapager-doctrine-orm-adapter/src/Internal/QueryCounter.php index c5e14157..1f082e72 100644 --- a/packages/rekapager-doctrine-orm-adapter/src/Internal/QueryCounter.php +++ b/packages/rekapager-doctrine-orm-adapter/src/Internal/QueryCounter.php @@ -86,8 +86,10 @@ private function getCountQuery(): Query throw new RuntimeException('Could not determine the installed version of doctrine/orm'); } - if (version_compare($version, '3.0.0', '>=')) { - $outputWalker = CountOutputWalker::class; + if (version_compare($version, '3.3.0', '>=')) { + $outputWalker = CountOutputWalker33::class; + } elseif (version_compare($version, '3.0.0', '>=')) { + $outputWalker = CountOutputWalker30::class; } else { $outputWalker = CountOutputWalker2::class; } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e6b6cf53..28d73666 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -13,7 +13,8 @@ parameters: excludePaths: - tests/var - tests/src/App/Factory - - packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker.php + - packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker33.php + - packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker30.php - packages/rekapager-doctrine-orm-adapter/src/Internal/CountOutputWalker2.php ignoreErrors: - '#Rekalogika\\Rekapager\\Tests\\App\\Entity\\Post> given.#' diff --git a/psalm.xml b/psalm.xml index 8bb07cde..a737163e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -16,7 +16,8 @@ - + +