From efa3a546781b1ad38c10f648aa93500e632fc2a9 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Tue, 7 Jan 2025 15:42:42 +0100 Subject: [PATCH] chore: support dbal 4, orm 3, phpcr-odm 2 --- .github/workflows/phpstan.yml | 2 +- .github/workflows/test.yml | 9 +- composer.json | 10 +- src/DBAL/DummyResult.php | 58 ++++------- src/DBAL/DummyResultCompatTrait.php | 21 ++++ src/DBAL/DummyResultCompatTraitV2.php | 47 +++++++++ src/DBAL/DummyResultCompatTraitV4.php | 38 ++++++++ src/DBAL/DummyStatement.php | 66 +------------ src/DBAL/DummyStatementCompatTrait.php | 23 +++++ src/DBAL/DummyStatementCompatTraitV2.php | 30 ++++++ src/DBAL/DummyStatementCompatTraitV4.php | 15 +++ src/ODM/PhpCr/DocumentIterator.php | 15 +-- src/ODM/PhpCr/DocumentRepository.php | 1 - src/ODM/PhpCr/Enable/EnableTrait.php | 2 + src/ODM/PhpCr/IteratorTrait.php | 7 +- .../Timestampable/TimestampableTrait.php | 3 + src/ORM/DQL/Cast.php | 28 ++++-- src/ORM/DQL/Rand.php | 15 ++- src/ORM/DQL/RegExp.php | 31 ++++-- src/ORM/EntityIterator.php | 9 +- src/ORM/EntityRepository.php | 20 ++-- src/ORM/IteratorTrait.php | 27 +++++- src/ORM/Metadata/ClassMetadata.php | 30 ++---- src/ORM/Metadata/ClassMetadataFactory.php | 16 ++-- src/ORM/Type/MoneyCurrencyType.php | 18 +++- src/ORM/Type/PhoneNumberType.php | 22 ++++- src/ORM/Type/PhpEnumType.php | 78 +++++++++++---- .../Timestampable/TimestampUpdaterTest.php | 11 ++- .../Mock/ODM/MongoDB/DocumentManagerTrait.php | 1 - .../Mock/ODM/MongoDB/FakeMetadataFactory.php | 87 +++++++++++++++++ tests/Mock/ODM/PhpCr/DocumentManagerTrait.php | 2 +- tests/Mock/ODM/PhpCr/FakeMetadataFactory.php | 89 +++++++++++++++++ tests/Mock/ORM/EntityManagerTrait.php | 6 +- tests/Mock/{ => ORM}/FakeMetadataFactory.php | 31 +++--- tests/Mock/Platform.php | 96 +++++++++++++++++-- tests/ORM/EntityIteratorTest.php | 2 +- tests/ORM/EntityRepositoryTest.php | 25 +++-- tests/ORM/Type/MoneyCurrencyTypeTest.php | 6 +- tests/ORM/Type/PhoneNumberTypeTest.php | 7 +- tests/ORM/Type/PhpEnumTypeTest.php | 18 +++- 40 files changed, 763 insertions(+), 259 deletions(-) create mode 100644 src/DBAL/DummyResultCompatTrait.php create mode 100644 src/DBAL/DummyResultCompatTraitV2.php create mode 100644 src/DBAL/DummyResultCompatTraitV4.php create mode 100644 src/DBAL/DummyStatementCompatTrait.php create mode 100644 src/DBAL/DummyStatementCompatTraitV2.php create mode 100644 src/DBAL/DummyStatementCompatTraitV4.php create mode 100644 tests/Mock/ODM/MongoDB/FakeMetadataFactory.php create mode 100644 tests/Mock/ODM/PhpCr/FakeMetadataFactory.php rename tests/Mock/{ => ORM}/FakeMetadataFactory.php (79%) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 310da42..bb7970b 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -18,7 +18,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: none - php-version: "8.2" + php-version: "8.3" tools: cs2pr - name: Install dependencies with composer diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8181273..737cf25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,8 +15,14 @@ jobs: php_version: - '8.1' - '8.2' + - '8.3' + - '8.4' - name: PHP ${{ matrix.php_version }} + dbal_version: + - 3.0 + - 4.0 + + name: PHP ${{ matrix.php_version }} - DBAL ${{ matrix.dbal_version }} steps: - uses: actions/checkout@v2 @@ -28,6 +34,7 @@ jobs: extensions: :opcache, mongodb - run: composer remove --no-update --dev roave/security-advisories solido/php-coding-standards + - run: composer require --no-update doctrine/dbal:^${{ matrix.dbal_version }} - run: composer update --with-all-dependencies ${{ matrix.composer_flags }} - run: vendor/bin/phpunit --coverage-clover coverage.xml diff --git a/composer.json b/composer.json index a30de3a..408ff7f 100644 --- a/composer.json +++ b/composer.json @@ -25,10 +25,10 @@ }, "require-dev": { "composer-runtime-api": "^2", - "doctrine/dbal": "^3.2", - "doctrine/mongodb-odm": ">=2.0,<2.5", - "doctrine/orm": "^2.7", - "doctrine/phpcr-odm": "^1.5", + "doctrine/dbal": "^2.6 || ^3.0 || ^4.0", + "doctrine/mongodb-odm": "^2.7", + "doctrine/orm": "^2.7 || ^3.0", + "doctrine/phpcr-odm": "^1.5 || ^2.0", "giggsey/libphonenumber-for-php": "^8.10", "jackalope/jackalope-doctrine-dbal": "*", "moneyphp/money": "^3.2", @@ -53,7 +53,7 @@ } }, "conflict": { - "doctrine/dbal": "<3.0 || >=4.0", + "doctrine/dbal": "<3.0 || >=5.0", "doctrine/orm": "<2.9" }, "config": { diff --git a/src/DBAL/DummyResult.php b/src/DBAL/DummyResult.php index 9d26baa..3b72e68 100644 --- a/src/DBAL/DummyResult.php +++ b/src/DBAL/DummyResult.php @@ -7,60 +7,31 @@ use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; -use function array_values; +use function array_keys; use function count; -use function reset; /** @internal The class is internal to the caching layer implementation. */ final class DummyResult implements Result { + use DummyResultCompatTrait; + private int $columnCount = 0; + /** @var string[] */ + private array $columnNames = []; private int $num = 0; - /** @param mixed[] $data */ - public function __construct(private array $data, int|null $columnCount = null) + /** + * @param mixed[] $data + * @param string[]|null $columnNames + */ + public function __construct(private array $data, int|null $columnCount = null, array|null $columnNames = null) { if (count($data) === 0) { return; } $this->columnCount = $columnCount ?? count($data[0]); - } - - /** - * {@inheritDoc} - */ - public function fetchNumeric() - { - $row = $this->fetch(); - - if ($row === false) { - return false; - } - - return array_values($row); - } - - /** - * {@inheritDoc} - */ - public function fetchAssociative() - { - return $this->fetch(); - } - - /** - * {@inheritDoc} - */ - public function fetchOne() - { - $row = $this->fetch(); - - if ($row === false) { - return false; - } - - return reset($row); + $this->columnNames = $columnNames ?? array_keys($data[0]); /** @phpstan-ignore-line */ } /** @@ -97,12 +68,17 @@ public function columnCount(): int return $this->columnCount; } + public function getColumnName(int $index): string + { + return $this->columnNames[$index] ?? ''; + } + public function free(): void { $this->data = []; } - private function fetch(): mixed + private function doFetch(): mixed { if (! isset($this->data[$this->num])) { return false; diff --git a/src/DBAL/DummyResultCompatTrait.php b/src/DBAL/DummyResultCompatTrait.php new file mode 100644 index 0000000..2d9d7f9 --- /dev/null +++ b/src/DBAL/DummyResultCompatTrait.php @@ -0,0 +1,21 @@ +=')) { + trait DummyResultCompatTrait + { + use DummyResultCompatTraitV4; + } +} else { + trait DummyResultCompatTrait // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses + { + use DummyResultCompatTraitV2; + } +} diff --git a/src/DBAL/DummyResultCompatTraitV2.php b/src/DBAL/DummyResultCompatTraitV2.php new file mode 100644 index 0000000..0c22ff0 --- /dev/null +++ b/src/DBAL/DummyResultCompatTraitV2.php @@ -0,0 +1,47 @@ +doFetch(); + + if ($row === false) { + return false; + } + + return array_values($row); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->doFetch(); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + $row = $this->doFetch(); + + if ($row === false) { + return false; + } + + return reset($row); + } +} diff --git a/src/DBAL/DummyResultCompatTraitV4.php b/src/DBAL/DummyResultCompatTraitV4.php new file mode 100644 index 0000000..5c5fbe0 --- /dev/null +++ b/src/DBAL/DummyResultCompatTraitV4.php @@ -0,0 +1,38 @@ +doFetch(); + + if ($row === false) { + return false; + } + + return array_values($row); + } + + public function fetchAssociative(): array|false + { + return $this->doFetch(); + } + + public function fetchOne(): mixed + { + $row = $this->doFetch(); + + if ($row === false) { + return false; + } + + return reset($row); + } +} diff --git a/src/DBAL/DummyStatement.php b/src/DBAL/DummyStatement.php index 9ca7068..7e80369 100644 --- a/src/DBAL/DummyStatement.php +++ b/src/DBAL/DummyStatement.php @@ -8,12 +8,9 @@ use Doctrine\DBAL\Driver\FetchUtils; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; -use Doctrine\DBAL\ParameterType; use IteratorAggregate; -use function array_values; use function count; -use function reset; /** * Dummy statement serves a static result statement @@ -23,6 +20,8 @@ */ class DummyStatement implements IteratorAggregate, Statement, Result { + use DummyStatementCompatTrait; + private int $columnCount; private int $num; @@ -52,42 +51,6 @@ public function getIterator(): ArrayIterator return new ArrayIterator($data); } - /** - * {@inheritDoc} - */ - public function fetchNumeric() - { - $row = $this->doFetch(); - - if ($row === false) { - return false; - } - - return array_values($row); - } - - /** - * {@inheritDoc} - */ - public function fetchAssociative() - { - return $this->doFetch(); - } - - /** - * {@inheritDoc} - */ - public function fetchOne() - { - $row = $this->doFetch(); - - if ($row === false) { - return false; - } - - return reset($row); - } - /** * {@inheritDoc} */ @@ -117,30 +80,7 @@ public function free(): void $this->data = []; } - /** - * {@inheritDoc} - */ - public function bindValue($param, $value, $type = ParameterType::STRING): bool - { - // TODO - - return true; - } - - /** - * {@inheritDoc} - */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null): bool - { - // TODO - - return true; - } - - /** - * {@inheritDoc} - */ - public function execute($params = null): Result + public function execute(mixed $params = null): Result { return new DummyResult($this->data); } diff --git a/src/DBAL/DummyStatementCompatTrait.php b/src/DBAL/DummyStatementCompatTrait.php new file mode 100644 index 0000000..541db55 --- /dev/null +++ b/src/DBAL/DummyStatementCompatTrait.php @@ -0,0 +1,23 @@ +=')) { + trait DummyStatementCompatTrait + { + use DummyResultCompatTraitV4; + use DummyStatementCompatTraitV4; + } +} else { + trait DummyStatementCompatTrait // phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses + { + use DummyResultCompatTraitV2; + use DummyStatementCompatTraitV2; + } +} diff --git a/src/DBAL/DummyStatementCompatTraitV2.php b/src/DBAL/DummyStatementCompatTraitV2.php new file mode 100644 index 0000000..3249090 --- /dev/null +++ b/src/DBAL/DummyStatementCompatTraitV2.php @@ -0,0 +1,30 @@ +queryBuilder->getQuery(); + $result = $query->getResult(); - try { - /* @phpstan-ignore-next-line */ - $this->internalIterator = $query->iterate(); - } catch (QueryException) { - $result = $query->getResult(); - - /* @phpstan-ignore-next-line */ - assert($result instanceof ArrayCollection); - $this->internalIterator = new ArrayIterator(array_values($result->toArray())); - } + assert($result instanceof ArrayCollection); + $this->internalIterator = new ArrayIterator(array_values($result->toArray())); assert($this->internalIterator instanceof Iterator); - /* @phpstan-ignore-next-line */ $this->currentElement = $this->internalIterator->current(); return $this->internalIterator; diff --git a/src/ODM/PhpCr/DocumentRepository.php b/src/ODM/PhpCr/DocumentRepository.php index 3f0e7d9..3d14e03 100644 --- a/src/ODM/PhpCr/DocumentRepository.php +++ b/src/ODM/PhpCr/DocumentRepository.php @@ -39,7 +39,6 @@ public function count(array $criteria = []): int ->getQuery() ->getResult(Query::HYDRATE_PHPCR); - /* @phpstan-ignore-next-line */ assert($result instanceof QueryResultInterface); return count(iterator_to_array($result->getRows(), false)); diff --git a/src/ODM/PhpCr/Enable/EnableTrait.php b/src/ODM/PhpCr/Enable/EnableTrait.php index 93c8d3c..aa76345 100644 --- a/src/ODM/PhpCr/Enable/EnableTrait.php +++ b/src/ODM/PhpCr/Enable/EnableTrait.php @@ -5,6 +5,7 @@ namespace Refugis\DoctrineExtra\ODM\PhpCr\Enable; use Doctrine\ODM\PHPCR\Mapping\Annotations as ODM; +use Doctrine\ODM\PHPCR\Mapping\Attributes as PHPCRAttributes; use Refugis\DoctrineExtra\Enable\EnableTrait as BaseTrait; trait EnableTrait @@ -16,5 +17,6 @@ trait EnableTrait * * @ODM\Field(type="boolean") */ + #[PHPCRAttributes\Field(type: 'boolean')] private bool $enabled; } diff --git a/src/ODM/PhpCr/IteratorTrait.php b/src/ODM/PhpCr/IteratorTrait.php index b132f83..79c82f2 100644 --- a/src/ODM/PhpCr/IteratorTrait.php +++ b/src/ODM/PhpCr/IteratorTrait.php @@ -4,6 +4,7 @@ namespace Refugis\DoctrineExtra\ODM\PhpCr; +use Countable; use Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder; use Doctrine\ODM\PHPCR\Query\Query; use Doctrine\Persistence\ObjectManager; @@ -34,7 +35,11 @@ public function count(): int /* @phpstan-ignore-next-line */ assert($result instanceof QueryResultInterface); - $this->totalCount = count($result->getRows()); + + $rows = $result->getRows(); + assert($rows instanceof Countable); + + $this->totalCount = count($rows); } return $this->totalCount; diff --git a/src/ODM/PhpCr/Timestampable/TimestampableTrait.php b/src/ODM/PhpCr/Timestampable/TimestampableTrait.php index 01cc41f..07d1087 100644 --- a/src/ODM/PhpCr/Timestampable/TimestampableTrait.php +++ b/src/ODM/PhpCr/Timestampable/TimestampableTrait.php @@ -8,6 +8,7 @@ use DateTimeImmutable; use DateTimeInterface; use Doctrine\ODM\PHPCR\Mapping\Annotations as ODM; +use Doctrine\ODM\PHPCR\Mapping\Attributes as PHPCRAttributes; use Refugis\DoctrineExtra\Timestampable\TimestampableInterface; use Refugis\DoctrineExtra\Timestampable\TimestampableTrait as BaseTrait; @@ -21,9 +22,11 @@ trait TimestampableTrait use BaseTrait; /** @ODM\Field(type="date") */ + #[PHPCRAttributes\Field(type: 'date')] private DateTime $createdAt; /** @ODM\Field(type="date") */ + #[PHPCRAttributes\Field(type: 'date')] private DateTime $updatedAt; public function getCreatedAt(): DateTimeInterface diff --git a/src/ORM/DQL/Cast.php b/src/ORM/DQL/Cast.php index 09c6896..98e3bb6 100644 --- a/src/ORM/DQL/Cast.php +++ b/src/ORM/DQL/Cast.php @@ -9,6 +9,9 @@ use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +use function class_exists; class Cast extends FunctionNode { @@ -18,16 +21,29 @@ class Cast extends FunctionNode public function parse(Parser $parser): void { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); + if (class_exists(TokenType::class)) { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->field = $parser->StringPrimary(); + + $parser->match(TokenType::T_AS); + + $this->type = $parser->StringPrimary(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } else { + $parser->match(Lexer::T_IDENTIFIER); /** @phpstan-ignore-line */ + $parser->match(Lexer::T_OPEN_PARENTHESIS); /** @phpstan-ignore-line */ - $this->field = $parser->StringPrimary(); + $this->field = $parser->StringPrimary(); - $parser->match(Lexer::T_AS); + $parser->match(Lexer::T_AS); /** @phpstan-ignore-line */ - $this->type = $parser->StringPrimary(); + $this->type = $parser->StringPrimary(); - $parser->match(Lexer::T_CLOSE_PARENTHESIS); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); /** @phpstan-ignore-line */ + } } public function getSql(SqlWalker $sqlWalker): string diff --git a/src/ORM/DQL/Rand.php b/src/ORM/DQL/Rand.php index b9cb0f3..0fe7abe 100644 --- a/src/ORM/DQL/Rand.php +++ b/src/ORM/DQL/Rand.php @@ -8,14 +8,23 @@ use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +use function class_exists; class Rand extends FunctionNode { public function parse(Parser $parser): void { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); - $parser->match(Lexer::T_CLOSE_PARENTHESIS); + if (class_exists(TokenType::class)) { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } else { + $parser->match(Lexer::T_IDENTIFIER); /** @phpstan-ignore-line */ + $parser->match(Lexer::T_OPEN_PARENTHESIS); /** @phpstan-ignore-line */ + $parser->match(Lexer::T_CLOSE_PARENTHESIS); /** @phpstan-ignore-line */ + } } public function getSql(SqlWalker $sqlWalker): string diff --git a/src/ORM/DQL/RegExp.php b/src/ORM/DQL/RegExp.php index 3837c02..7bb348f 100644 --- a/src/ORM/DQL/RegExp.php +++ b/src/ORM/DQL/RegExp.php @@ -9,6 +9,9 @@ use Doctrine\ORM\Query\Lexer; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; +use Doctrine\ORM\Query\TokenType; + +use function class_exists; /** * Example Usage: @@ -23,17 +26,31 @@ class RegExp extends FunctionNode public function parse(Parser $parser): void { - $parser->match(Lexer::T_IDENTIFIER); - $parser->match(Lexer::T_OPEN_PARENTHESIS); + if (class_exists(TokenType::class)) { + $parser->match(TokenType::T_IDENTIFIER); + $parser->match(TokenType::T_OPEN_PARENTHESIS); + + $this->value = $parser->StringPrimary(); + + $parser->match(TokenType::T_COMMA); + + /* @phpstan-ignore-next-line */ + $this->regExp = $parser->StringExpression(); + + $parser->match(TokenType::T_CLOSE_PARENTHESIS); + } else { + $parser->match(Lexer::T_IDENTIFIER); /** @phpstan-ignore-line */ + $parser->match(Lexer::T_OPEN_PARENTHESIS); /** @phpstan-ignore-line */ - $this->value = $parser->StringPrimary(); + $this->value = $parser->StringPrimary(); - $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_COMMA); /** @phpstan-ignore-line */ - /* @phpstan-ignore-next-line */ - $this->regExp = $parser->StringExpression(); + /* @phpstan-ignore-next-line */ + $this->regExp = $parser->StringExpression(); - $parser->match(Lexer::T_CLOSE_PARENTHESIS); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); /** @phpstan-ignore-line */ + } } public function getSql(SqlWalker $sqlWalker): string diff --git a/src/ORM/EntityIterator.php b/src/ORM/EntityIterator.php index bb13e6d..7795ab2 100644 --- a/src/ORM/EntityIterator.php +++ b/src/ORM/EntityIterator.php @@ -119,14 +119,11 @@ private function getIterator(): Iterator if (method_exists($query, 'enableResultCache')) { $query->enableResultCache($this->cacheLifetime, $this->resultCache); } else { - $query->useResultCache(true, $this->cacheLifetime, $this->resultCache); + $query->useResultCache(true, $this->cacheLifetime, $this->resultCache); /** @phpstan-ignore-line */ } - - $iterator = $query->iterate(); - } else { - $iterator = method_exists($query, 'toIterable') ? $query->toIterable() : $query->iterate(); } + $iterator = method_exists($query, 'toIterable') ? $query->toIterable() : $query->iterate(); /** @phpstan-ignore-line */ if (! $iterator instanceof Iterator) { $iterator = (static function (iterable $iterable): Generator { yield from $iterable; @@ -145,7 +142,7 @@ private function getCurrentElement(): mixed $current = $this->internalIterator->current(); if ($current === null) { - return $current; + return null; } if (is_array($current)) { diff --git a/src/ORM/EntityRepository.php b/src/ORM/EntityRepository.php index a9213cd..d7a06d5 100644 --- a/src/ORM/EntityRepository.php +++ b/src/ORM/EntityRepository.php @@ -4,6 +4,7 @@ namespace Refugis\DoctrineExtra\ORM; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityRepository as BaseRepository; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; @@ -33,15 +34,12 @@ public function all(): ObjectIteratorInterface return new EntityIterator($this->createQueryBuilder('a')); } - /** - * {@inheritDoc} - */ - public function count(array $criteria = []): int + public function count(array|Criteria $criteria = []): int { - return (int) $this->buildQueryBuilderForCriteria($criteria) - ->select('COUNT(a)') - ->getQuery() - ->getSingleScalarResult(); + return $this->getEntityManager() + ->getUnitOfWork() + ->getEntityPersister($this->getEntityName()) + ->count($criteria); } /** @@ -56,7 +54,7 @@ public function findOneByCached(array $criteria, array|null $orderBy = null, int if (method_exists($query, 'enableResultCache')) { $query->enableResultCache($ttl, $cacheKey); } else { - $query->useResultCache(true, $ttl, $cacheKey); + $query->useResultCache(true, $ttl, $cacheKey); /** @phpstan-ignore-line */ } try { @@ -89,7 +87,7 @@ public function findByCached( if (method_exists($query, 'enableResultCache')) { $query->enableResultCache($ttl, $cacheKey); } else { - $query->useResultCache(true, $ttl, $cacheKey); + $query->useResultCache(true, $ttl, $cacheKey); /** @phpstan-ignore-line */ } return $query->getResult(); @@ -130,7 +128,7 @@ public function getOneByCached(array $criteria, array|null $orderBy = null, int if (method_exists($query, 'enableResultCache')) { $query->enableResultCache($ttl, $cacheKey); } else { - $query->useResultCache(true, $ttl, $cacheKey); + $query->useResultCache(true, $ttl, $cacheKey); /** @phpstan-ignore-line */ } try { diff --git a/src/ORM/IteratorTrait.php b/src/ORM/IteratorTrait.php index 0e558e9..ceb9765 100644 --- a/src/ORM/IteratorTrait.php +++ b/src/ORM/IteratorTrait.php @@ -4,6 +4,9 @@ namespace Refugis\DoctrineExtra\ORM; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\QueryBuilder; @@ -12,6 +15,8 @@ use function assert; use function is_string; +use function ksort; +use function method_exists; trait IteratorTrait { @@ -35,10 +40,14 @@ public function count(): int if (! empty($groupBy)) { $dbalQb = $queryBuilder->getEntityManager()->getConnection()->createQueryBuilder(); - $parser = new Parser($queryBuilder->getQuery()); + $query = $queryBuilder->getQuery(); + $parser = new Parser($query); $parserResult = $parser->parse(); $parameters = $queryBuilder->getParameters(); + $params = []; + /** @var array $paramTypes */ + $paramTypes = []; foreach ($parserResult->getParameterMappings() as $name => $mapping) { $parameter = $parameters->filter(static fn (Parameter $parameter) => $parameter->getName() === $name)->first(); if ($parameter === false) { @@ -47,11 +56,23 @@ public function count(): int assert($parameter instanceof Parameter); foreach ($mapping as $position) { - $dbalQb->setParameter($position, $parameter->getValue(), $parameter->getType()); + $params[(string) $position] = $parameter->getValue(); + $paramTypes[(string) $position] = $parameter->getType(); } } - $sql = $parserResult->getSqlExecutor()->getSqlStatements(); + ksort($params); + ksort($paramTypes); + + $dbalQb->setParameters($params, $paramTypes); + + if (method_exists($parserResult, 'prepareSqlExecutor')) { + $sqlExecutor = $parserResult->prepareSqlExecutor($query); + } else { + $sqlExecutor = $parserResult->getSqlExecutor(); + } + + $sql = $sqlExecutor->getSqlStatements(); assert(is_string($sql)); $dbalQb->select('COUNT(*)')->from('(' . $sql . ') scrl_c_0'); diff --git a/src/ORM/Metadata/ClassMetadata.php b/src/ORM/Metadata/ClassMetadata.php index 5e558f6..31d63b1 100644 --- a/src/ORM/Metadata/ClassMetadata.php +++ b/src/ORM/Metadata/ClassMetadata.php @@ -6,7 +6,6 @@ use Doctrine\Instantiator\Instantiator; use Doctrine\ORM\Mapping\ClassMetadata as BaseClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\ReflectionEmbeddedProperty; use Doctrine\Persistence\Mapping\ReflectionService; @@ -17,18 +16,15 @@ class ClassMetadata extends BaseClassMetadata { - /** - * {@inheritDoc} - */ - public function wakeupReflection($reflectionService): void + public function wakeupReflection(ReflectionService $reflService): void { // Restore ReflectionClass and properties - $reflectionClass = $reflectionService->getClass($this->name); + $reflectionClass = $reflService->getClass($this->name); assert($reflectionClass !== null); $this->reflClass = $reflectionClass; - $instantiatorProperty = new ReflectionProperty(ClassMetadataInfo::class, 'instantiator'); + $instantiatorProperty = new ReflectionProperty(BaseClassMetadata::class, 'instantiator'); $instantiatorProperty->setAccessible(true); $instantiator = $instantiatorProperty->getValue($this); @@ -44,7 +40,7 @@ public function wakeupReflection($reflectionService): void assert(isset($class, $embeddedClass['originalField'])); $parentProperty = $parentReflectionFields[$embeddedClass['declaredField']] ?? null; - $childProperty = $reflectionService->getAccessibleProperty($class, $embeddedClass['originalField']); + $childProperty = $reflService->getAccessibleProperty($class, $embeddedClass['originalField']); assert(isset($parentProperty, $childProperty)); $parentReflectionFields[$property] = new ReflectionEmbeddedProperty($parentProperty, $childProperty, $class); @@ -52,25 +48,22 @@ public function wakeupReflection($reflectionService): void continue; } - $fieldReflection = $reflectionService->getAccessibleProperty($embeddedClass['declared'] ?? $this->name, $property); + $fieldReflection = $reflService->getAccessibleProperty($embeddedClass['declared'] ?? $this->name, $property); $parentReflectionFields[$property] = $fieldReflection; $this->reflFields[$property] = $fieldReflection; } foreach ($this->fieldMappings as $field => $mapping) { - $this->mapReflectionField($mapping, $parentReflectionFields, $reflectionService, $field); + $this->mapReflectionField((array) $mapping, $parentReflectionFields, $reflService, $field); } foreach ($this->associationMappings as $key => $mapping) { - $this->mapReflectionField($mapping, $parentReflectionFields, $reflectionService, $key); + $this->mapReflectionField((array) $mapping, $parentReflectionFields, $reflService, $key); } } - /** - * {@inheritDoc} - */ - public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void + public function inlineEmbeddable(string $property, BaseClassMetadata $embeddable): void { $reflectionClass = $this->reflClass; @@ -83,7 +76,6 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void $fieldMapping['originalField'] ??= $fieldMapping['fieldName']; $fieldMapping['fieldName'] = $property . '.' . $fieldMapping['fieldName']; - /* @phpstan-ignore-next-line */ assert(isset($fieldMapping['columnName'])); if (! empty($this->embeddedClasses[$property]['columnPrefix'])) { $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName']; @@ -97,7 +89,7 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void ); } - $this->mapField($fieldMapping); + $this->mapField((array) $fieldMapping); } foreach ($embeddable->associationMappings as $assocMapping) { @@ -106,7 +98,6 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void } $assocMapping['originalClass'] ??= $embeddable->name; - /* @phpstan-ignore-next-line */ $assocMapping['declaredField'] = isset($assocMapping['declaredField']) ? $property . '_' . $assocMapping['declaredField'] : $property; $assocMapping['originalField'] ??= $assocMapping['fieldName']; $assocMapping['fieldName'] = $property . '_' . $assocMapping['fieldName']; @@ -115,7 +106,6 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void $assocMapping['joinColumnFieldNames'] = []; $assocMapping['targetToSourceKeyColumns'] = []; - /* @phpstan-ignore-next-line */ foreach ($assocMapping['joinColumns'] as &$column) { if (! empty($this->embeddedClasses[$property]['columnPrefix'])) { $column['name'] = $this->embeddedClasses[$property]['columnPrefix'] . $column['name']; @@ -131,7 +121,7 @@ public function inlineEmbeddable($property, ClassMetadataInfo $embeddable): void } unset($column); - $this->mapManyToOne($assocMapping); + $this->mapManyToOne((array) $assocMapping); } } diff --git a/src/ORM/Metadata/ClassMetadataFactory.php b/src/ORM/Metadata/ClassMetadataFactory.php index 3031382..440fea9 100644 --- a/src/ORM/Metadata/ClassMetadataFactory.php +++ b/src/ORM/Metadata/ClassMetadataFactory.php @@ -6,24 +6,20 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataFactory as Base; -use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface; class ClassMetadataFactory extends Base { - private EntityManagerInterface $entityManager; + private EntityManagerInterface $em; - public function setEntityManager(EntityManagerInterface $entityManager): void + public function setEntityManager(EntityManagerInterface $em): void { - $this->entityManager = $entityManager; + $this->em = $em; - parent::setEntityManager($entityManager); + parent::setEntityManager($em); } - /** - * {@inheritDoc} - */ - protected function newClassMetadataInstance($className): ClassMetadataInterface + protected function newClassMetadataInstance(string $className): ClassMetadata { - return new ClassMetadata($className, $this->entityManager->getConfiguration()->getNamingStrategy()); + return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy()); } } diff --git a/src/ORM/Type/MoneyCurrencyType.php b/src/ORM/Type/MoneyCurrencyType.php index 7d5dc19..8c7e5c7 100644 --- a/src/ORM/Type/MoneyCurrencyType.php +++ b/src/ORM/Type/MoneyCurrencyType.php @@ -6,9 +6,13 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; use Doctrine\DBAL\Types\Type; use Money\Currency; +use function class_exists; +use function method_exists; + class MoneyCurrencyType extends Type { public const NAME = 'money_currency'; @@ -16,9 +20,14 @@ class MoneyCurrencyType extends Type /** * {@inheritDoc} */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { - return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); + if (method_exists($platform, 'getStringTypeDeclarationSQL')) { + return $platform->getStringTypeDeclarationSQL($column); + } + + /** @phpstan-ignore-next-line */ + return $platform->getVarcharTypeDeclarationSQL($column); } /** @@ -31,6 +40,11 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): stri } if (! $value instanceof Currency) { + if (class_exists(InvalidType::class)) { + throw InvalidType::new($value, self::NAME, ['ISO currency string']); + } + + /** @phpstan-ignore-next-line */ throw ConversionException::conversionFailedInvalidType($value, self::NAME, ['ISO currency string']); } diff --git a/src/ORM/Type/PhoneNumberType.php b/src/ORM/Type/PhoneNumberType.php index e6d4470..c8d474d 100644 --- a/src/ORM/Type/PhoneNumberType.php +++ b/src/ORM/Type/PhoneNumberType.php @@ -6,12 +6,17 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; +use Doctrine\DBAL\Types\Exception\ValueNotConvertible; use Doctrine\DBAL\Types\Type; use libphonenumber\NumberParseException; use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; +use function class_exists; +use function method_exists; + class PhoneNumberType extends Type { public const NAME = 'phone_number'; @@ -19,8 +24,13 @@ class PhoneNumberType extends Type /** * {@inheritDoc} */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { + if (method_exists($platform, 'getStringTypeDeclarationSQL')) { + return $platform->getStringTypeDeclarationSQL($column); + } + + /** @phpstan-ignore-next-line */ return $platform->getVarcharTypeDeclarationSQL(['length' => 36]); } @@ -34,6 +44,11 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): stri } if (! $value instanceof PhoneNumber) { + if (class_exists(InvalidType::class)) { + throw InvalidType::new($value, self::NAME, [PhoneNumber::class]); + } + + /** @phpstan-ignore-next-line */ throw ConversionException::conversionFailedInvalidType($value, self::NAME, [PhoneNumber::class]); } @@ -54,6 +69,11 @@ public function convertToPHPValue($value, AbstractPlatform $platform): PhoneNumb try { return $util->parse($value, PhoneNumberUtil::UNKNOWN_REGION); } catch (NumberParseException $e) { + if (class_exists(ValueNotConvertible::class)) { + throw ValueNotConvertible::new($value, self::NAME, previous: $e); + } + + /** @phpstan-ignore-next-line */ throw ConversionException::conversionFailed($value, self::NAME, $e); } } diff --git a/src/ORM/Type/PhpEnumType.php b/src/ORM/Type/PhpEnumType.php index 3850cb0..6c9d8a4 100644 --- a/src/ORM/Type/PhpEnumType.php +++ b/src/ORM/Type/PhpEnumType.php @@ -9,6 +9,8 @@ use Doctrine\DBAL\Exception; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; +use Doctrine\DBAL\Types\Exception\ValueNotConvertible; use Doctrine\DBAL\Types\Type; use IntBackedEnum; use InvalidArgumentException; @@ -19,13 +21,15 @@ use function array_map; use function assert; +use function class_exists; use function interface_exists; use function is_a; use function is_string; use function is_subclass_of; use function json_decode; use function json_encode; -use function Safe\sprintf; +use function method_exists; +use function sprintf; use const JSON_THROW_ON_ERROR; use const PHP_VERSION_ID; @@ -56,13 +60,17 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st return $platform->getJsonTypeDeclarationSQL([]); } - return $this->type === self::TYPE_STRING ? $platform->getVarcharTypeDeclarationSQL([]) : $platform->getIntegerTypeDeclarationSQL([]); + if ($this->type === self::TYPE_STRING) { + return method_exists($platform, 'getStringTypeDeclarationSQL') ? + $platform->getStringTypeDeclarationSQL([]) : + /** @phpstan-ignore-next-line */ + $platform->getVarcharTypeDeclarationSQL([]); + } + + return $platform->getIntegerTypeDeclarationSQL([]); } - /** - * {@inheritDoc} - */ - public function convertToPHPValue($value, AbstractPlatform $platform) + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed { if ($value === null || $value === '') { return null; @@ -75,10 +83,7 @@ public function convertToPHPValue($value, AbstractPlatform $platform) return array_map($this->toPhp, json_decode($value, true, 512, JSON_THROW_ON_ERROR)); } - /** - * {@inheritDoc} - */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) + public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed { if ($value === null) { return null; @@ -125,7 +130,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null $toPhp = function ($enumValue) use ($enumClass): BackedEnum { $val = $enumClass::tryFrom($enumValue); if ($val === null) { - throw ConversionException::conversionFailed($enumValue, $this->name); /* @phpstan-ignore-line */ + if (class_exists(ValueNotConvertible::class)) { + /** @phpstan-ignore-next-line */ + throw ValueNotConvertible::new($enumValue, $this->name); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailed($enumValue, $this->name); } return $val; @@ -133,7 +144,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null $toDatabase = function (BackedEnum $enum) { if (! $enum instanceof $this->enumClass) { /* @phpstan-ignore-line */ - throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); /* @phpstan-ignore-line */ + if (class_exists(InvalidType::class)) { + /** @phpstan-ignore-next-line */ + throw InvalidType::new($enum, $this->name, [$this->enumClass]); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); } return $enum->value; @@ -144,7 +161,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null try { $case = $reflection->getCase($enumValue); } catch (ReflectionException $e) { - throw ConversionException::conversionFailed($enumValue, $this->name, $e); /* @phpstan-ignore-line */ + if (class_exists(ValueNotConvertible::class)) { + /** @phpstan-ignore-next-line */ + throw ValueNotConvertible::new($enumValue, $this->name, previous: $e); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailed($enumValue, $this->name, $e); } return $case->getValue(); @@ -152,7 +175,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null $toDatabase = function (UnitEnum $enum): string { if (! $enum instanceof $this->enumClass) { /* @phpstan-ignore-line */ - throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); /* @phpstan-ignore-line */ + if (class_exists(InvalidType::class)) { + /** @phpstan-ignore-next-line */ + throw InvalidType::new($enum, $this->name, [$this->enumClass]); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); } return $enum->name; @@ -161,7 +190,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null } else { $toPhp = function ($enumValue): Enum { if (! $this->enumClass::isValid($enumValue)) { /* @phpstan-ignore-line */ - throw ConversionException::conversionFailed($enumValue, $this->name); /* @phpstan-ignore-line */ + if (class_exists(ValueNotConvertible::class)) { + /** @phpstan-ignore-next-line */ + throw ValueNotConvertible::new($enumValue, $this->name); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailed($enumValue, $this->name); } return new $this->enumClass($enumValue); /* @phpstan-ignore-line */ @@ -169,7 +204,13 @@ public static function registerEnumType(string $typeNameOrEnumClass, string|null $toDatabase = function (Enum $enum): string { if (! $enum instanceof $this->enumClass) { /* @phpstan-ignore-line */ - throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); /* @phpstan-ignore-line */ + if (class_exists(InvalidType::class)) { + /** @phpstan-ignore-next-line */ + throw InvalidType::new($enum, $this->name, [$this->enumClass]); + } + + /** @phpstan-ignore-next-line */ + throw ConversionException::conversionFailedInvalidType($enum, $this->name, [$this->enumClass]); } return (string) $enum; @@ -216,10 +257,7 @@ public static function registerEnumTypes(array $types): void } } - /** - * {@inheritDoc} - */ - public function requiresSQLCommentHint(AbstractPlatform $platform) + public function requiresSQLCommentHint(AbstractPlatform $platform): bool { return true; } diff --git a/tests/EventListener/Timestampable/TimestampUpdaterTest.php b/tests/EventListener/Timestampable/TimestampUpdaterTest.php index 47f5ee8..445860c 100644 --- a/tests/EventListener/Timestampable/TimestampUpdaterTest.php +++ b/tests/EventListener/Timestampable/TimestampUpdaterTest.php @@ -70,7 +70,10 @@ public function testListenerShouldWork(MappingDriver $mappingDriver): void ]; $eventManager->addEventListener($events, new TimestampUpdater()); - AnnotationRegistry::registerLoader('class_exists'); + if (class_exists(AnnotationRegistry::class)) { + AnnotationRegistry::registerLoader('class_exists'); + } + $configuration = new ORM\Configuration(); $configuration->setMetadataDriverImpl($mappingDriver); $configuration->setProxyDir(\sys_get_temp_dir()); @@ -93,7 +96,7 @@ public function testListenerShouldWork(MappingDriver $mappingDriver): void SQL ); - $entityManager = ORM\EntityManager::create($connection, $configuration, $eventManager); + $entityManager = new ORM\EntityManager($connection, $configuration, $eventManager); $foo = new FooTimestampable(); $entityManager->persist($foo); @@ -113,7 +116,9 @@ public function testListenerShouldWork(MappingDriver $mappingDriver): void public function metadataImplProvider(): iterable { - yield [ new ORM\Mapping\Driver\AnnotationDriver(new AnnotationReader()) ]; + if (class_exists(ORM\Mapping\Driver\AnnotationDriver::class)) { + yield [new ORM\Mapping\Driver\AnnotationDriver(new AnnotationReader())]; + } if (PHP_VERSION_ID >= 80000) { yield [new ORM\Mapping\Driver\AttributeDriver([])]; diff --git a/tests/Mock/ODM/MongoDB/DocumentManagerTrait.php b/tests/Mock/ODM/MongoDB/DocumentManagerTrait.php index eae71b9..0bcf593 100644 --- a/tests/Mock/ODM/MongoDB/DocumentManagerTrait.php +++ b/tests/Mock/ODM/MongoDB/DocumentManagerTrait.php @@ -11,7 +11,6 @@ use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Refugis\DoctrineExtra\ODM\MongoDB\DocumentRepository; -use Refugis\DoctrineExtra\Tests\Mock\FakeMetadataFactory; trait DocumentManagerTrait { diff --git a/tests/Mock/ODM/MongoDB/FakeMetadataFactory.php b/tests/Mock/ODM/MongoDB/FakeMetadataFactory.php new file mode 100644 index 0000000..4d99c19 --- /dev/null +++ b/tests/Mock/ODM/MongoDB/FakeMetadataFactory.php @@ -0,0 +1,87 @@ +metadata = []; + } + + public function setDocumentManager($dm): void + { + } + + public function setConfiguration(mixed $config): void + { + } + + /** + * {@inheritdoc} + */ + public function getAllMetadata(): array + { + return \array_values($this->metadata); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFor($className): ClassMetadata + { + if (! isset($this->metadata[$className])) { + throw new MappingException('Cannot find metadata for "'.$className.'"'); + } + + return $this->metadata[$className]; + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($className): bool + { + return isset($this->metadata[$className]); + } + + /** + * {@inheritdoc} + */ + public function setMetadataFor($className, $class): void + { + $this->metadata[$className] = $class; + } + + /** + * {@inheritdoc} + */ + public function isTransient($className): bool + { + return false; + } + + public function setCache(CacheItemPoolInterface $cache): void + { + } + + public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void + { + } +} diff --git a/tests/Mock/ODM/PhpCr/DocumentManagerTrait.php b/tests/Mock/ODM/PhpCr/DocumentManagerTrait.php index 9d9a36a..b201b6a 100644 --- a/tests/Mock/ODM/PhpCr/DocumentManagerTrait.php +++ b/tests/Mock/ODM/PhpCr/DocumentManagerTrait.php @@ -17,7 +17,6 @@ use Prophecy\Prophecy\ObjectProphecy; use Refugis\DoctrineExtra\DBAL\DummyStatement; use Refugis\DoctrineExtra\ODM\PhpCr\DocumentRepository; -use Refugis\DoctrineExtra\Tests\Mock\FakeMetadataFactory; function nodeTypesQuery($connection) { @@ -101,6 +100,7 @@ public function getDocumentManager(): DocumentManagerInterface $this->prophesize(DriverConnection::class); $connection = new Connection([ 'platform' => new MySqlPlatform(), + 'serverVersion' => '5.7.10', ], new Driver()); (fn (DriverConnection $connection) => $this->_conn = $connection) diff --git a/tests/Mock/ODM/PhpCr/FakeMetadataFactory.php b/tests/Mock/ODM/PhpCr/FakeMetadataFactory.php new file mode 100644 index 0000000..8e2d31b --- /dev/null +++ b/tests/Mock/ODM/PhpCr/FakeMetadataFactory.php @@ -0,0 +1,89 @@ +metadata = []; + $this->reflectionService = new RuntimeReflectionService(); + } + + /** + * {@inheritdoc} + */ + public function getAllMetadata(): array + { + return \array_values($this->metadata); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFor($className): ClassMetadata + { + if (! isset($this->metadata[$className])) { + throw new MappingException('Cannot find metadata for "'.$className.'"'); + } + + return $this->metadata[$className]; + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($className): bool + { + return isset($this->metadata[$className]); + } + + /** + * {@inheritdoc} + */ + public function setMetadataFor($className, $class): void + { + $this->metadata[$className] = $class; + + if ($class instanceof ClassMetadata) { + $class->initializeReflection($this->reflectionService); + $class->wakeupReflection($this->reflectionService); + } + } + + /** + * {@inheritdoc} + */ + public function isTransient($className): bool + { + return false; + } + + public function setCache(CacheItemPoolInterface $cache): void + { + } + + public function setProxyClassNameResolver(ProxyClassNameResolver $resolver): void + { + } + + public function __call(string $name, array $arguments) + { + } +} diff --git a/tests/Mock/ORM/EntityManagerTrait.php b/tests/Mock/ORM/EntityManagerTrait.php index b0433d3..0c9def1 100644 --- a/tests/Mock/ORM/EntityManagerTrait.php +++ b/tests/Mock/ORM/EntityManagerTrait.php @@ -1,4 +1,6 @@ - 'user', 'name' => 'dbname', 'platform' => new Platform(), + 'serverVersion' => '8.1.0', ], new Driver(), $this->configuration); (fn (DriverConnection $connection) => $this->_conn = $connection) diff --git a/tests/Mock/FakeMetadataFactory.php b/tests/Mock/ORM/FakeMetadataFactory.php similarity index 79% rename from tests/Mock/FakeMetadataFactory.php rename to tests/Mock/ORM/FakeMetadataFactory.php index fe5b728..707cf02 100644 --- a/tests/Mock/FakeMetadataFactory.php +++ b/tests/Mock/ORM/FakeMetadataFactory.php @@ -1,15 +1,20 @@ value . ')'; + } + + public function getAlterTableSQL(TableDiff $diff): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getListViewsSQL($database): string + { + return 'SHOW VIEWS'; + } + + /** + * {@inheritdoc} + */ + public function getSetTransactionIsolationSQL($level): string + { + return ''; + } + + public function getDateTimeTypeDeclarationSQL(array $column): string + { + return 'TIMESTAMP'; + } + + public function getDateTypeDeclarationSQL(array $column): string + { + return 'DATE'; + } + + public function getTimeTypeDeclarationSQL(array $column): string + { + return 'TIME'; + } + + protected function createReservedKeywordsList(): KeywordList + { + return new PostgreSQLKeywords(); + } + + public function createSchemaManager(Connection $connection): AbstractSchemaManager + { + return new PostgreSQLSchemaManager($connection, $this); + } } diff --git a/tests/ORM/EntityIteratorTest.php b/tests/ORM/EntityIteratorTest.php index 05ec6d1..312ec5a 100644 --- a/tests/ORM/EntityIteratorTest.php +++ b/tests/ORM/EntityIteratorTest.php @@ -132,7 +132,7 @@ public function testCountShouldWorkWithEntityWithForeignIdentifier(): void $metadata->mapOneToOne([ 'fieldName' => 'id', 'targetEntity' => FooBar::class, - 'joinColumns' => [['name' => 'id', 'unique' => true, 'nullable' => 'false']], + 'joinColumns' => [['name' => 'id', 'unique' => true, 'nullable' => false, 'referencedColumnName' => 'id']], 'id' => true, ]); $metadata->reflFields['id'] = new \ReflectionProperty(ForeignIdentifierEntity::class, 'id'); diff --git a/tests/ORM/EntityRepositoryTest.php b/tests/ORM/EntityRepositoryTest.php index f7597c1..358e693 100644 --- a/tests/ORM/EntityRepositoryTest.php +++ b/tests/ORM/EntityRepositoryTest.php @@ -2,12 +2,14 @@ namespace Refugis\DoctrineExtra\Tests\ORM; +use Composer\InstalledVersions; use Doctrine\DBAL\Driver\Statement; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use PDO; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Refugis\DoctrineExtra\DBAL\DummyResult; @@ -67,7 +69,7 @@ public function testAllShouldReturnAnEntityIterator(): void public function testCountWillReturnRowCount(): void { $this->innerConnection - ->query('SELECT COUNT(t0_.id) AS sclr_0 FROM TestEntity t0_') + ->query('SELECT COUNT(*) FROM TestEntity t0') ->willReturn(new DummyStatement([ ['sclr_0' => '42'], ])) @@ -144,8 +146,10 @@ public function testFindByCachedShouldFireTheCorrectQuery(): void ->shouldBeCalledTimes(1) ; - $statement->bindValue(1, 2, PDO::PARAM_INT)->willReturn(); - $statement->bindValue(2, 3, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 2, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); + $statement->bindValue(2, 3, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([ ['id_0' => '2'], ['id_0' => '3'], @@ -167,7 +171,8 @@ public function testGetShouldReturnAnEntity(): void ; /* @var Statement|ObjectProphecy $statement */ - $statement->bindValue(1, 1, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 1, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([['id_1' => '1']])); $obj1 = $this->repository->get(1); @@ -187,7 +192,8 @@ public function testGetShouldThrowIfNoResultIsFound(): void ; /* @var Statement|ObjectProphecy $statement */ - $statement->bindValue(1, 1, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 1, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([])); $this->repository->get(1); @@ -202,7 +208,8 @@ public function testGetOneByShouldReturnAnEntity(): void ; /* @var Statement|ObjectProphecy $statement */ - $statement->bindValue(1, 12, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 12, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([['id_1' => '12']])); $obj1 = $this->repository->getOneBy(['id' => 12]); @@ -222,7 +229,8 @@ public function testGetOneByShouldThrowIfNoResultIsFound(): void ; /* @var Statement|ObjectProphecy $statement */ - $statement->bindValue(1, 12, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 12, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([])); $this->repository->getOneBy(['id' => 12]); @@ -237,7 +245,8 @@ public function testGetOneByCachedShouldCheckTheCache(): void ; /* @var Statement|ObjectProphecy $statement */ - $statement->bindValue(1, 12, PDO::PARAM_INT)->willReturn(); + $statement->bindValue(1, 12, Argument::any()) + ->will(version_compare(InstalledVersions::getVersion('doctrine/dbal'), '4.0.0', '>=') ? static function () {} : static fn () => true); $statement->execute()->willReturn(new DummyResult([['id_0' => '12']])); $obj1 = $this->repository->getOneByCached(['id' => 12]); diff --git a/tests/ORM/Type/MoneyCurrencyTypeTest.php b/tests/ORM/Type/MoneyCurrencyTypeTest.php index 64ed2b3..03d7a01 100644 --- a/tests/ORM/Type/MoneyCurrencyTypeTest.php +++ b/tests/ORM/Type/MoneyCurrencyTypeTest.php @@ -53,7 +53,11 @@ public function tearDown(): void public function testSQLDeclarationShouldBeCorrect(): void { $platform = $this->prophesize(AbstractPlatform::class); - $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + if (method_exists(AbstractPlatform::class, 'getStringTypeDeclarationSQL')) { + $platform->getStringTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + } else { + $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + } $type = Type::getType(MoneyCurrencyType::NAME); diff --git a/tests/ORM/Type/PhoneNumberTypeTest.php b/tests/ORM/Type/PhoneNumberTypeTest.php index e4c3bee..9fc19ad 100644 --- a/tests/ORM/Type/PhoneNumberTypeTest.php +++ b/tests/ORM/Type/PhoneNumberTypeTest.php @@ -54,7 +54,12 @@ public function tearDown(): void public function testSQLDeclarationShouldBeCorrect(): void { $platform = $this->prophesize(AbstractPlatform::class); - $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(36)'); + + if (method_exists(AbstractPlatform::class, 'getStringTypeDeclarationSQL')) { + $platform->getStringTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(36)'); + } else { + $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(36)'); + } $type = Type::getType(PhoneNumberType::NAME); diff --git a/tests/ORM/Type/PhpEnumTypeTest.php b/tests/ORM/Type/PhpEnumTypeTest.php index 10e6321..5a39a86 100644 --- a/tests/ORM/Type/PhpEnumTypeTest.php +++ b/tests/ORM/Type/PhpEnumTypeTest.php @@ -36,8 +36,15 @@ protected function tearDown(): void $multipleActionEnum = "array<$fooEnum>"; foreach ([$fooEnum, $multipleFooEnum, $actionEnum, $multipleActionEnum] as $enumClass) { - if (Type::hasType($enumClass)) { - Type::overrideType($enumClass, null); + $refl = new \ReflectionClass(Type::class); + if ($refl->hasProperty('typeRegistry')) { + $property = $refl->getProperty('typeRegistry'); + $property->setAccessible(true); + $property->setValue(null, null); + } else { + if (Type::hasType($enumClass)) { + Type::overrideType($enumClass, null); + } } } @@ -90,7 +97,12 @@ public function testRegisterShouldThrowIfNotAnEnumClass(): void public function testSQLDeclarationShouldBeCorrect(): void { $platform = $this->prophesize(AbstractPlatform::class); - $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + if (method_exists(AbstractPlatform::class, 'getStringTypeDeclarationSQL')) { + $platform->getStringTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + } else { + $platform->getVarcharTypeDeclarationSQL(Argument::type('array'))->willReturn('VARCHAR(255)'); + } + $platform->getJsonTypeDeclarationSQL(Argument::type('array'))->willReturn('JSON'); $enumClass = FoobarEnum::class;