From ef825677300f39baa7440682f0321e934e0bf42f Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 3 Aug 2022 17:05:42 +0200 Subject: [PATCH] Fix DBAL 4 compatibility (#9950) --- lib/Doctrine/ORM/AbstractQuery.php | 13 +- .../Entity/AbstractEntityPersister.php | 81 ++++++++--- .../ORM/Decorator/EntityManagerDecorator.php | 5 +- lib/Doctrine/ORM/EntityManager.php | 6 +- lib/Doctrine/ORM/EntityManagerInterface.php | 18 +-- lib/Doctrine/ORM/EntityRepository.php | 8 +- .../Collection/ManyToManyPersister.php | 10 +- .../Entity/BasicEntityPersister.php | 136 +++++++++++------- .../ORM/Persisters/Entity/EntityPersister.php | 88 ++++++++---- .../Entity/JoinedSubclassPersister.php | 4 +- lib/Doctrine/ORM/Query.php | 2 +- .../ORM/Query/AST/Functions/TrimFunction.php | 2 +- .../ORM/Query/ParameterTypeInferer.php | 2 +- lib/Doctrine/ORM/UnitOfWork.php | 2 +- phpstan.neon | 10 ++ psalm-baseline.xml | 9 +- psalm.xml | 6 + .../Performance/Mock/NonLoadingPersister.php | 3 +- .../Mock/NonProxyLoadingEntityManager.php | 5 +- .../Tests/Mocks/CompatibilityType.php | 39 +++++ .../ORM/Functional/Ticket/GH9335Test.php | 5 +- .../ORM/Query/ParameterTypeInfererTest.php | 2 +- 22 files changed, 316 insertions(+), 140 deletions(-) create mode 100644 tests/Doctrine/Tests/Mocks/CompatibilityType.php diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index efb77284d38..102190b0abd 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -9,6 +9,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Result; use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\QueryCacheKey; @@ -327,15 +328,15 @@ public function setParameters(ArrayCollection|array $parameters): static /** * Sets a query parameter. * - * @param string|int $key The parameter position or name. - * @param mixed $value The parameter value. - * @param string|int|null $type The parameter type. If specified, the given value will be run through - * the type conversion of this type. This is usually not needed for - * strings and numeric types. + * @param string|int $key The parameter position or name. + * @param mixed $value The parameter value. + * @param ParameterType|string|int|null $type The parameter type. If specified, the given value will be run through + * the type conversion of this type. This is usually not needed for + * strings and numeric types. * * @return $this */ - public function setParameter(string|int $key, mixed $value, string|int|null $type = null): static + public function setParameter(string|int $key, mixed $value, ParameterType|string|int|null $type = null): static { $existingParameter = $this->getParameter($key); diff --git a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php index ecc41b50fbc..64ffb368622 100644 --- a/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/Entity/AbstractEntityPersister.php @@ -6,6 +6,7 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Util\ClassUtils; +use Doctrine\DBAL\LockMode; use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; @@ -83,8 +84,14 @@ public function getInserts(): array return $this->persister->getInserts(); } - public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, ?int $lockMode = null, ?int $limit = null, ?int $offset = null, ?array $orderBy = null): string - { + public function getSelectSQL( + array|Criteria $criteria, + ?array $assoc = null, + LockMode|int|null $lockMode = null, + ?int $limit = null, + ?int $offset = null, + ?array $orderBy = null + ): string { return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy); } @@ -103,8 +110,12 @@ public function getResultSetMapping(): ResultSetMapping return $this->persister->getResultSetMapping(); } - public function getSelectConditionStatementSQL(string $field, mixed $value, ?array $assoc = null, ?string $comparison = null): string - { + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + ?array $assoc = null, + ?string $comparison = null + ): string { return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); } @@ -191,8 +202,13 @@ private function storeJoinedAssociations(object $entity): void * @param string[]|Criteria $criteria * @param string[] $orderBy */ - protected function getHash(string $query, array|Criteria $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): string - { + protected function getHash( + string $query, + array|Criteria $criteria, + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ): string { [$params] = $criteria instanceof Criteria ? $this->persister->expandCriteriaParameters($criteria) : $this->persister->expandParameters($criteria); @@ -224,16 +240,24 @@ public function getClassMetadata(): ClassMetadata /** * {@inheritdoc} */ - public function getManyToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array - { + public function getManyToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array { return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit); } /** * {@inheritdoc} */ - public function getOneToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array - { + public function getOneToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array { return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit); } @@ -255,8 +279,15 @@ public function executeInserts(): array /** * {@inheritdoc} */ - public function load(array $criteria, ?object $entity = null, ?array $assoc = null, array $hints = [], ?int $lockMode = null, ?int $limit = null, ?array $orderBy = null): ?object - { + public function load( + array $criteria, + ?object $entity = null, + ?array $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + ?int $limit = null, + ?array $orderBy = null + ): ?object { if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) { return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); } @@ -295,8 +326,12 @@ public function load(array $criteria, ?object $entity = null, ?array $assoc = nu /** * {@inheritdoc} */ - public function loadAll(array $criteria = [], ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array - { + public function loadAll( + array $criteria = [], + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ): array { $query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); $hash = $this->getHash($query, $criteria); $rsm = $this->getResultSetMapping(); @@ -419,8 +454,11 @@ public function loadCriteria(Criteria $criteria): array /** * {@inheritdoc} */ - public function loadManyToManyCollection(array $assoc, object $sourceEntity, PersistentCollection $collection): array - { + public function loadManyToManyCollection( + array $assoc, + object $sourceEntity, + PersistentCollection $collection + ): array { $persister = $this->uow->getCollectionPersister($assoc); $hasCache = ($persister instanceof CachedPersister); @@ -450,8 +488,11 @@ public function loadManyToManyCollection(array $assoc, object $sourceEntity, Per /** * {@inheritdoc} */ - public function loadOneToManyCollection(array $assoc, object $sourceEntity, PersistentCollection $collection): mixed - { + public function loadOneToManyCollection( + array $assoc, + object $sourceEntity, + PersistentCollection $collection + ): mixed { $persister = $this->uow->getCollectionPersister($assoc); $hasCache = ($persister instanceof CachedPersister); @@ -489,7 +530,7 @@ public function loadOneToOneEntity(array $assoc, object $sourceEntity, array $id /** * {@inheritdoc} */ - public function lock(array $criteria, int $lockMode): void + public function lock(array $criteria, LockMode|int $lockMode): void { $this->persister->lock($criteria, $lockMode); } @@ -497,7 +538,7 @@ public function lock(array $criteria, int $lockMode): void /** * {@inheritdoc} */ - public function refresh(array $id, object $entity, ?int $lockMode = null): void + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void { $this->persister->refresh($id, $entity, $lockMode); } diff --git a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php index 3687505bb1c..d324429df4a 100644 --- a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php +++ b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php @@ -6,6 +6,7 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\LockMode; use Doctrine\ORM\Cache; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; @@ -119,12 +120,12 @@ public function close(): void /** * {@inheritdoc} */ - public function lock(object $entity, int $lockMode, $lockVersion = null): void + public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null): void { $this->wrapped->lock($entity, $lockMode, $lockVersion); } - public function find(string $className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object + public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object { return $this->wrapped->find($className, $id, $lockMode, $lockVersion); } diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 2f84fb31b58..122b8d92851 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -270,7 +270,7 @@ public function flush(): void /** * {@inheritdoc} */ - public function find($className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object + public function find($className, mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object { $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\')); @@ -520,7 +520,7 @@ public function detach(object $object): void /** * {@inheritDoc} */ - public function lock(object $entity, int $lockMode, $lockVersion = null): void + public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null): void { $this->unitOfWork->lock($entity, $lockMode, $lockVersion); } @@ -689,7 +689,7 @@ public function hasFilters(): bool * @throws OptimisticLockException * @throws TransactionRequiredException */ - private function checkLockRequirements(int $lockMode, ClassMetadata $class): void + private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void { switch ($lockMode) { case LockMode::OPTIMISTIC: diff --git a/lib/Doctrine/ORM/EntityManagerInterface.php b/lib/Doctrine/ORM/EntityManagerInterface.php index 2d4383b6553..46d6bc87ceb 100644 --- a/lib/Doctrine/ORM/EntityManagerInterface.php +++ b/lib/Doctrine/ORM/EntityManagerInterface.php @@ -110,13 +110,13 @@ public function createQueryBuilder(): QueryBuilder; /** * Finds an Entity by its identifier. * - * @param string $className The class name of the entity to find. - * @param mixed $id The identity of the entity to find. - * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * during the search. - * @param int|null $lockVersion The version of the entity to find when using - * optimistic locking. + * @param string $className The class name of the entity to find. + * @param mixed $id The identity of the entity to find. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. + * @param int|null $lockVersion The version of the entity to find when using + * optimistic locking. * @psalm-param class-string $className * @psalm-param LockMode::*|null $lockMode * @@ -130,7 +130,7 @@ public function createQueryBuilder(): QueryBuilder; * * @template T of object */ - public function find(string $className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object; + public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object; /** * Gets a reference to the entity identified by the given type and identifier @@ -189,7 +189,7 @@ public function close(): void; * @throws OptimisticLockException * @throws PessimisticLockException */ - public function lock(object $entity, int $lockMode, $lockVersion = null): void; + public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null): void; /** * Gets the EventManager used by the EntityManager. diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 3335fc0ccf2..42c0ab7d08d 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -75,15 +75,15 @@ public function createResultSetMappingBuilder(string $alias): ResultSetMappingBu /** * Finds an entity by its primary key / identifier. * - * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * during the search. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * during the search. * @psalm-param LockMode::*|null $lockMode * * @return object|null The entity instance or NULL if the entity can not be found. * @psalm-return ?T */ - public function find(mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object + public function find(mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object { return $this->em->find($this->entityName, $id, $lockMode, $lockVersion); } diff --git a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php index 43acaccce84..7700fde3ac2 100644 --- a/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php @@ -7,6 +7,7 @@ use BadMethodCallException; use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\LockMode; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\SqlValueVisitor; @@ -86,7 +87,14 @@ public function get(PersistentCollection $collection, mixed $index): mixed ? $mapping['inversedBy'] : $mapping['mappedBy']; - return $persister->load([$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], null, $mapping, [], 0, 1); + return $persister->load( + [$mappedKey => $collection->getOwner(), $mapping['indexBy'] => $index], + null, + $mapping, + [], + LockMode::NONE, + 1 + ); } public function count(PersistentCollection $collection): int diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 9a3de3a110c..312d85e6fad 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -10,6 +10,7 @@ use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Type; @@ -329,8 +330,8 @@ protected function fetchVersionAndNotUpsertableValues(ClassMetadata $versionedCl /** * @param mixed[] $id * - * @return int[]|null[]|string[] - * @psalm-return list + * @return list + * @psalm-return list */ private function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array { @@ -689,8 +690,15 @@ public function getOwningTable(string $fieldName): string /** * {@inheritdoc} */ - public function load(array $criteria, ?object $entity = null, ?array $assoc = null, array $hints = [], ?int $lockMode = null, ?int $limit = null, ?array $orderBy = null): ?object - { + public function load( + array $criteria, + ?object $entity = null, + ?array $assoc = null, + array $hints = [], + LockMode|int|null $lockMode = null, + ?int $limit = null, + ?array $orderBy = null + ): ?object { $this->switchPersisterContext(null, $limit); $sql = $this->getSelectSQL($criteria, $assoc, $lockMode, $limit, null, $orderBy); @@ -779,7 +787,7 @@ public function loadOneToOneEntity(array $assoc, object $sourceEntity, array $id /** * {@inheritdoc} */ - public function refresh(array $id, object $entity, ?int $lockMode = null): void + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void { $sql = $this->getSelectSQL($id, null, $lockMode); [$params, $types] = $this->expandParameters($id); @@ -852,8 +860,12 @@ public function expandCriteriaParameters(Criteria $criteria): array /** * {@inheritdoc} */ - public function loadAll(array $criteria = [], ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array - { + public function loadAll( + array $criteria = [], + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ): array { $this->switchPersisterContext($offset, $limit); $sql = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy); @@ -868,8 +880,12 @@ public function loadAll(array $criteria = [], ?array $orderBy = null, ?int $limi /** * {@inheritdoc} */ - public function getManyToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array - { + public function getManyToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array { $this->switchPersisterContext($offset, $limit); $stmt = $this->getManyToManyStatement($assoc, $sourceEntity, $offset, $limit); @@ -1006,11 +1022,16 @@ private function getManyToManyStatement( return $this->conn->executeQuery($sql, $params, $types); } - public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, ?int $lockMode = null, ?int $limit = null, ?int $offset = null, ?array $orderBy = null): string - { + public function getSelectSQL( + array|Criteria $criteria, + ?array $assoc = null, + LockMode|int|null $lockMode = null, + ?int $limit = null, + ?int $offset = null, + ?array $orderBy = null + ): string { $this->switchPersisterContext($offset, $limit); - $lockSql = ''; $joinSql = ''; $orderBySql = ''; @@ -1030,15 +1051,11 @@ public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, ?in ? $this->getSelectConditionCriteriaSQL($criteria) : $this->getSelectConditionSQL($criteria, $assoc); - switch ($lockMode) { - case LockMode::PESSIMISTIC_READ: - $lockSql = ' ' . $this->platform->getReadLockSQL(); - break; - - case LockMode::PESSIMISTIC_WRITE: - $lockSql = ' ' . $this->platform->getWriteLockSQL(); - break; - } + $lockSql = match ($lockMode) { + LockMode::PESSIMISTIC_READ => ' ' . $this->platform->getReadLockSQL(), + LockMode::PESSIMISTIC_WRITE => ' ' . $this->platform->getWriteLockSQL(), + default => '', + }; $columnList = $this->getSelectColumnsSQL(); $tableAlias = $this->getSQLTableAlias($this->class->name); @@ -1268,8 +1285,12 @@ protected function getSelectColumnsSQL(): string * * @param mixed[] $assoc */ - protected function getSelectColumnAssociationSQL(string $field, array $assoc, ClassMetadata $class, string $alias = 'r'): string - { + protected function getSelectColumnAssociationSQL( + string $field, + array $assoc, + ClassMetadata $class, + string $alias = 'r' + ): string { if (! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { return ''; } @@ -1458,20 +1479,15 @@ protected function getSQLTableAlias(string $className, string $assocName = ''): /** * {@inheritdoc} */ - public function lock(array $criteria, int $lockMode): void + public function lock(array $criteria, LockMode|int $lockMode): void { - $lockSql = ''; $conditionSql = $this->getSelectConditionSQL($criteria); - switch ($lockMode) { - case LockMode::PESSIMISTIC_READ: - $lockSql = $this->platform->getReadLockSQL(); - - break; - case LockMode::PESSIMISTIC_WRITE: - $lockSql = $this->platform->getWriteLockSQL(); - break; - } + $lockSql = match ($lockMode) { + LockMode::PESSIMISTIC_READ => $this->platform->getReadLockSQL(), + LockMode::PESSIMISTIC_WRITE => $this->platform->getWriteLockSQL(), + default => '', + }; $lock = $this->getLockTablesSql($lockMode); $where = ($conditionSql ? ' WHERE ' . $conditionSql : '') . ' '; @@ -1490,7 +1506,7 @@ public function lock(array $criteria, int $lockMode): void * * @psalm-param LockMode::* $lockMode */ - protected function getLockTablesSql(int $lockMode): string + protected function getLockTablesSql(LockMode|int $lockMode): string { return $this->platform->appendLockHint( 'FROM ' @@ -1516,8 +1532,12 @@ protected function getSelectConditionCriteriaSQL(Criteria $criteria): string return $visitor->dispatch($expression); } - public function getSelectConditionStatementSQL(string $field, mixed $value, ?array $assoc = null, ?string $comparison = null): string - { + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + ?array $assoc = null, + ?string $comparison = null + ): string { $selectedColumns = []; $columns = $this->getSelectConditionStatementColumnSQL($field, $assoc); @@ -1675,8 +1695,12 @@ protected function getSelectConditionSQL(array $criteria, ?array $assoc = null): /** * {@inheritdoc} */ - public function getOneToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array - { + public function getOneToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array { $this->switchPersisterContext($offset, $limit); $stmt = $this->getOneToManyStatement($assoc, $sourceEntity, $offset, $limit); @@ -1687,8 +1711,11 @@ public function getOneToManyCollection(array $assoc, object $sourceEntity, ?int /** * {@inheritdoc} */ - public function loadOneToManyCollection(array $assoc, object $sourceEntity, PersistentCollection $collection): mixed - { + public function loadOneToManyCollection( + array $assoc, + object $sourceEntity, + PersistentCollection $collection + ): mixed { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); return $this->loadCollectionFromStatement($assoc, $stmt, $collection); @@ -1780,7 +1807,7 @@ public function expandParameters(array $criteria): array * - class to which the field belongs to * * @return mixed[][] - * @psalm-return array{0: array, 1: list} + * @psalm-return array{0: array, 1: list} */ private function expandToManyParameters(array $criteria): array { @@ -1802,8 +1829,8 @@ private function expandToManyParameters(array $criteria): array /** * Infers field types to be used by parameter type casting. * - * @return int[]|null[]|string[] - * @psalm-return list + * @return list + * @psalm-return list * * @throws QueryException */ @@ -1836,21 +1863,30 @@ private function getTypes(string $field, mixed $value, ClassMetadata $class): ar break; default: - $types[] = null; + $types[] = ParameterType::STRING; break; } if (is_array($value)) { - return array_map(static function ($type) { - $type = Type::getType($type); - - return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET; - }, $types); + return array_map([$this, 'getArrayBindingType'], $types); } return $types; } + private function getArrayBindingType(ParameterType|int|string $type): int + { + if (! $type instanceof ParameterType) { + $type = Type::getType((string) $type)->getBindingType(); + } + + return match ($type) { + ParameterType::STRING => Connection::PARAM_STR_ARRAY, + ParameterType::INTEGER => Connection::PARAM_INT_ARRAY, + ParameterType::ASCII => Connection::PARAM_ASCII_STR_ARRAY, + }; + } + /** * Retrieves the parameters that identifies a value. * diff --git a/lib/Doctrine/ORM/Persisters/Entity/EntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/EntityPersister.php index ae5ba7f30cf..6885ea0149d 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/EntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/EntityPersister.php @@ -6,6 +6,7 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\ParameterType; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\PersistentCollection; @@ -47,7 +48,14 @@ public function getInsertSQL(): string; * @param mixed[]|null $orderBy * @psalm-param LockMode::*|null $lockMode */ - public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, ?int $lockMode = null, ?int $limit = null, ?int $offset = null, ?array $orderBy = null): string; + public function getSelectSQL( + array|Criteria $criteria, + ?array $assoc = null, + LockMode|int|null $lockMode = null, + ?int $limit = null, + ?int $offset = null, + ?array $orderBy = null + ): string; /** * Get the COUNT SQL to count entities (optionally based on a criteria) @@ -61,14 +69,14 @@ public function getCountSQL(array|Criteria $criteria = []): string; * * @param string[] $criteria * - * @psalm-return array{list, list} + * @psalm-return array{list, list} */ public function expandParameters(array $criteria): array; /** * Expands Criteria Parameters by walking the expressions and grabbing all parameters and types from it. * - * @psalm-return array{list, list} + * @psalm-return array{list, list} */ public function expandCriteriaParameters(Criteria $criteria): array; @@ -77,7 +85,12 @@ public function expandCriteriaParameters(Criteria $criteria): array; * * @psalm-param array|null $assoc */ - public function getSelectConditionStatementSQL(string $field, mixed $value, ?array $assoc = null, ?string $comparison = null): string; + public function getSelectConditionStatementSQL( + string $field, + mixed $value, + ?array $assoc = null, + ?string $comparison = null + ): string; /** * Adds an entity to the queued insertions. @@ -137,17 +150,17 @@ public function getOwningTable(string $fieldName): string; /** * Loads an entity by a list of field criteria. * - * @param mixed[] $criteria The criteria by which to load the entity. - * @param object|null $entity The entity to load the data into. If not specified, - * a new entity is created. - * @param mixed[]|null $assoc The association that connects the entity - * to load to another entity, if any. - * @param mixed[] $hints Hints for entity creation. - * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * for loading the entity. - * @param int|null $limit Limit number of results. - * @param string[]|null $orderBy Criteria to order by. + * @param mixed[] $criteria The criteria by which to load the entity. + * @param object|null $entity The entity to load the data into. If not specified, + * a new entity is created. + * @param mixed[]|null $assoc The association that connects the entity + * to load to another entity, if any. + * @param mixed[] $hints Hints for entity creation. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * for loading the entity. + * @param int|null $limit Limit number of results. + * @param string[]|null $orderBy Criteria to order by. * @psalm-param array $criteria * @psalm-param array|null $assoc * @psalm-param array $hints @@ -163,7 +176,7 @@ public function load( ?object $entity = null, ?array $assoc = null, array $hints = [], - ?int $lockMode = null, + LockMode|int|null $lockMode = null, ?int $limit = null, ?array $orderBy = null ): ?object; @@ -199,15 +212,15 @@ public function loadOneToOneEntity(array $assoc, object $sourceEntity, array $id /** * Refreshes a managed entity. * - * @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants - * or NULL if no specific lock mode should be used - * for refreshing the managed entity. + * @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants + * or NULL if no specific lock mode should be used + * for refreshing the managed entity. * @psalm-param array $id The identifier of the entity as an * associative array from column or * field names to values. * @psalm-param LockMode::*|null $lockMode */ - public function refresh(array $id, object $entity, ?int $lockMode = null): void; + public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void; /** * Loads Entities matching the given Criteria object. @@ -224,7 +237,12 @@ public function loadCriteria(Criteria $criteria): array; * * @return mixed[] */ - public function loadAll(array $criteria = [], ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; + public function loadAll( + array $criteria = [], + ?array $orderBy = null, + ?int $limit = null, + ?int $offset = null + ): array; /** * Gets (sliced or full) elements of the given collection. @@ -233,7 +251,12 @@ public function loadAll(array $criteria = [], ?array $orderBy = null, ?int $limi * * @return mixed[] */ - public function getManyToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array; + public function getManyToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array; /** * Loads a collection of entities of a many-to-many association. @@ -244,7 +267,11 @@ public function getManyToManyCollection(array $assoc, object $sourceEntity, ?int * * @return mixed[] */ - public function loadManyToManyCollection(array $assoc, object $sourceEntity, PersistentCollection $collection): array; + public function loadManyToManyCollection( + array $assoc, + object $sourceEntity, + PersistentCollection $collection + ): array; /** * Loads a collection of entities in a one-to-many association. @@ -252,7 +279,11 @@ public function loadManyToManyCollection(array $assoc, object $sourceEntity, Per * @param PersistentCollection $collection The collection to load/fill. * @psalm-param array $assoc */ - public function loadOneToManyCollection(array $assoc, object $sourceEntity, PersistentCollection $collection): mixed; + public function loadOneToManyCollection( + array $assoc, + object $sourceEntity, + PersistentCollection $collection + ): mixed; /** * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. @@ -260,7 +291,7 @@ public function loadOneToManyCollection(array $assoc, object $sourceEntity, Pers * @psalm-param array $criteria * @psalm-param LockMode::* $lockMode */ - public function lock(array $criteria, int $lockMode): void; + public function lock(array $criteria, LockMode|int $lockMode): void; /** * Returns an array with (sliced or full list) of elements in the specified collection. @@ -269,7 +300,12 @@ public function lock(array $criteria, int $lockMode): void; * * @return mixed[] */ - public function getOneToManyCollection(array $assoc, object $sourceEntity, ?int $offset = null, ?int $limit = null): array; + public function getOneToManyCollection( + array $assoc, + object $sourceEntity, + ?int $offset = null, + ?int $limit = null + ): array; /** * Checks whether the given managed entity exists in the database. diff --git a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php index ce3ce515051..8b3640a50fc 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/JoinedSubclassPersister.php @@ -236,7 +236,7 @@ public function delete(object $entity): bool return (bool) $this->conn->delete($rootTable, $id, $rootTypes); } - public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, ?int $lockMode = null, ?int $limit = null, ?int $offset = null, ?array $orderBy = null): string + public function getSelectSQL(array|Criteria $criteria, ?array $assoc = null, LockMode|int|null $lockMode = null, ?int $limit = null, ?int $offset = null, ?array $orderBy = null): string { $this->switchPersisterContext($offset, $limit); @@ -324,7 +324,7 @@ public function getCountSQL(array|Criteria $criteria = []): string . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); } - protected function getLockTablesSql(int $lockMode): string + protected function getLockTablesSql(LockMode|int $lockMode): string { $joinSql = ''; $identifierColumns = $this->class->getIdentifierColumnNames(); diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 222308ae017..6877e249b6e 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -648,7 +648,7 @@ public function setHydrationMode(string|int $hydrationMode): static * * @throws TransactionRequiredException */ - public function setLockMode(int $lockMode): self + public function setLockMode(LockMode|int $lockMode): self { if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) { if (! $this->em->getConnection()->isTransactionActive()) { diff --git a/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php b/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php index c26313efdcc..3a14d81c9d2 100644 --- a/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php +++ b/lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php @@ -84,7 +84,7 @@ public function parse(Parser $parser) /** * @psalm-return TrimMode::* */ - private function getTrimMode(): int + private function getTrimMode(): TrimMode|int { if ($this->leading) { return TrimMode::LEADING; diff --git a/lib/Doctrine/ORM/Query/ParameterTypeInferer.php b/lib/Doctrine/ORM/Query/ParameterTypeInferer.php index 95b76a2133c..1fe6cb03caa 100644 --- a/lib/Doctrine/ORM/Query/ParameterTypeInferer.php +++ b/lib/Doctrine/ORM/Query/ParameterTypeInferer.php @@ -29,7 +29,7 @@ final class ParameterTypeInferer * - Type (\Doctrine\DBAL\Types\Type::*) * - Connection (\Doctrine\DBAL\Connection::PARAM_*) */ - public static function inferType(mixed $value): int|string + public static function inferType(mixed $value): ParameterType|int|string { if (is_int($value)) { return Types::INTEGER; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0d06fb7c811..8dcb54f165b 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2013,7 +2013,7 @@ private function cascadeRemove(object $entity, array &$visited): void * @throws TransactionRequiredException * @throws OptimisticLockException */ - public function lock(object $entity, int $lockMode, $lockVersion = null): void + public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null): void { if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) { throw ORMInvalidArgumentException::entityNotManaged($entity); diff --git a/phpstan.neon b/phpstan.neon index 4f9da1757a2..c9ced268155 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,3 +11,13 @@ parameters: # Symfony cache supports passing a key prefix to the clear method. - '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/' + + # We can be certain that those values are not matched. + - + message: '~^Match expression does not handle remaining values:~' + path: lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php + + # DBAL 4 compatibility + - + message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~' + path: lib/Doctrine/ORM/Query/AST/Functions/TrimFunction.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 3c753ce1f91..0787c8953dd 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1030,10 +1030,9 @@ array array - + $assoc['mappedBy'] $association - $type $assoc['isOwningSide'] @@ -2244,16 +2243,10 @@ $collectionToUpdate $commitOrder[$i] - - object - $this->entityChangeSets $this->entityChangeSets - - $this->identityMap[$rootClassName][$idHash] - $this->identityMap[$rootClassName] diff --git a/psalm.xml b/psalm.xml index a056287efac..b6a1d02eaa7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -148,6 +148,12 @@ + + + + + + diff --git a/tests/Doctrine/Performance/Mock/NonLoadingPersister.php b/tests/Doctrine/Performance/Mock/NonLoadingPersister.php index 2692be5a1a3..b240f417166 100644 --- a/tests/Doctrine/Performance/Mock/NonLoadingPersister.php +++ b/tests/Doctrine/Performance/Mock/NonLoadingPersister.php @@ -4,6 +4,7 @@ namespace Doctrine\Performance\Mock; +use Doctrine\DBAL\LockMode; use Doctrine\ORM\Persisters\Entity\BasicEntityPersister; /** @@ -23,7 +24,7 @@ public function load( ?object $entity = null, ?array $assoc = null, array $hints = [], - ?int $lockMode = null, + LockMode|int|null $lockMode = null, ?int $limit = null, ?array $orderBy = null ): ?object { diff --git a/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php b/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php index 2b93530aac3..fac993f7408 100644 --- a/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php +++ b/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php @@ -6,6 +6,7 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\LockMode; use Doctrine\ORM\Cache; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; @@ -132,7 +133,7 @@ public function close(): void /** * {@inheritDoc} */ - public function lock(object $entity, int $lockMode, $lockVersion = null): void + public function lock(object $entity, LockMode|int $lockMode, $lockVersion = null): void { $this->realEntityManager->lock($entity, $lockMode, $lockVersion); } @@ -175,7 +176,7 @@ public function hasFilters(): bool return $this->realEntityManager->hasFilters(); } - public function find(string $className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object + public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object { return $this->realEntityManager->find($className, $id, $lockMode, $lockVersion); } diff --git a/tests/Doctrine/Tests/Mocks/CompatibilityType.php b/tests/Doctrine/Tests/Mocks/CompatibilityType.php new file mode 100644 index 00000000000..c70a498c034 --- /dev/null +++ b/tests/Doctrine/Tests/Mocks/CompatibilityType.php @@ -0,0 +1,39 @@ +doGetBindingType(); + } + + private function doGetBindingType(): int|ParameterType + { + return parent::getBindingType(); + } + } +} else { + trait CompatibilityType + { + public function getBindingType(): ParameterType + { + return $this->doGetBindingType(); + } + + private function doGetBindingType(): int|ParameterType + { + return parent::getBindingType(); + } + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php index cd1729902f7..eaf0263c18e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9335Test.php @@ -13,6 +13,7 @@ use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Mapping\OneToOne; +use Doctrine\Tests\Mocks\CompatibilityType; use Doctrine\Tests\OrmFunctionalTestCase; /** @@ -54,6 +55,8 @@ public function testFlattenIdentifierWithObjectId(): void class GH9335IntObjectType extends Type { + use CompatibilityType; + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { return $platform->getIntegerTypeDeclarationSQL($column); @@ -74,7 +77,7 @@ public function convertToPHPValue($value, AbstractPlatform $platform): GH9335Int return new GH9335IntObject((int) $value); } - public function getBindingType(): int + private function doGetBindingType(): ParameterType|int { return ParameterType::INTEGER; } diff --git a/tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php b/tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php index 54d379a5aeb..d1aa56894f5 100644 --- a/tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php +++ b/tests/Doctrine/Tests/ORM/Query/ParameterTypeInfererTest.php @@ -46,7 +46,7 @@ public function providerParameterTypeInferer(): Generator /** * @dataProvider providerParameterTypeInferer */ - public function testParameterTypeInferer(mixed $value, int|string $expected): void + public function testParameterTypeInferer(mixed $value, ParameterType|int|string $expected): void { self::assertEquals($expected, ParameterTypeInferer::inferType($value)); }