From a7a0d047485e8fa5b46cd0c03c92272a73e49227 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 20 Dec 2021 02:32:09 +0100 Subject: [PATCH] Add types to entity manager --- .../ORM/Decorator/EntityManagerDecorator.php | 147 ++++------- lib/Doctrine/ORM/EntityManager.php | 244 ++++-------------- lib/Doctrine/ORM/EntityManagerInterface.php | 162 +++++------- phpstan-baseline.neon | 42 ++- psalm-baseline.xml | 52 ++-- .../Mock/NonProxyLoadingEntityManager.php | 167 ++++-------- .../Tests/Mocks/EntityManagerMock.php | 18 +- .../Decorator/EntityManagerDecoratorTest.php | 112 -------- 8 files changed, 293 insertions(+), 651 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Decorator/EntityManagerDecoratorTest.php diff --git a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php index 0e8301e6b87..bf57450bf0d 100644 --- a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php +++ b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php @@ -4,8 +4,23 @@ namespace Doctrine\ORM\Decorator; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Connection; +use Doctrine\ORM\Cache; +use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Internal\Hydration\AbstractHydrator; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataFactory; +use Doctrine\ORM\NativeQuery; +use Doctrine\ORM\Proxy\ProxyFactory; +use Doctrine\ORM\Query; +use Doctrine\ORM\Query\Expr; +use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\UnitOfWork; use Doctrine\Persistence\ObjectManagerDecorator; /** @@ -21,26 +36,35 @@ public function __construct(EntityManagerInterface $wrapped) $this->wrapped = $wrapped; } + public function getRepository($className): EntityRepository + { + return parent::getRepository($className); + } + + public function getMetadataFactory(): ClassMetadataFactory + { + return parent::getMetadataFactory(); + } + /** * {@inheritdoc} */ - public function getConnection() + public function getClassMetadata($className): ClassMetadata + { + return parent::getClassMetadata($className); + } + + public function getConnection(): Connection { return $this->wrapped->getConnection(); } - /** - * {@inheritdoc} - */ - public function getExpressionBuilder() + public function getExpressionBuilder(): Expr { return $this->wrapped->getExpressionBuilder(); } - /** - * {@inheritdoc} - */ - public function beginTransaction() + public function beginTransaction(): void { $this->wrapped->beginTransaction(); } @@ -53,58 +77,37 @@ public function wrapInTransaction(callable $func) return $this->wrapped->wrapInTransaction($func); } - /** - * {@inheritdoc} - */ - public function commit() + public function commit(): void { $this->wrapped->commit(); } - /** - * {@inheritdoc} - */ - public function rollback() + public function rollback(): void { $this->wrapped->rollback(); } - /** - * {@inheritdoc} - */ - public function createQuery($dql = '') + public function createQuery(string $dql = ''): Query { return $this->wrapped->createQuery($dql); } - /** - * {@inheritdoc} - */ - public function createNamedQuery($name) + public function createNamedQuery(string $name): Query { return $this->wrapped->createNamedQuery($name); } - /** - * {@inheritdoc} - */ - public function createNativeQuery($sql, ResultSetMapping $rsm) + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery { return $this->wrapped->createNativeQuery($sql, $rsm); } - /** - * {@inheritdoc} - */ - public function createNamedNativeQuery($name) + public function createNamedNativeQuery(string $name): NativeQuery { return $this->wrapped->createNamedNativeQuery($name); } - /** - * {@inheritdoc} - */ - public function createQueryBuilder() + public function createQueryBuilder(): QueryBuilder { return $this->wrapped->createQueryBuilder(); } @@ -112,7 +115,7 @@ public function createQueryBuilder() /** * {@inheritdoc} */ - public function getReference($entityName, $id) + public function getReference(string $entityName, $id): ?object { return $this->wrapped->getReference($entityName, $id); } @@ -120,15 +123,12 @@ public function getReference($entityName, $id) /** * {@inheritdoc} */ - public function getPartialReference($entityName, $identifier) + public function getPartialReference(string $entityName, $identifier): ?object { return $this->wrapped->getPartialReference($entityName, $identifier); } - /** - * {@inheritdoc} - */ - public function close() + public function close(): void { $this->wrapped->close(); } @@ -136,7 +136,7 @@ public function close() /** * {@inheritdoc} */ - public function lock($entity, $lockMode, $lockVersion = null) + public function lock(object $entity, int $lockMode, $lockVersion = null): void { $this->wrapped->lock($entity, $lockMode, $lockVersion); } @@ -144,7 +144,7 @@ public function lock($entity, $lockMode, $lockVersion = null) /** * {@inheritdoc} */ - public function find($className, $id, $lockMode = null, $lockVersion = null) + public function find($className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object { return $this->wrapped->find($className, $id, $lockMode, $lockVersion); } @@ -152,39 +152,27 @@ public function find($className, $id, $lockMode = null, $lockVersion = null) /** * {@inheritdoc} */ - public function flush($entity = null) + public function flush($entity = null): void { $this->wrapped->flush($entity); } - /** - * {@inheritdoc} - */ - public function getEventManager() + public function getEventManager(): EventManager { return $this->wrapped->getEventManager(); } - /** - * {@inheritdoc} - */ - public function getConfiguration() + public function getConfiguration(): Configuration { return $this->wrapped->getConfiguration(); } - /** - * {@inheritdoc} - */ - public function isOpen() + public function isOpen(): bool { return $this->wrapped->isOpen(); } - /** - * {@inheritdoc} - */ - public function getUnitOfWork() + public function getUnitOfWork(): UnitOfWork { return $this->wrapped->getUnitOfWork(); } @@ -192,55 +180,32 @@ public function getUnitOfWork() /** * {@inheritdoc} */ - public function getHydrator($hydrationMode) - { - return $this->wrapped->getHydrator($hydrationMode); - } - - /** - * {@inheritdoc} - */ - public function newHydrator($hydrationMode) + public function newHydrator($hydrationMode): AbstractHydrator { return $this->wrapped->newHydrator($hydrationMode); } - /** - * {@inheritdoc} - */ - public function getProxyFactory() + public function getProxyFactory(): ProxyFactory { return $this->wrapped->getProxyFactory(); } - /** - * {@inheritdoc} - */ - public function getFilters() + public function getFilters(): FilterCollection { return $this->wrapped->getFilters(); } - /** - * {@inheritdoc} - */ - public function isFiltersStateClean() + public function isFiltersStateClean(): bool { return $this->wrapped->isFiltersStateClean(); } - /** - * {@inheritdoc} - */ - public function hasFilters() + public function hasFilters(): bool { return $this->wrapped->hasFilters(); } - /** - * {@inheritdoc} - */ - public function getCache() + public function getCache(): ?Cache { return $this->wrapped->getCache(); } diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index b13ea28393b..7ea7bead069 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -18,6 +18,7 @@ use Doctrine\ORM\Exception\MissingMappingDriverImplementation; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\UnrecognizedIdentifierFields; +use Doctrine\ORM\Internal\Hydration\AbstractHydrator; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Proxy\ProxyFactory; @@ -67,80 +68,58 @@ { /** * The used Configuration. - * - * @var Configuration */ - private $config; + private Configuration $config; /** * The database connection used by the EntityManager. - * - * @var Connection */ - private $conn; + private Connection $conn; /** * The metadata factory, used to retrieve the ORM metadata of entity classes. - * - * @var ClassMetadataFactory */ - private $metadataFactory; + private ClassMetadataFactory $metadataFactory; /** * The UnitOfWork used to coordinate object-level transactions. - * - * @var UnitOfWork */ - private $unitOfWork; + private UnitOfWork $unitOfWork; /** * The event manager that is the central point of the event system. - * - * @var EventManager */ - private $eventManager; + private EventManager $eventManager; /** * The proxy factory used to create dynamic proxies. - * - * @var ProxyFactory */ - private $proxyFactory; + private ProxyFactory $proxyFactory; /** * The repository factory used to create dynamic repositories. - * - * @var RepositoryFactory */ - private $repositoryFactory; + private RepositoryFactory $repositoryFactory; /** * The expression builder instance used to generate query expressions. - * - * @var Expr|null */ - private $expressionBuilder; + private ?Expr $expressionBuilder = null; /** * Whether the EntityManager is closed or not. - * - * @var bool */ - private $closed = false; + private bool $closed = false; /** * Collection of query filters. - * - * @var FilterCollection|null */ - private $filterCollection; + private ?FilterCollection $filterCollection = null; /** * The second level cache regions API. - * - * @var Cache|null */ - private $cache; + private ?Cache $cache = null; /** * Creates a new EntityManager that operates on the given database connection @@ -175,48 +154,27 @@ protected function __construct(Connection $conn, Configuration $config, EventMan } } - /** - * {@inheritDoc} - */ - public function getConnection() + public function getConnection(): Connection { return $this->conn; } - /** - * Gets the metadata factory used to gather the metadata of classes. - * - * @return ClassMetadataFactory - */ - public function getMetadataFactory() + public function getMetadataFactory(): ClassMetadataFactory { return $this->metadataFactory; } - /** - * {@inheritDoc} - */ - public function getExpressionBuilder() + public function getExpressionBuilder(): Expr { - if ($this->expressionBuilder === null) { - $this->expressionBuilder = new Query\Expr(); - } - - return $this->expressionBuilder; + return $this->expressionBuilder ??= new Expr(); } - /** - * {@inheritDoc} - */ - public function beginTransaction() + public function beginTransaction(): void { $this->conn->beginTransaction(); } - /** - * {@inheritDoc} - */ - public function getCache() + public function getCache(): ?Cache { return $this->cache; } @@ -243,18 +201,12 @@ public function wrapInTransaction(callable $func) } } - /** - * {@inheritDoc} - */ - public function commit() + public function commit(): void { $this->conn->commit(); } - /** - * {@inheritDoc} - */ - public function rollback() + public function rollback(): void { $this->conn->rollBack(); } @@ -273,15 +225,12 @@ public function rollback() * * {@inheritDoc} */ - public function getClassMetadata($className) + public function getClassMetadata($className): Mapping\ClassMetadata { return $this->metadataFactory->getMetadataFor($className); } - /** - * {@inheritDoc} - */ - public function createQuery($dql = '') + public function createQuery(string $dql = ''): Query { $query = new Query($this); @@ -292,18 +241,12 @@ public function createQuery($dql = '') return $query; } - /** - * {@inheritDoc} - */ - public function createNamedQuery($name) + public function createNamedQuery(string $name): Query { return $this->createQuery($this->config->getNamedQuery($name)); } - /** - * {@inheritDoc} - */ - public function createNativeQuery($sql, ResultSetMapping $rsm) + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery { $query = new NativeQuery($this); @@ -313,20 +256,14 @@ public function createNativeQuery($sql, ResultSetMapping $rsm) return $query; } - /** - * {@inheritDoc} - */ - public function createNamedNativeQuery($name) + public function createNamedNativeQuery(string $name): NativeQuery { [$sql, $rsm] = $this->config->getNamedNativeQuery($name); return $this->createNativeQuery($sql, $rsm); } - /** - * {@inheritDoc} - */ - public function createQueryBuilder() + public function createQueryBuilder(): QueryBuilder { return new QueryBuilder($this); } @@ -341,13 +278,11 @@ public function createQueryBuilder() * * @param object|mixed[]|null $entity * - * @return void - * * @throws OptimisticLockException If a version check on an entity that * makes use of optimistic locking fails. * @throws ORMException */ - public function flush($entity = null) + public function flush($entity = null): void { if ($entity !== null) { Deprecation::trigger( @@ -364,29 +299,9 @@ public function flush($entity = null) } /** - * 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. - * @psalm-param class-string $className - * @psalm-param LockMode::*|null $lockMode - * - * @return object|null The entity instance or NULL if the entity can not be found. - * @psalm-return ?T - * - * @throws OptimisticLockException - * @throws ORMInvalidArgumentException - * @throws TransactionRequiredException - * @throws ORMException - * - * @template T + * {@inheritdoc} */ - public function find($className, $id, $lockMode = null, $lockVersion = null) + public function find($className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object { $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\')); @@ -477,7 +392,7 @@ public function find($className, $id, $lockMode = null, $lockVersion = null) /** * {@inheritDoc} */ - public function getReference($entityName, $id) + public function getReference(string $entityName, $id): ?object { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); @@ -521,7 +436,7 @@ public function getReference($entityName, $id) /** * {@inheritDoc} */ - public function getPartialReference($entityName, $identifier) + public function getPartialReference(string $entityName, $identifier): ?object { $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\')); @@ -552,13 +467,11 @@ public function getPartialReference($entityName, $identifier) * * @param string|null $entityName if given, only entities of this type will get detached * - * @return void - * * @throws ORMInvalidArgumentException If a non-null non-string value is given. * @throws MappingException If a $entityName is given, but that entity is not * found in the mappings. */ - public function clear($entityName = null) + public function clear($entityName = null): void { if ($entityName !== null && ! is_string($entityName)) { throw ORMInvalidArgumentException::invalidEntityName($entityName); @@ -580,10 +493,7 @@ public function clear($entityName = null) ); } - /** - * {@inheritDoc} - */ - public function close() + public function close(): void { $this->clear(); @@ -601,12 +511,10 @@ public function close() * * @param object $entity The instance to make managed and persistent. * - * @return void - * * @throws ORMInvalidArgumentException * @throws ORMException */ - public function persist($entity) + public function persist($entity): void { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity); @@ -625,12 +533,10 @@ public function persist($entity) * * @param object $entity The entity instance to remove. * - * @return void - * * @throws ORMInvalidArgumentException * @throws ORMException */ - public function remove($entity) + public function remove($entity): void { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity); @@ -647,12 +553,10 @@ public function remove($entity) * * @param object $entity The entity to refresh. * - * @return void - * * @throws ORMInvalidArgumentException * @throws ORMException */ - public function refresh($entity) + public function refresh($entity): void { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity); @@ -672,11 +576,9 @@ public function refresh($entity) * * @param object $entity The entity to detach. * - * @return void - * * @throws ORMInvalidArgumentException */ - public function detach($entity) + public function detach($entity): void { if (! is_object($entity)) { throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity); @@ -699,7 +601,7 @@ public function detach($entity) * @throws ORMInvalidArgumentException * @throws ORMException */ - public function merge($entity) + public function merge($entity): object { Deprecation::trigger( 'doctrine/orm', @@ -720,7 +622,7 @@ public function merge($entity) /** * {@inheritDoc} */ - public function lock($entity, $lockMode, $lockVersion = null) + public function lock(object $entity, int $lockMode, $lockVersion = null): void { $this->unitOfWork->lock($entity, $lockMode, $lockVersion); } @@ -736,7 +638,7 @@ public function lock($entity, $lockMode, $lockVersion = null) * * @template T */ - public function getRepository($entityName) + public function getRepository($entityName): EntityRepository { return $this->repositoryFactory->getRepository($this, $entityName); } @@ -748,25 +650,19 @@ public function getRepository($entityName) * * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise. */ - public function contains($entity) + public function contains($entity): bool { return $this->unitOfWork->isScheduledForInsert($entity) || $this->unitOfWork->isInIdentityMap($entity) && ! $this->unitOfWork->isScheduledForDelete($entity); } - /** - * {@inheritDoc} - */ - public function getEventManager() + public function getEventManager(): EventManager { return $this->eventManager; } - /** - * {@inheritDoc} - */ - public function getConfiguration() + public function getConfiguration(): Configuration { return $this->config; } @@ -783,18 +679,12 @@ private function errorIfClosed(): void } } - /** - * {@inheritDoc} - */ - public function isOpen() + public function isOpen(): bool { return ! $this->closed; } - /** - * {@inheritDoc} - */ - public function getUnitOfWork() + public function getUnitOfWork(): UnitOfWork { return $this->unitOfWork; } @@ -802,15 +692,7 @@ public function getUnitOfWork() /** * {@inheritDoc} */ - public function getHydrator($hydrationMode) - { - return $this->newHydrator($hydrationMode); - } - - /** - * {@inheritDoc} - */ - public function newHydrator($hydrationMode) + public function newHydrator($hydrationMode): AbstractHydrator { switch ($hydrationMode) { case Query::HYDRATE_OBJECT: @@ -842,10 +724,7 @@ public function newHydrator($hydrationMode) throw InvalidHydrationMode::fromMode((string) $hydrationMode); } - /** - * {@inheritDoc} - */ - public function getProxyFactory() + public function getProxyFactory(): ProxyFactory { return $this->proxyFactory; } @@ -853,7 +732,7 @@ public function getProxyFactory() /** * {@inheritDoc} */ - public function initializeObject($obj) + public function initializeObject($obj): void { $this->unitOfWork->initializeObject($obj); } @@ -871,7 +750,7 @@ public function initializeObject($obj) * @throws InvalidArgumentException * @throws ORMException */ - public static function create($connection, Configuration $config, ?EventManager $eventManager = null) + public static function create($connection, Configuration $config, ?EventManager $eventManager = null): EntityManager { if (! $config->getMetadataDriverImpl()) { throw MissingMappingDriverImplementation::create(); @@ -890,12 +769,10 @@ public static function create($connection, Configuration $config, ?EventManager * @param EventManager|null $eventManager The EventManager instance to use. * @psalm-param array|Connection $connection * - * @return Connection - * * @throws InvalidArgumentException * @throws ORMException */ - protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null) + protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null): Connection { if (is_array($connection)) { return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager()); @@ -918,30 +795,17 @@ protected static function createConnection($connection, Configuration $config, ? return $connection; } - /** - * {@inheritDoc} - */ - public function getFilters() + public function getFilters(): FilterCollection { - if ($this->filterCollection === null) { - $this->filterCollection = new FilterCollection($this); - } - - return $this->filterCollection; + return $this->filterCollection ??= new FilterCollection($this); } - /** - * {@inheritDoc} - */ - public function isFiltersStateClean() + public function isFiltersStateClean(): bool { return $this->filterCollection === null || $this->filterCollection->isClean(); } - /** - * {@inheritDoc} - */ - public function hasFilters() + public function hasFilters(): bool { return $this->filterCollection !== null; } diff --git a/lib/Doctrine/ORM/EntityManagerInterface.php b/lib/Doctrine/ORM/EntityManagerInterface.php index bedef5e8a78..5a04fbd219e 100644 --- a/lib/Doctrine/ORM/EntityManagerInterface.php +++ b/lib/Doctrine/ORM/EntityManagerInterface.php @@ -10,17 +10,13 @@ use Doctrine\DBAL\LockMode; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Internal\Hydration\AbstractHydrator; +use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ObjectManager; -/** - * EntityManager interface - * - * @method Mapping\ClassMetadataFactory getMetadataFactory() - */ interface EntityManagerInterface extends ObjectManager { /** @@ -32,21 +28,19 @@ interface EntityManagerInterface extends ObjectManager * * @template T */ - public function getRepository($className); + public function getRepository($className): EntityRepository; /** * Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled. - * - * @return Cache|null */ - public function getCache(); + public function getCache(): ?Cache; /** * Gets the database connection object used by the EntityManager. - * - * @return Connection */ - public function getConnection(); + public function getConnection(): Connection; + + public function getMetadataFactory(): ClassMetadataFactory; /** * Gets an ExpressionBuilder used for object-oriented construction of query expressions. @@ -59,17 +53,13 @@ public function getConnection(); * $qb->select('u')->from('User', 'u') * ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2))); * - * - * @return Expr */ - public function getExpressionBuilder(); + public function getExpressionBuilder(): Expr; /** * Starts a transaction on the underlying database connection. - * - * @return void */ - public function beginTransaction(); + public function beginTransaction(): void; /** * Executes a function in a transaction. @@ -81,9 +71,10 @@ public function beginTransaction(); * If an exception occurs during execution of the function or flushing or transaction commit, * the transaction is rolled back, the EntityManager closed and the exception re-thrown. * - * @param callable(self): T $func The function to execute transactionally. + * @psalm-param callable(self): T $func The function to execute transactionally. * - * @return T The value returned from the closure. + * @return mixed The value returned from the closure. + * @psalm-return T * * @template T */ @@ -91,61 +82,65 @@ public function wrapInTransaction(callable $func); /** * Commits a transaction on the underlying database connection. - * - * @return void */ - public function commit(); + public function commit(): void; /** * Performs a rollback on the underlying database connection. - * - * @return void */ - public function rollback(); + public function rollback(): void; /** * Creates a new Query object. * * @param string $dql The DQL string. - * - * @return Query */ - public function createQuery($dql = ''); + public function createQuery(string $dql = ''): Query; /** * Creates a Query from a named query. - * - * @param string $name - * - * @return Query */ - public function createNamedQuery($name); + public function createNamedQuery(string $name): Query; /** * Creates a native SQL query. - * - * @param string $sql - * @param ResultSetMapping $rsm The ResultSetMapping to use. - * - * @return NativeQuery */ - public function createNativeQuery($sql, ResultSetMapping $rsm); + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery; /** * Creates a NativeQuery from a named native query. - * - * @param string $name - * - * @return NativeQuery */ - public function createNamedNativeQuery($name); + public function createNamedNativeQuery(string $name): NativeQuery; /** * Create a QueryBuilder instance + */ + 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. + * @psalm-param class-string $className + * @psalm-param LockMode::*|null $lockMode + * + * @return object|null The entity instance or NULL if the entity can not be found. + * @psalm-return T|null + * + * @throws OptimisticLockException + * @throws ORMInvalidArgumentException + * @throws TransactionRequiredException + * @throws ORMException * - * @return QueryBuilder + * @template T of object */ - public function createQueryBuilder(); + public function find($className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object; /** * Gets a reference to the entity identified by the given type and identifier @@ -155,14 +150,13 @@ public function createQueryBuilder(); * @param mixed $id The entity identifier. * @psalm-param class-string $entityName * - * @return object|null The entity reference. * @psalm-return T|null * * @throws ORMException * - * @template T + * @template T of object */ - public function getReference($entityName, $id); + public function getReference(string $entityName, $id): ?object; /** * Gets a partial reference to the entity identified by the given type and identifier @@ -183,127 +177,87 @@ public function getReference($entityName, $id); * @param mixed $identifier The entity identifier. * @psalm-param class-string $entityName * - * @return object|null The (partial) entity reference * @psalm-return T|null * - * @template T + * @template T of object */ - public function getPartialReference($entityName, $identifier); + public function getPartialReference(string $entityName, $identifier): ?object; /** * Closes the EntityManager. All entities that are currently managed * by this EntityManager become detached. The EntityManager may no longer * be used after it is closed. - * - * @return void */ - public function close(); + public function close(): void; /** * Acquire a lock on the given entity. * - * @param object $entity - * @param int $lockMode * @param int|DateTimeInterface|null $lockVersion * @psalm-param LockMode::* $lockMode * - * @return void - * * @throws OptimisticLockException * @throws PessimisticLockException */ - public function lock($entity, $lockMode, $lockVersion = null); + public function lock(object $entity, int $lockMode, $lockVersion = null): void; /** * Gets the EventManager used by the EntityManager. - * - * @return EventManager */ - public function getEventManager(); + public function getEventManager(): EventManager; /** * Gets the Configuration used by the EntityManager. - * - * @return Configuration */ - public function getConfiguration(); + public function getConfiguration(): Configuration; /** * Check if the Entity manager is open or closed. - * - * @return bool */ - public function isOpen(); + public function isOpen(): bool; /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. - * - * @return UnitOfWork - */ - public function getUnitOfWork(); - - /** - * Gets a hydrator for the given hydration mode. - * - * This method caches the hydrator instances which is used for all queries that don't - * selectively iterate over the result. - * - * @deprecated - * - * @param string|int $hydrationMode - * - * @return AbstractHydrator */ - public function getHydrator($hydrationMode); + public function getUnitOfWork(): UnitOfWork; /** * Create a new instance for the given hydration mode. * * @param string|int $hydrationMode * - * @return AbstractHydrator - * * @throws ORMException */ - public function newHydrator($hydrationMode); + public function newHydrator($hydrationMode): AbstractHydrator; /** * Gets the proxy factory used by the EntityManager to create entity proxies. - * - * @return ProxyFactory */ - public function getProxyFactory(); + public function getProxyFactory(): ProxyFactory; /** * Gets the enabled filters. - * - * @return FilterCollection The active filter collection. */ - public function getFilters(); + public function getFilters(): FilterCollection; /** * Checks whether the state of the filter collection is clean. - * - * @return bool True, if the filter collection is clean. */ - public function isFiltersStateClean(); + public function isFiltersStateClean(): bool; /** * Checks whether the Entity Manager has filters. - * - * @return bool True, if the EM has a filter collection. */ - public function hasFilters(); + public function hasFilters(): bool; /** * {@inheritDoc} * * @psalm-param string|class-string $className * - * @return Mapping\ClassMetadata * @psalm-return Mapping\ClassMetadata * * @psalm-template T of object */ - public function getClassMetadata($className); + public function getClassMetadata($className): Mapping\ClassMetadata; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fcbe53514ff..7d1bf0276a3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -121,7 +121,17 @@ parameters: path: lib/Doctrine/ORM/Configuration.php - - message: "#^Method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:find\\(\\) invoked with 4 parameters, 2 required\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\Decorator\\\\EntityManagerDecorator\\:\\:getClassMetadata\\(\\) should return Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata but returns Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\\\.$#" + count: 1 + path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php + + - + message: "#^Method Doctrine\\\\ORM\\\\Decorator\\\\EntityManagerDecorator\\:\\:getMetadataFactory\\(\\) should return Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataFactory but returns Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\.$#" + count: 1 + path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php + + - + message: "#^Method Doctrine\\\\ORM\\\\Decorator\\\\EntityManagerDecorator\\:\\:getRepository\\(\\) should return Doctrine\\\\ORM\\\\EntityRepository but returns Doctrine\\\\Persistence\\\\ObjectRepository\\\\.$#" count: 1 path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php @@ -130,38 +140,48 @@ parameters: count: 1 path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php + - + message: "#^Return type \\(Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ORM\\\\Decorator\\\\EntityManagerDecorator\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" + count: 1 + path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php + + - + message: "#^Return type \\(Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ORM\\\\Decorator\\\\EntityManagerDecorator\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManagerDecorator\\:\\:getMetadataFactory\\(\\)$#" + count: 1 + path: lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php + - message: "#^Else branch is unreachable because ternary operator condition is always true\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:find\\(\\) should return T\\|null but returns object\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:find\\(\\) should return T of object\\|null but returns object\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:find\\(\\) should return T\\|null but returns object\\|null\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:find\\(\\) should return T of object\\|null but returns object\\|null\\.$#" count: 3 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getPartialReference\\(\\) should return T\\|null but returns object\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getPartialReference\\(\\) should return T of object\\|null but returns object\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getPartialReference\\(\\) should return T\\|null but returns object\\|null\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getPartialReference\\(\\) should return T of object\\|null but returns object\\|null\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getReference\\(\\) should return T\\|null but returns Doctrine\\\\Common\\\\Proxy\\\\Proxy\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getReference\\(\\) should return T of object\\|null but returns Doctrine\\\\Common\\\\Proxy\\\\Proxy\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php - - message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getReference\\(\\) should return T\\|null but returns object\\|null\\.$#" + message: "#^Method Doctrine\\\\ORM\\\\EntityManager\\:\\:getReference\\(\\) should return T of object\\|null but returns object\\|null\\.$#" count: 1 path: lib/Doctrine/ORM/EntityManager.php @@ -172,9 +192,14 @@ parameters: - message: "#^Return type \\(Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ORM\\\\EntityManager\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" - count: 2 + count: 1 path: lib/Doctrine/ORM/EntityManager.php + - + message: "#^Return type \\(Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataFactory\\) of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" + count: 1 + path: lib/Doctrine/ORM/EntityManagerInterface.php + - message: "#^Template type T of method Doctrine\\\\ORM\\\\EntityManagerInterface\\:\\:getClassMetadata\\(\\) is not referenced in a parameter\\.$#" count: 1 @@ -1554,3 +1579,4 @@ parameters: message: "#^Access to an undefined property Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadata\\:\\:\\$subClasses\\.$#" count: 1 path: lib/Doctrine/ORM/Utility/HierarchyDiscriminatorResolver.php + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index abdeb61b299..fddb99638a2 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -369,19 +369,33 @@ - - getHydrator - - + + $className + + + ProxyFactory + + + parent::getMetadataFactory() + + + ClassMetadataFactory + + + parent::getClassMetadata($className) + parent::getRepository($className) + + $entity - $lockMode - $lockVersion + + ClassMetadata + EntityRepository + $wrapped - - find + flush @@ -389,7 +403,8 @@ $connection - + + ProxyFactory ProxyFactory @@ -407,10 +422,7 @@ is_object($entity) is_object($entity) - - ClassMetadataFactory - - + $entity $entity $entity @@ -419,13 +431,15 @@ $entity instanceof $class->name ? $entity : null $persister->load($sortedId, null, null, [], $lockMode) $persister->loadById($sortedId) + $this->metadataFactory $this->metadataFactory->getMetadataFor($className) - - ?T - getClassMetadata - getPartialReference - getReference + + ?object + ?object + ?object + ClassMetadataFactory + Mapping\ClassMetadata $hydrationMode @@ -435,8 +449,8 @@ new $class($this) + AbstractHydrator EntityRepository<T> - newHydrator $entity diff --git a/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php b/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php index 8f101c416e4..40db66982e5 100644 --- a/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php +++ b/tests/Doctrine/Performance/Mock/NonProxyLoadingEntityManager.php @@ -4,27 +4,37 @@ namespace Doctrine\Performance\Mock; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Connection; +use Doctrine\ORM\Cache; +use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Internal\Hydration\AbstractHydrator; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataFactory; +use Doctrine\ORM\NativeQuery; use Doctrine\ORM\Proxy\ProxyFactory; +use Doctrine\ORM\Query; +use Doctrine\ORM\Query\Expr; +use Doctrine\ORM\Query\FilterCollection; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\UnitOfWork; /** * An entity manager mock that prevents lazy-loading of proxies */ class NonProxyLoadingEntityManager implements EntityManagerInterface { - /** @var EntityManagerInterface */ - private $realEntityManager; + private EntityManagerInterface $realEntityManager; public function __construct(EntityManagerInterface $realEntityManager) { $this->realEntityManager = $realEntityManager; } - /** - * {@inheritDoc} - */ - public function getProxyFactory() + public function getProxyFactory(): ProxyFactory { $config = $this->realEntityManager->getConfiguration(); @@ -36,10 +46,7 @@ public function getProxyFactory() ); } - /** - * {@inheritDoc} - */ - public function getMetadataFactory() + public function getMetadataFactory(): ClassMetadataFactory { return $this->realEntityManager->getMetadataFactory(); } @@ -47,47 +54,32 @@ public function getMetadataFactory() /** * {@inheritDoc} */ - public function getClassMetadata($className) + public function getClassMetadata($className): ClassMetadata { return $this->realEntityManager->getClassMetadata($className); } - /** - * {@inheritDoc} - */ - public function getUnitOfWork() + public function getUnitOfWork(): UnitOfWork { return new NonProxyLoadingUnitOfWork(); } - /** - * {@inheritDoc} - */ - public function getCache() + public function getCache(): ?Cache { return $this->realEntityManager->getCache(); } - /** - * {@inheritDoc} - */ - public function getConnection() + public function getConnection(): Connection { return $this->realEntityManager->getConnection(); } - /** - * {@inheritDoc} - */ - public function getExpressionBuilder() + public function getExpressionBuilder(): Expr { return $this->realEntityManager->getExpressionBuilder(); } - /** - * {@inheritDoc} - */ - public function beginTransaction() + public function beginTransaction(): void { $this->realEntityManager->beginTransaction(); } @@ -100,58 +92,37 @@ public function wrapInTransaction(callable $func) return $this->realEntityManager->wrapInTransaction($func); } - /** - * {@inheritDoc} - */ - public function commit() + public function commit(): void { $this->realEntityManager->commit(); } - /** - * {@inheritDoc} - */ - public function rollback() + public function rollback(): void { $this->realEntityManager->rollback(); } - /** - * {@inheritDoc} - */ - public function createQuery($dql = '') + public function createQuery(string $dql = ''): Query { return $this->realEntityManager->createQuery($dql); } - /** - * {@inheritDoc} - */ - public function createNamedQuery($name) + public function createNamedQuery(string $name): Query { return $this->realEntityManager->createNamedQuery($name); } - /** - * {@inheritDoc} - */ - public function createNativeQuery($sql, ResultSetMapping $rsm) + public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery { return $this->realEntityManager->createNativeQuery($sql, $rsm); } - /** - * {@inheritDoc} - */ - public function createNamedNativeQuery($name) + public function createNamedNativeQuery(string $name): NativeQuery { return $this->realEntityManager->createNamedNativeQuery($name); } - /** - * {@inheritDoc} - */ - public function createQueryBuilder() + public function createQueryBuilder(): QueryBuilder { return $this->realEntityManager->createQueryBuilder(); } @@ -159,7 +130,7 @@ public function createQueryBuilder() /** * {@inheritDoc} */ - public function getReference($entityName, $id) + public function getReference(string $entityName, $id): ?object { return $this->realEntityManager->getReference($entityName, $id); } @@ -167,15 +138,12 @@ public function getReference($entityName, $id) /** * {@inheritDoc} */ - public function getPartialReference($entityName, $identifier) + public function getPartialReference(string $entityName, $identifier): ?object { return $this->realEntityManager->getPartialReference($entityName, $identifier); } - /** - * {@inheritDoc} - */ - public function close() + public function close(): void { $this->realEntityManager->close(); } @@ -183,31 +151,22 @@ public function close() /** * {@inheritDoc} */ - public function lock($entity, $lockMode, $lockVersion = null) + public function lock(object $entity, int $lockMode, $lockVersion = null): void { $this->realEntityManager->lock($entity, $lockMode, $lockVersion); } - /** - * {@inheritDoc} - */ - public function getEventManager() + public function getEventManager(): EventManager { return $this->realEntityManager->getEventManager(); } - /** - * {@inheritDoc} - */ - public function getConfiguration() + public function getConfiguration(): Configuration { return $this->realEntityManager->getConfiguration(); } - /** - * {@inheritDoc} - */ - public function isOpen() + public function isOpen(): bool { return $this->realEntityManager->isOpen(); } @@ -215,39 +174,22 @@ public function isOpen() /** * {@inheritDoc} */ - public function getHydrator($hydrationMode) - { - return $this->realEntityManager->getHydrator($hydrationMode); - } - - /** - * {@inheritDoc} - */ - public function newHydrator($hydrationMode) + public function newHydrator($hydrationMode): AbstractHydrator { return $this->realEntityManager->newHydrator($hydrationMode); } - /** - * {@inheritDoc} - */ - public function getFilters() + public function getFilters(): FilterCollection { return $this->realEntityManager->getFilters(); } - /** - * {@inheritDoc} - */ - public function isFiltersStateClean() + public function isFiltersStateClean(): bool { return $this->realEntityManager->isFiltersStateClean(); } - /** - * {@inheritDoc} - */ - public function hasFilters() + public function hasFilters(): bool { return $this->realEntityManager->hasFilters(); } @@ -255,15 +197,15 @@ public function hasFilters() /** * {@inheritDoc} */ - public function find($className, $id) + public function find($className, mixed $id, ?int $lockMode = null, ?int $lockVersion = null): ?object { - return $this->realEntityManager->find($className, $id); + return $this->realEntityManager->find($className, $id, $lockMode, $lockVersion); } /** * {@inheritDoc} */ - public function persist($object) + public function persist($object): void { $this->realEntityManager->persist($object); } @@ -271,7 +213,7 @@ public function persist($object) /** * {@inheritDoc} */ - public function remove($object) + public function remove($object): void { $this->realEntityManager->remove($object); } @@ -279,7 +221,7 @@ public function remove($object) /** * {@inheritDoc} */ - public function merge($object) + public function merge($object): object { return $this->realEntityManager->merge($object); } @@ -287,7 +229,7 @@ public function merge($object) /** * {@inheritDoc} */ - public function clear($objectName = null) + public function clear($objectName = null): void { $this->realEntityManager->clear($objectName); } @@ -295,7 +237,7 @@ public function clear($objectName = null) /** * {@inheritDoc} */ - public function detach($object) + public function detach($object): void { $this->realEntityManager->detach($object); } @@ -303,15 +245,12 @@ public function detach($object) /** * {@inheritDoc} */ - public function refresh($object) + public function refresh($object): void { $this->realEntityManager->refresh($object); } - /** - * {@inheritDoc} - */ - public function flush() + public function flush(): void { $this->realEntityManager->flush(); } @@ -319,7 +258,7 @@ public function flush() /** * {@inheritDoc} */ - public function getRepository($className) + public function getRepository($className): EntityRepository { return $this->realEntityManager->getRepository($className); } @@ -327,7 +266,7 @@ public function getRepository($className) /** * {@inheritDoc} */ - public function initializeObject($obj) + public function initializeObject($obj): void { $this->realEntityManager->initializeObject($obj); } @@ -335,7 +274,7 @@ public function initializeObject($obj) /** * {@inheritDoc} */ - public function contains($object) + public function contains($object): bool { return $this->realEntityManager->contains($object); } diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index 51ca576ae32..33798a14a95 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -15,16 +15,10 @@ */ class EntityManagerMock extends EntityManager { - /** @var UnitOfWork|null */ - private $_uowMock; + private ?UnitOfWork $_uowMock = null; + private ?ProxyFactory $_proxyFactoryMock = null; - /** @var ProxyFactory|null */ - private $_proxyFactoryMock; - - /** - * {@inheritdoc} - */ - public function getUnitOfWork() + public function getUnitOfWork(): UnitOfWork { return $this->_uowMock ?? parent::getUnitOfWork(); } @@ -54,7 +48,7 @@ public function getProxyFactory(): ProxyFactory * * {@inheritdoc} */ - public static function create($conn, ?Configuration $config = null, ?EventManager $eventManager = null) + public static function create($conn, ?Configuration $config = null, ?EventManager $eventManager = null): self { if ($config === null) { $config = new Configuration(); @@ -63,9 +57,7 @@ public static function create($conn, ?Configuration $config = null, ?EventManage $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver([], false)); } - if ($eventManager === null) { - $eventManager = new EventManager(); - } + $eventManager ??= new EventManager(); return new EntityManagerMock($conn, $config, $eventManager); } diff --git a/tests/Doctrine/Tests/ORM/Decorator/EntityManagerDecoratorTest.php b/tests/Doctrine/Tests/ORM/Decorator/EntityManagerDecoratorTest.php deleted file mode 100644 index 4600871065d..00000000000 --- a/tests/Doctrine/Tests/ORM/Decorator/EntityManagerDecoratorTest.php +++ /dev/null @@ -1,112 +0,0 @@ -wrapped = $this->createMock(EntityManagerInterface::class); - } - - /** @psalm-return array */ - public function getMethodParameters(): array - { - $class = new ReflectionClass(EntityManagerInterface::class); - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isConstructor() || $method->isStatic() || ! $method->isPublic()) { - continue; - } - - $methods[$method->getName()] = $this->getParameters($method); - } - - return $methods; - } - - /** - * @return mixed[] - */ - private function getParameters(ReflectionMethod $method): array - { - /** Special case EntityManager::createNativeQuery() */ - if ($method->getName() === 'createNativeQuery') { - return [$method->getName(), ['name', new ResultSetMapping()]]; - } - - if ($method->getName() === 'wrapInTransaction') { - return [ - $method->getName(), - [ - static function (): void { - }, - ], - ]; - } - - if ($method->getNumberOfRequiredParameters() === 0) { - return [$method->getName(), []]; - } - - if ($method->getNumberOfRequiredParameters() > 0) { - return [$method->getName(), array_fill(0, $method->getNumberOfRequiredParameters(), 'req') ?: []]; - } - - if ($method->getNumberOfParameters() !== $method->getNumberOfRequiredParameters()) { - return [$method->getName(), array_fill(0, $method->getNumberOfParameters(), 'all') ?: []]; - } - - return []; - } - - /** - * @dataProvider getMethodParameters - */ - public function testAllMethodCallsAreDelegatedToTheWrappedInstance($method, array $parameters): void - { - $return = ! in_array($method, self::VOID_METHODS, true) ? 'INNER VALUE FROM ' . $method : null; - - $this->wrapped->expects(self::once()) - ->method($method) - ->with(...$parameters) - ->willReturn($return); - - $decorator = new class ($this->wrapped) extends EntityManagerDecorator { - }; - - self::assertSame($return, $decorator->$method(...$parameters)); - } -}