From 9e5f9171303de27744a92af1d60200d6e5bceaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 9 Oct 2024 12:29:47 +0200 Subject: [PATCH 01/14] Use VarExporter instead of ProxyManager --- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 3 +- .../ODM/MongoDB/InvalidArgumentException.php | 39 ++++ .../Proxy/Factory/AwesomeProxyFactory.php | 196 ++++++++++++++++++ .../MongoDB/Proxy/Factory/ProxyFactory.php | 3 +- .../ODM/MongoDB/Proxy/InternalProxy.php | 16 ++ 5 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/AwesomeProxyFactory.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 0a281a6c6f..5871efe48f 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -9,6 +9,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Proxy\Factory\AwesomeProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver; @@ -182,7 +183,7 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory); $this->schemaManager = new SchemaManager($this, $this->metadataFactory); - $this->proxyFactory = new StaticProxyFactory($this); + $this->proxyFactory = new AwesomeProxyFactory($this); $this->repositoryFactory = $this->config->getRepositoryFactory(); } diff --git a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php new file mode 100644 index 0000000000..b7795c54b0 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php @@ -0,0 +1,39 @@ +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ +{ + + + /** + * @internal + */ + public bool $__isCloning = false; + + public function __construct(?\Closure $initializer = null) + { + self::createLazyGhost($initializer, , $this); + } + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __clone() + { + $this->__isCloning = true; + try { + $this->__doClone(); + } finally { + $this->__isCloning = false; + } + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + private UnitOfWork $uow; + private LifecycleEventManager $lifecycleEventManager; + private LazyLoadingGhostFactory $proxyFactory; + + public function __construct(DocumentManager $documentManager) + { + $this->uow = $documentManager->getUnitOfWork(); + $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager()); + $this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory(); + } + + public function generateProxyClasses(array $classes): int + { + $concreteClasses = array_filter($classes, static fn (ClassMetadata $metadata): bool => ! ($metadata->isMappedSuperclass || $metadata->isQueryResultDocument || $metadata->getReflectionClass()->isAbstract())); + + foreach ($concreteClasses as $metadata) { + $this + ->proxyFactory + ->createProxy( + $metadata->getName(), + static fn (): bool => true, // empty closure, serves its purpose, for now + [ + 'skippedProperties' => $this->skippedFieldsFqns($metadata), + ], + ); + } + + return count($concreteClasses); + } + + /** + * @param class-string $className + * @param array $identifier + */ + public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface + { + $className = $metadata->getName(); + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); + + return $proxyFactory($identifier); + } + + private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void + { + $i = strrpos($proxyClassName, '\\'); + $placeholders = [ + '' => $class->getName(), + '' => substr($proxyClassName, 0, $i), + '' => substr($proxyClassName, 1 + $i), + '' => InternalProxy::class, + ]; + + preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); + + foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { + $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); + } + + $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); + + if (! $fileName) { + if (! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { + throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); + } + + if (! is_writable($parentDirectory)) { + throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); + } + + $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + /** + * @param ClassMetadata $metadata + * + * @return array + */ + private function skippedFieldsFqns(ClassMetadata $metadata): array + { + $idFieldFqcns = []; + + foreach ($metadata->getIdentifierFieldNames() as $idField) { + $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); + } + + return $idFieldFqcns; + } + + private function propertyFqcn(ReflectionProperty $property): string + { + if ($property->isPrivate()) { + return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); + } + + if ($property->isProtected()) { + return "\0*\0" . $property->getName(); + } + + return $property->getName(); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php index 35b292281e..b726055d4c 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use ProxyManager\Proxy\GhostObjectInterface; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; interface ProxyFactory { @@ -19,7 +20,7 @@ public function generateProxyClasses(array $classes): int; * @param mixed $identifier * @psalm-param ClassMetadata $metadata * - * @psalm-return T&GhostObjectInterface + * @return T&(GhostObjectInterface|InternalProxy) * * @template T of object */ diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php new file mode 100644 index 0000000000..36c159269e --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php @@ -0,0 +1,16 @@ + + */ +interface InternalProxy extends Proxy +{ + public function __setInitialized(bool $initialized): void; +} From ce1f19968ff3cb3dad9b354b8cb01444142350b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 14 Oct 2024 10:29:40 +0200 Subject: [PATCH 02/14] - --- composer.json | 1 + lib/Doctrine/ODM/MongoDB/Configuration.php | 34 ++ lib/Doctrine/ODM/MongoDB/DocumentManager.php | 5 +- .../ODM/MongoDB/InvalidArgumentException.php | 27 +- .../Proxy/Factory/AwesomeProxyFactory.php | 196 --------- .../Factory/CopiedLazyGhostProxyFactory.php | 380 ++++++++++++++++++ .../Proxy/Factory/LazyGhostProxyFactory.php | 234 +++++++++++ .../MongoDB/Proxy/Factory/ProxyFactory.php | 4 +- lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 2 + 9 files changed, 671 insertions(+), 212 deletions(-) delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/AwesomeProxyFactory.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/CopiedLazyGhostProxyFactory.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php diff --git a/composer.json b/composer.json index fdcf7c9a2d..cc074d78fc 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ "phpunit/phpunit": "^10.4", "squizlabs/php_codesniffer": "^3.5", "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0", "vimeo/psalm": "~5.24.0" }, "conflict": { diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index aef7fa05b7..07443947f6 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -82,6 +82,16 @@ class Configuration */ public const AUTOGENERATE_EVAL = 3; + /** + * Autogenerate the proxy class when the proxy file does not exist or + * when the proxied file changed. + * + * This strategy causes a file_exists() call whenever any proxy is used the + * first time in a request. When the proxied file is changed, the proxy will + * be updated. + */ + public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4; + /** * Array of attributes for this configuration instance. * @@ -608,6 +618,30 @@ public function isTransactionalFlushEnabled(): bool { return $this->useTransactionalFlush; } + + public function isLazyGhostObjectEnabled(): bool + { + return $this->isLazyGhostObjectEnabled; + } + + public function setLazyGhostObjectEnabled(bool $flag): void + { + if ($flag && ! trait_exists(LazyGhostTrait::class)) { + throw new \LogicException( + 'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library' + . ' version 6.4 or higher is not installed. Please run "composer require symfony/var-exporter:^6.4|^7".' + ); + } + + if ($flag && ! class_exists(RuntimeReflectionProperty::class)) { + throw new \LogicException( + 'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library' + . ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".' + ); + } + + $this->_attributes['isLazyGhostObjectEnabled'] = $flag; + } } interface_exists(MappingDriver::class); diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 5871efe48f..9947ec716a 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -9,7 +9,8 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Mapping\MappingException; -use Doctrine\ODM\MongoDB\Proxy\Factory\AwesomeProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\Factory\CopiedLazyGhostProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\Factory\LazyGhostProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver; @@ -183,7 +184,7 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory); $this->schemaManager = new SchemaManager($this, $this->metadataFactory); - $this->proxyFactory = new AwesomeProxyFactory($this); + $this->proxyFactory = new LazyGhostProxyFactory($this); $this->repositoryFactory = $this->config->getRepositoryFactory(); } diff --git a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php index b7795c54b0..a827b701f3 100644 --- a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php +++ b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php @@ -1,26 +1,19 @@ ; - -/** - * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR - */ -class extends \ implements \ -{ - - - /** - * @internal - */ - public bool $__isCloning = false; - - public function __construct(?\Closure $initializer = null) - { - self::createLazyGhost($initializer, , $this); - } - - public function __isInitialized(): bool - { - return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); - } - - public function __clone() - { - $this->__isCloning = true; - try { - $this->__doClone(); - } finally { - $this->__isCloning = false; - } - } - - public function __serialize(): array - { - - } -} - -EOPHP; - - private UnitOfWork $uow; - private LifecycleEventManager $lifecycleEventManager; - private LazyLoadingGhostFactory $proxyFactory; - - public function __construct(DocumentManager $documentManager) - { - $this->uow = $documentManager->getUnitOfWork(); - $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager()); - $this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory(); - } - - public function generateProxyClasses(array $classes): int - { - $concreteClasses = array_filter($classes, static fn (ClassMetadata $metadata): bool => ! ($metadata->isMappedSuperclass || $metadata->isQueryResultDocument || $metadata->getReflectionClass()->isAbstract())); - - foreach ($concreteClasses as $metadata) { - $this - ->proxyFactory - ->createProxy( - $metadata->getName(), - static fn (): bool => true, // empty closure, serves its purpose, for now - [ - 'skippedProperties' => $this->skippedFieldsFqns($metadata), - ], - ); - } - - return count($concreteClasses); - } - - /** - * @param class-string $className - * @param array $identifier - */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface - { - $className = $metadata->getName(); - $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); - - return $proxyFactory($identifier); - } - - private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void - { - $i = strrpos($proxyClassName, '\\'); - $placeholders = [ - '' => $class->getName(), - '' => substr($proxyClassName, 0, $i), - '' => substr($proxyClassName, 1 + $i), - '' => InternalProxy::class, - ]; - - preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); - - foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { - $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); - } - - $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); - - if (! $fileName) { - if (! class_exists($proxyClassName)) { - eval(substr($proxyCode, 5)); - } - - return; - } - - $parentDirectory = dirname($fileName); - - if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); - } - - if (! is_writable($parentDirectory)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); - } - - $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); - - file_put_contents($tmpFileName, $proxyCode); - @chmod($tmpFileName, 0664); - rename($tmpFileName, $fileName); - } - - /** - * @param ClassMetadata $metadata - * - * @return array - */ - private function skippedFieldsFqns(ClassMetadata $metadata): array - { - $idFieldFqcns = []; - - foreach ($metadata->getIdentifierFieldNames() as $idField) { - $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); - } - - return $idFieldFqcns; - } - - private function propertyFqcn(ReflectionProperty $property): string - { - if ($property->isPrivate()) { - return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); - } - - if ($property->isProtected()) { - return "\0*\0" . $property->getName(); - } - - return $property->getName(); - } -} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/CopiedLazyGhostProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/CopiedLazyGhostProxyFactory.php new file mode 100644 index 0000000000..3da223c616 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/CopiedLazyGhostProxyFactory.php @@ -0,0 +1,380 @@ +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ +{ + + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + /** The UnitOfWork this factory uses to retrieve persisters */ + private readonly UnitOfWork $uow; + + private readonly Configuration $configuration; + + /** @var array */ + private array $proxyFactories = []; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given DocumentManager. + * + * @param DocumentManager $dm The DocumentManager the new factory works for. + */ + public function __construct( + private readonly DocumentManager $dm, + ) { + $this->uow = $dm->getUnitOfWork(); + $this->configuration = $dm->getConfiguration(); + } + + /** + * @param class-string $className + * @param array $identifier + * + * @return InternalProxy + */ + public function getProxy(ClassMetadata $metadata, mixed $identifier): GhostObjectInterface|InternalProxy + { + $className = $metadata->getName(); + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); + + return $proxyFactory($identifier); + } + + /** + * Generates proxy classes for all given classes. + * + * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the DocumentManager used + * by this factory is used. + * + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, string|null $proxyDir = null): int + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->getProxyDir()); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); + + $this->generateProxyClass($class, $proxyFileName, $proxyClassName); + + ++$generated; + } + + return $generated; + } + + protected function skipClass(ClassMetadata $metadata): bool + { + return $metadata->isMappedSuperclass + || $metadata->isEmbeddedDocument + || $metadata->getReflectionClass()->isAbstract(); + } + + /** + * Creates a closure capable of initializing a proxy + * + * @return Closure(InternalProxy, array):void + * + * @throws DocumentNotFoundException + */ + private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $documentPersister): Closure + { + return static function (InternalProxy $proxy, array $identifier) use ($documentPersister, $classMetadata): void { + $original = $documentPersister->loadById($identifier); + + if ($original === null) { + throw DocumentNotFoundException::fromClassNameAndIdentifier( + $classMetadata->getName(), + $identifier, + ); + } + + if ($proxy === $original) { + return; + } + + $class = $documentPersister->getClassMetadata(); + + foreach ($class->getReflectionProperties() as $property) { + if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) { + continue; + } + + $property->setValue($proxy, $property->getValue($original)); + } + }; + } + + private function getProxyFileName(string $className, string $baseDirectory): string + { + $baseDirectory = $baseDirectory ?: $this->getProxyDir(); + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + private function getProxyFactory(string $className): Closure + { + $skippedProperties = []; + $class = $this->dm->getClassMetadata($className); + $identifierFieldName = $class->getIdentifierFieldNames()[0]; + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); + + while ($reflector) { + foreach ($reflector->getProperties($filter) as $property) { + $name = $property->name; + + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { + continue; + } + + $prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : ''); + + $skippedProperties[$prefix . $name] = true; + } + + $filter = ReflectionProperty::IS_PRIVATE; + $reflector = $reflector->getParentClass(); + } + + $className = $class->getName(); // aliases and case sensitivity + $documentPersister = $this->uow->getDocumentPersister($className); + $initializer = $this->createLazyInitializer($class, $documentPersister); + $proxyClassName = $this->loadProxyClass($class); + $reflector = $class->getReflectionProperties()[$identifierFieldName]; + + $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $reflector): InternalProxy { + $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { + $initializer($object, $identifier); + }, $skippedProperties); + + $reflector->setValue($proxy, $identifier); + + return $proxy; + }, null, $proxyClassName); + + return $this->proxyFactories[$className] = $proxyFactory; + } + + private function loadProxyClass(ClassMetadata $class): string + { + $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); + + if (class_exists($proxyClassName, false)) { + return $proxyClassName; + } + + if ($this->configuration->getAutoGenerateProxyClasses() === Configuration::AUTOGENERATE_EVAL) { + $this->generateProxyClass($class, null, $proxyClassName); + + return $proxyClassName; + } + + $fileName = $this->getProxyFileName($class->getName(), $this->getProxyDir()); + + switch ($this->configuration->getAutoGenerateProxyClasses()) { + case Configuration::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: + if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { + break; + } + // no break + case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: + if (file_exists($fileName)) { + break; + } + // no break + case Configuration::AUTOGENERATE_ALWAYS: + $this->generateProxyClass($class, $fileName, $proxyClassName); + break; + } + + require $fileName; + + return $proxyClassName; + } + + private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void + { + $i = strrpos($proxyClassName, '\\'); + $placeholders = [ + '' => $class->getName(), + '' => substr($proxyClassName, 0, $i), + '' => substr($proxyClassName, 1 + $i), + '' => InternalProxy::class, + ]; + + preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); + + foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { + $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); + } + + $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); + + if (! $fileName) { + if (! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { + throw InvalidArgumentException::proxyDirectoryNotWritable($this->getProxyDir()); + } + + if (! is_writable($parentDirectory)) { + throw InvalidArgumentException::proxyDirectoryNotWritable($this->getProxyDir()); + } + + $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + private function generateUseLazyGhostTrait(ClassMetadata $class): string + { + $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); + $code = substr($code, 7 + (int) strpos($code, "\n{")); + $code = substr($code, 0, (int) strpos($code, "\n}")); + $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { + initializeLazyObject as private; + setLazyObjectAsInitialized as public __setInitialized; + isLazyObjectInitialized as private; + createLazyGhost as private; + resetLazyObject as private; + } + + public function __load(): void + { + $this->initializeLazyObject(); + } + '), $code); + + return $code; + } + + private function generateSerializeImpl(ClassMetadata $class): string + { + $reflector = $class->getReflectionClass(); + $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; + + $code = '$properties = ' . $properties . '; + unset($properties["\0" . self::class . "\0lazyObjectState"]); + + '; + + if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { + return $code . 'return $properties;'; + } + + return $code . '$data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data;'; + } + + private static function generateProxyClassName(string $className, string $proxyNamespace): string + { + return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); + } + + private function getProxyDir(): string + { + return $this->configuration->getProxyDir(); + } + + private function getProxyNs(): string + { + return $this->configuration->getProxyNamespace(); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php new file mode 100644 index 0000000000..3a03873d24 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php @@ -0,0 +1,234 @@ +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ +{ + + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + private UnitOfWork $uow; + private LifecycleEventManager $lifecycleEventManager; + private LazyLoadingGhostFactory $proxyFactory; + + public function __construct(DocumentManager $documentManager) + { + $this->uow = $documentManager->getUnitOfWork(); + $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager()); + $this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory(); + } + + /** + * @param mixed $identifier + * @psalm-param ClassMetadata $metadata + * + * @psalm-return T&GhostObjectInterface + * + * @template T of object + */ + public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface + { + $documentPersister = $this->uow->getDocumentPersister($metadata->getName()); + + $ghostObject = $this + ->proxyFactory + ->createProxy( + $metadata->getName(), + $this->createInitializer($metadata, $documentPersister), + [ + 'skippedProperties' => $this->skippedFieldsFqns($metadata), + ], + ); + + $metadata->setIdentifierValue($ghostObject, $identifier); + + return $ghostObject; + } + + public function createProxy( + string $className, + Closure $initializer, + array $proxyOptions = [] + ): InternalProxy { + + } + + + /** + * Generates proxy classes for all given classes. + * + * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the DocumentManager used + * by this factory is used. + * + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, string|null $proxyDir = null): int + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->getProxyDir()); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); + + $this->generateProxyClass($class, $proxyFileName, $proxyClassName); + + ++$generated; + } + + return $generated; + } + + public function generateProxyClasses(array $classes): int + { + $concreteClasses = array_filter($classes, static fn (ClassMetadata $metadata): bool => ! ($metadata->isMappedSuperclass || $metadata->isQueryResultDocument || $metadata->getReflectionClass()->isAbstract())); + + foreach ($concreteClasses as $metadata) { + $this + ->proxyFactory + ->createProxy( + $metadata->getName(), + static fn (): bool => true, // empty closure, serves its purpose, for now + [ + 'skippedProperties' => $this->skippedFieldsFqns($metadata), + ], + ); + } + + return count($concreteClasses); + } + + /** + * @param ClassMetadata $metadata + * @param DocumentPersister $documentPersister + * + * @psalm-return Closure( + * TDocument&GhostObjectInterface=, + * string=, + * array=, + * ?Closure=, + * array= + * ) : bool + * + * @template TDocument of object + */ + private function createInitializer( + ClassMetadata $metadata, + DocumentPersister $documentPersister, + ): Closure { + return function ( + InternalProxy $ghostObject, + string $method, // we don't care + array $parameters, // we don't care + &$initializer, + array $properties, // we currently do not use this + ) use ( + $metadata, + $documentPersister, + ): bool { + $originalInitializer = $initializer; + $initializer = null; + $identifier = $metadata->getIdentifierValue($ghostObject); + + try { + $document = $documentPersister->load(['_id' => $identifier], $ghostObject); + } catch (Throwable $exception) { + $initializer = $originalInitializer; + + throw $exception; + } + + if (! $document) { + $initializer = $originalInitializer; + + if (! $this->lifecycleEventManager->documentNotFound($ghostObject, $identifier)) { + throw DocumentNotFoundException::documentNotFound($metadata->getName(), $identifier); + } + } + + if ($ghostObject instanceof NotifyPropertyChanged) { + $ghostObject->addPropertyChangedListener($this->uow); + } + + return true; + }; + } + + /** + * @param ClassMetadata $metadata + * + * @return array + */ + private function skippedFieldsFqns(ClassMetadata $metadata): array + { + $idFieldFqcns = []; + + foreach ($metadata->getIdentifierFieldNames() as $idField) { + $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); + } + + return $idFieldFqcns; + } + + private function propertyFqcn(ReflectionProperty $property): string + { + if ($property->isPrivate()) { + return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); + } + + if ($property->isProtected()) { + return "\0*\0" . $property->getName(); + } + + return $property->getName(); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php index b726055d4c..5754ee4499 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php @@ -5,8 +5,8 @@ namespace Doctrine\ODM\MongoDB\Proxy\Factory; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use ProxyManager\Proxy\GhostObjectInterface; use Doctrine\ODM\MongoDB\Proxy\InternalProxy; +use ProxyManager\Proxy\GhostObjectInterface; interface ProxyFactory { @@ -24,5 +24,5 @@ public function generateProxyClasses(array $classes): int; * * @template T of object */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface; + public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface|InternalProxy; } diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index 774f55f6a6..001dceb176 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -19,6 +19,7 @@ use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\Utility\CollectionHelper; use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager; +use Doctrine\ORM\Proxy\InternalProxy; use Doctrine\Persistence\Mapping\ReflectionService; use Doctrine\Persistence\Mapping\RuntimeReflectionService; use Doctrine\Persistence\NotifyPropertyChanged; @@ -3078,6 +3079,7 @@ public function isUninitializedObject(object $obj): bool return match (true) { $obj instanceof GhostObjectInterface => $obj->isProxyInitialized() === false, $obj instanceof PersistentCollectionInterface => $obj->isInitialized() === false, + $obj instanceof Proxy\InternalProxy => $obj->__isInitialized() === false, default => false }; } From 691588990871ee5895483c117a203858a9ac2320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 18 Oct 2024 23:59:54 +0200 Subject: [PATCH 03/14] Remove proxy-manager-lts and its factory --- composer.json | 5 +- lib/Doctrine/ODM/MongoDB/Configuration.php | 109 ++------ lib/Doctrine/ODM/MongoDB/DocumentManager.php | 27 +- .../ODM/MongoDB/Hydrator/HydratorFactory.php | 6 +- .../ODM/MongoDB/InvalidArgumentException.php | 5 + .../ODM/MongoDB/Mapping/ClassMetadata.php | 6 +- lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php | 89 +++++++ .../Proxy/DefaultProxyClassNameResolver.php | 36 +++ .../Proxy/Factory/LazyGhostProxyFactory.php | 234 ------------------ .../Proxy/Factory/StaticProxyFactory.php | 169 ------------- .../ODM/MongoDB/Proxy/FileLocator.php | 25 -- ...yFactory.php => LazyGhostProxyFactory.php} | 163 ++++++++---- .../Proxy/{Factory => }/ProxyFactory.php | 10 +- .../Resolver/CachingClassNameResolver.php | 35 --- .../Proxy/Resolver/ClassNameResolver.php | 16 -- .../ProxyManagerClassNameResolver.php | 40 --- lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 7 +- .../ODM/MongoDB/Tests/DocumentManagerTest.php | 2 +- 18 files changed, 295 insertions(+), 689 deletions(-) create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php create mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/DefaultProxyClassNameResolver.php delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/FileLocator.php rename lib/Doctrine/ODM/MongoDB/Proxy/{Factory/CopiedLazyGhostProxyFactory.php => LazyGhostProxyFactory.php} (69%) rename lib/Doctrine/ODM/MongoDB/Proxy/{Factory => }/ProxyFactory.php (63%) delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Resolver/CachingClassNameResolver.php delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ProxyManagerClassNameResolver.php diff --git a/composer.json b/composer.json index cc074d78fc..ece00f6185 100644 --- a/composer.json +++ b/composer.json @@ -27,14 +27,13 @@ "doctrine/collections": "^1.5 || ^2.0", "doctrine/event-manager": "^1.0 || ^2.0", "doctrine/instantiator": "^1.1 || ^2", - "doctrine/persistence": "^3.2", - "friendsofphp/proxy-manager-lts": "^1.0", + "doctrine/persistence": "^3.2 || ^4.0", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", "mongodb/mongodb": "^1.17.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.2 || ^3.0", - "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0" + "symfony/var-dumper": "^6.4 || ^7.0" }, "require-dev": { "ext-bcmath": "*", diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index 07443947f6..0e096a92f5 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -15,7 +15,6 @@ use Doctrine\ODM\MongoDB\PersistentCollection\DefaultPersistentCollectionGenerator; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionFactory; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionGenerator; -use Doctrine\ODM\MongoDB\Proxy\FileLocator; use Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository; use Doctrine\ODM\MongoDB\Repository\DefaultRepositoryFactory; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; @@ -23,12 +22,10 @@ use Doctrine\ODM\MongoDB\Repository\RepositoryFactory; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\ObjectRepository; -use InvalidArgumentException; use MongoDB\Driver\WriteConcern; +use project\Controller\TodoController; use ProxyManager\Configuration as ProxyManagerConfiguration; use ProxyManager\Factory\LazyLoadingGhostFactory; -use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; -use ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; @@ -52,33 +49,31 @@ class Configuration { /** - * Never autogenerate a proxy/hydrator/persistent collection and rely that - * it was generated by some process before deployment. Copied from - * \Doctrine\Common\Proxy\AbstractProxyFactory. + * Never autogenerate a proxy and rely that it was generated by some + * process before deployment. */ public const AUTOGENERATE_NEVER = 0; /** - * Always generates a new proxy/hydrator/persistent collection in every request. + * Always generates a new proxy in every request. * * This is only sane during development. - * Copied from \Doctrine\Common\Proxy\AbstractProxyFactory. */ public const AUTOGENERATE_ALWAYS = 1; /** - * Autogenerate the proxy/hydrator/persistent collection class when the file does not exist. + * Autogenerate the proxy class when the proxy file does not exist. * - * This strategy causes a file exists call whenever any proxy/hydrator is used the - * first time in a request. Copied from \Doctrine\Common\Proxy\AbstractProxyFactory. + * This strategy causes a file_exists() call whenever any proxy is used the + * first time in a request. */ public const AUTOGENERATE_FILE_NOT_EXISTS = 2; /** - * Generate the proxy/hydrator/persistent collection classes using eval(). + * Generate the proxy classes using eval(). * - * This strategy is only sane for development. - * Copied from \Doctrine\Common\Proxy\AbstractProxyFactory. + * This strategy is only sane for development, and even then it gives me + * the creeps a little. */ public const AUTOGENERATE_EVAL = 3; @@ -116,6 +111,8 @@ class Configuration * persistentCollectionGenerator?: PersistentCollectionGenerator, * persistentCollectionDir?: string, * persistentCollectionNamespace?: string, + * proxyDir?: string, + * proxyNamespace?: string, * repositoryFactory?: RepositoryFactory * } */ @@ -123,15 +120,12 @@ class Configuration private ?CacheItemPoolInterface $metadataCache = null; - private ProxyManagerConfiguration $proxyManagerConfiguration; - private int $autoGenerateProxyClasses = self::AUTOGENERATE_EVAL; private bool $useTransactionalFlush = false; public function __construct() { - $this->proxyManagerConfiguration = new ProxyManagerConfiguration(); $this->setAutoGenerateProxyClasses(self::AUTOGENERATE_FILE_NOT_EXISTS); } @@ -258,14 +252,7 @@ public function setMetadataCache(CacheItemPoolInterface $cache): void */ public function setProxyDir(string $dir): void { - $this->getProxyManagerConfiguration()->setProxiesTargetDir($dir); - - // Recreate proxy generator to ensure its path was updated - if ($this->autoGenerateProxyClasses !== self::AUTOGENERATE_FILE_NOT_EXISTS) { - return; - } - - $this->setAutoGenerateProxyClasses($this->autoGenerateProxyClasses); + $this->attributes['proxyDir'] = $dir; } /** @@ -273,7 +260,7 @@ public function setProxyDir(string $dir): void */ public function getProxyDir(): ?string { - return $this->getProxyManagerConfiguration()->getProxiesTargetDir(); + return $this->attributes['proxyDir'] ?? null; } /** @@ -289,37 +276,27 @@ public function getAutoGenerateProxyClasses(): int * Sets an int flag that indicates whether proxy classes should always be regenerated * during each script execution. * - * @throws InvalidArgumentException If an invalid mode was given. + * @param bool|self::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER. */ - public function setAutoGenerateProxyClasses(int $mode): void + public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void { - $this->autoGenerateProxyClasses = $mode; - $proxyManagerConfig = $this->getProxyManagerConfiguration(); - - switch ($mode) { - case self::AUTOGENERATE_FILE_NOT_EXISTS: - $proxyManagerConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy( - new FileLocator($proxyManagerConfig->getProxiesTargetDir()), - )); - - break; - case self::AUTOGENERATE_EVAL: - $proxyManagerConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - - break; - default: - throw new InvalidArgumentException('Invalid proxy generation strategy given - only AUTOGENERATE_FILE_NOT_EXISTS and AUTOGENERATE_EVAL are supported.'); - } + $this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate; } - public function getProxyNamespace(): ?string + /** + * Gets the namespace where proxy classes reside. + */ + public function getProxyNamespace(): string|null { - return $this->getProxyManagerConfiguration()->getProxiesNamespace(); + return $this->attributes['proxyNamespace'] ?? null; } + /** + * Sets the namespace where proxy classes reside. + */ public function setProxyNamespace(string $ns): void { - $this->getProxyManagerConfiguration()->setProxiesNamespace($ns); + $this->attributes['proxyNamespace'] = $ns; } public function setHydratorDir(string $dir): void @@ -599,16 +576,6 @@ public function getPersistentCollectionGenerator(): PersistentCollectionGenerato return $this->attributes['persistentCollectionGenerator']; } - public function buildGhostObjectFactory(): LazyLoadingGhostFactory - { - return new LazyLoadingGhostFactory(clone $this->getProxyManagerConfiguration()); - } - - public function getProxyManagerConfiguration(): ProxyManagerConfiguration - { - return $this->proxyManagerConfiguration; - } - public function setUseTransactionalFlush(bool $useTransactionalFlush): void { $this->useTransactionalFlush = $useTransactionalFlush; @@ -618,30 +585,6 @@ public function isTransactionalFlushEnabled(): bool { return $this->useTransactionalFlush; } - - public function isLazyGhostObjectEnabled(): bool - { - return $this->isLazyGhostObjectEnabled; - } - - public function setLazyGhostObjectEnabled(bool $flag): void - { - if ($flag && ! trait_exists(LazyGhostTrait::class)) { - throw new \LogicException( - 'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library' - . ' version 6.4 or higher is not installed. Please run "composer require symfony/var-exporter:^6.4|^7".' - ); - } - - if ($flag && ! class_exists(RuntimeReflectionProperty::class)) { - throw new \LogicException( - 'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library' - . ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".' - ); - } - - $this->_attributes['isLazyGhostObjectEnabled'] = $flag; - } } interface_exists(MappingDriver::class); diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 9947ec716a..3d75238af0 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -9,13 +9,14 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Proxy\DefaultProxyClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\Factory\CopiedLazyGhostProxyFactory; -use Doctrine\ODM\MongoDB\Proxy\Factory\LazyGhostProxyFactory; -use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory; use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; +use Doctrine\ODM\MongoDB\Proxy\LazyGhostProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\ProxyManagerClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver; -use Doctrine\ODM\MongoDB\Proxy\Resolver\ClassNameResolver; -use Doctrine\ODM\MongoDB\Proxy\Resolver\ProxyManagerClassNameResolver; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Repository\GridFSRepository; @@ -31,10 +32,8 @@ use MongoDB\Database; use MongoDB\Driver\ReadPreference; use MongoDB\GridFS\Bucket; -use ProxyManager\Proxy\GhostObjectInterface; use RuntimeException; use Throwable; - use function array_search; use function assert; use function gettype; @@ -135,7 +134,6 @@ class DocumentManager implements ObjectManager */ private ?FilterCollection $filterCollection = null; - /** @var ProxyClassNameResolver&ClassNameResolver */ private ProxyClassNameResolver $classNameResolver; private static ?string $version = null; @@ -159,7 +157,7 @@ protected function __construct(?Client $client = null, ?Configuration $config = ], ); - $this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config)); + $this->classNameResolver = new DefaultProxyClassNameResolver(); $metadataFactoryClassName = $this->config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName(); @@ -184,7 +182,12 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory); $this->schemaManager = new SchemaManager($this, $this->metadataFactory); - $this->proxyFactory = new LazyGhostProxyFactory($this); + $this->proxyFactory = new LazyGhostProxyFactory( + $this, + $config->getProxyDir(), + $config->getProxyNamespace(), + $config->getAutoGenerateProxyClasses(), + ); $this->repositoryFactory = $this->config->getRepositoryFactory(); } @@ -281,7 +284,7 @@ public function getSchemaManager(): SchemaManager * * @deprecated Fetch metadata for any class string (e.g. proxy object class) and read the class name from the metadata object */ - public function getClassNameResolver(): ClassNameResolver + public function getClassNameResolver(): ProxyClassNameResolver { return $this->classNameResolver; } @@ -597,7 +600,7 @@ public function flush(array $options = []): void * @param mixed $identifier * @psalm-param class-string $documentName * - * @psalm-return T|(T&GhostObjectInterface) + * @return T|(T&InternalProxy) * * @template T of object */ @@ -614,7 +617,7 @@ public function getReference(string $documentName, $identifier): object return $document; } - /** @psalm-var T&GhostObjectInterface $document */ + /** @psalm-var T&InternalProxy $document */ $document = $this->proxyFactory->getProxy($class, $identifier); $this->unitOfWork->registerManaged($document, $identifier, []); diff --git a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php index 52c1f3943e..51e5b13bde 100644 --- a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php @@ -13,7 +13,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\UnitOfWork; -use ProxyManager\Proxy\GhostObjectInterface; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use function array_key_exists; use function chmod; @@ -448,10 +448,10 @@ public function hydrate(object $document, array $data, array $hints = []): array } } - if ($document instanceof GhostObjectInterface && $document->getProxyInitializer() !== null) { + if ($document instanceof InternalProxy && $document->getProxyInitializer() !== null) { // Inject an empty initialiser to not load any object data $document->setProxyInitializer(static function ( - GhostObjectInterface $ghostObject, + InternalProxy $ghostObject, string $method, // we don't care array $parameters, // we don't care &$initializer, diff --git a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php index a827b701f3..a6adfe95b3 100644 --- a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php +++ b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php @@ -39,4 +39,9 @@ public static function missingPrimaryKeyValue(string $className, string $idField { return new self(sprintf('Missing value for primary key %s on %s', $idField, $className)); } + + public static function notAProxyClass(string $className, string $proxyNamespace): self + { + return new self(sprintf('The class %s is not a proxy class in namespace %s', $className, $proxyNamespace)); + } } diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index cd20698af2..e89027914b 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -23,7 +23,7 @@ use Doctrine\Persistence\Reflection\EnumReflectionProperty; use InvalidArgumentException; use LogicException; -use ProxyManager\Proxy\GhostObjectInterface; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use ReflectionClass; use ReflectionEnum; use ReflectionNamedType; @@ -1898,7 +1898,7 @@ public function getIdentifierObject(object $document) */ public function setFieldValue(object $document, string $field, $value): void { - if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) { + if ($document instanceof InternalProxy && ! $document->isProxyInitialized()) { //property changes to an uninitialized proxy will not be tracked or persisted, //so the proxy needs to be loaded first. $document->initializeProxy(); @@ -1914,7 +1914,7 @@ public function setFieldValue(object $document, string $field, $value): void */ public function getFieldValue(object $document, string $field) { - if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) { + if ($document instanceof InternalProxy && $field !== $this->identifier && ! $document->isProxyInitialized()) { $document->initializeProxy(); } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php new file mode 100644 index 0000000000..03d0002bb8 --- /dev/null +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php @@ -0,0 +1,89 @@ +resolveClassName($object::class); + } +} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php deleted file mode 100644 index 3a03873d24..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/LazyGhostProxyFactory.php +++ /dev/null @@ -1,234 +0,0 @@ -; - -/** - * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR - */ -class extends \ implements \ -{ - - - public function __isInitialized(): bool - { - return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); - } - - public function __serialize(): array - { - - } -} - -EOPHP; - - private UnitOfWork $uow; - private LifecycleEventManager $lifecycleEventManager; - private LazyLoadingGhostFactory $proxyFactory; - - public function __construct(DocumentManager $documentManager) - { - $this->uow = $documentManager->getUnitOfWork(); - $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager()); - $this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory(); - } - - /** - * @param mixed $identifier - * @psalm-param ClassMetadata $metadata - * - * @psalm-return T&GhostObjectInterface - * - * @template T of object - */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface - { - $documentPersister = $this->uow->getDocumentPersister($metadata->getName()); - - $ghostObject = $this - ->proxyFactory - ->createProxy( - $metadata->getName(), - $this->createInitializer($metadata, $documentPersister), - [ - 'skippedProperties' => $this->skippedFieldsFqns($metadata), - ], - ); - - $metadata->setIdentifierValue($ghostObject, $identifier); - - return $ghostObject; - } - - public function createProxy( - string $className, - Closure $initializer, - array $proxyOptions = [] - ): InternalProxy { - - } - - - /** - * Generates proxy classes for all given classes. - * - * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. - * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the - * directory configured on the Configuration of the DocumentManager used - * by this factory is used. - * - * @return int Number of generated proxies. - */ - public function generateProxyClasses(array $classes, string|null $proxyDir = null): int - { - $generated = 0; - - foreach ($classes as $class) { - if ($this->skipClass($class)) { - continue; - } - - $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->getProxyDir()); - $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); - - $this->generateProxyClass($class, $proxyFileName, $proxyClassName); - - ++$generated; - } - - return $generated; - } - - public function generateProxyClasses(array $classes): int - { - $concreteClasses = array_filter($classes, static fn (ClassMetadata $metadata): bool => ! ($metadata->isMappedSuperclass || $metadata->isQueryResultDocument || $metadata->getReflectionClass()->isAbstract())); - - foreach ($concreteClasses as $metadata) { - $this - ->proxyFactory - ->createProxy( - $metadata->getName(), - static fn (): bool => true, // empty closure, serves its purpose, for now - [ - 'skippedProperties' => $this->skippedFieldsFqns($metadata), - ], - ); - } - - return count($concreteClasses); - } - - /** - * @param ClassMetadata $metadata - * @param DocumentPersister $documentPersister - * - * @psalm-return Closure( - * TDocument&GhostObjectInterface=, - * string=, - * array=, - * ?Closure=, - * array= - * ) : bool - * - * @template TDocument of object - */ - private function createInitializer( - ClassMetadata $metadata, - DocumentPersister $documentPersister, - ): Closure { - return function ( - InternalProxy $ghostObject, - string $method, // we don't care - array $parameters, // we don't care - &$initializer, - array $properties, // we currently do not use this - ) use ( - $metadata, - $documentPersister, - ): bool { - $originalInitializer = $initializer; - $initializer = null; - $identifier = $metadata->getIdentifierValue($ghostObject); - - try { - $document = $documentPersister->load(['_id' => $identifier], $ghostObject); - } catch (Throwable $exception) { - $initializer = $originalInitializer; - - throw $exception; - } - - if (! $document) { - $initializer = $originalInitializer; - - if (! $this->lifecycleEventManager->documentNotFound($ghostObject, $identifier)) { - throw DocumentNotFoundException::documentNotFound($metadata->getName(), $identifier); - } - } - - if ($ghostObject instanceof NotifyPropertyChanged) { - $ghostObject->addPropertyChangedListener($this->uow); - } - - return true; - }; - } - - /** - * @param ClassMetadata $metadata - * - * @return array - */ - private function skippedFieldsFqns(ClassMetadata $metadata): array - { - $idFieldFqcns = []; - - foreach ($metadata->getIdentifierFieldNames() as $idField) { - $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); - } - - return $idFieldFqcns; - } - - private function propertyFqcn(ReflectionProperty $property): string - { - if ($property->isPrivate()) { - return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); - } - - if ($property->isProtected()) { - return "\0*\0" . $property->getName(); - } - - return $property->getName(); - } -} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php deleted file mode 100644 index 737795fe48..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php +++ /dev/null @@ -1,169 +0,0 @@ -uow = $documentManager->getUnitOfWork(); - $this->lifecycleEventManager = new LifecycleEventManager($documentManager, $this->uow, $documentManager->getEventManager()); - $this->proxyFactory = $documentManager->getConfiguration()->buildGhostObjectFactory(); - } - - /** - * @param mixed $identifier - * @psalm-param ClassMetadata $metadata - * - * @psalm-return T&GhostObjectInterface - * - * @template T of object - */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface - { - $documentPersister = $this->uow->getDocumentPersister($metadata->getName()); - - $ghostObject = $this - ->proxyFactory - ->createProxy( - $metadata->getName(), - $this->createInitializer($metadata, $documentPersister), - [ - 'skippedProperties' => $this->skippedFieldsFqns($metadata), - ], - ); - - $metadata->setIdentifierValue($ghostObject, $identifier); - - return $ghostObject; - } - - public function generateProxyClasses(array $classes): int - { - $concreteClasses = array_filter($classes, static fn (ClassMetadata $metadata): bool => ! ($metadata->isMappedSuperclass || $metadata->isQueryResultDocument || $metadata->getReflectionClass()->isAbstract())); - - foreach ($concreteClasses as $metadata) { - $this - ->proxyFactory - ->createProxy( - $metadata->getName(), - static fn (): bool => true, // empty closure, serves its purpose, for now - [ - 'skippedProperties' => $this->skippedFieldsFqns($metadata), - ], - ); - } - - return count($concreteClasses); - } - - /** - * @param ClassMetadata $metadata - * @param DocumentPersister $documentPersister - * - * @psalm-return Closure( - * TDocument&GhostObjectInterface=, - * string=, - * array=, - * ?Closure=, - * array= - * ) : bool - * - * @template TDocument of object - */ - private function createInitializer( - ClassMetadata $metadata, - DocumentPersister $documentPersister, - ): Closure { - return function ( - GhostObjectInterface $ghostObject, - string $method, // we don't care - array $parameters, // we don't care - &$initializer, - array $properties, // we currently do not use this - ) use ( - $metadata, - $documentPersister, - ): bool { - $originalInitializer = $initializer; - $initializer = null; - $identifier = $metadata->getIdentifierValue($ghostObject); - - try { - $document = $documentPersister->load(['_id' => $identifier], $ghostObject); - } catch (Throwable $exception) { - $initializer = $originalInitializer; - - throw $exception; - } - - if (! $document) { - $initializer = $originalInitializer; - - if (! $this->lifecycleEventManager->documentNotFound($ghostObject, $identifier)) { - throw DocumentNotFoundException::documentNotFound($metadata->getName(), $identifier); - } - } - - if ($ghostObject instanceof NotifyPropertyChanged) { - $ghostObject->addPropertyChangedListener($this->uow); - } - - return true; - }; - } - - /** - * @param ClassMetadata $metadata - * - * @return array - */ - private function skippedFieldsFqns(ClassMetadata $metadata): array - { - $idFieldFqcns = []; - - foreach ($metadata->getIdentifierFieldNames() as $idField) { - $idFieldFqcns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); - } - - return $idFieldFqcns; - } - - private function propertyFqcn(ReflectionProperty $property): string - { - if ($property->isPrivate()) { - return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName(); - } - - if ($property->isProtected()) { - return "\0*\0" . $property->getName(); - } - - return $property->getName(); - } -} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/FileLocator.php b/lib/Doctrine/ODM/MongoDB/Proxy/FileLocator.php deleted file mode 100644 index 516b4ef6e9..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/FileLocator.php +++ /dev/null @@ -1,25 +0,0 @@ - */ private array $proxyFactories = []; /** * Initializes a new instance of the ProxyFactory class that is - * connected to the given DocumentManager. + * connected to the given EntityManager. * - * @param DocumentManager $dm The DocumentManager the new factory works for. + * @param DocumentManager $dm The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. */ public function __construct( private readonly DocumentManager $dm, + private readonly string $proxyDir, + private readonly string $proxyNs, + bool|int $autoGenerate = self::AUTOGENERATE_NEVER, ) { - $this->uow = $dm->getUnitOfWork(); - $this->configuration = $dm->getConfiguration(); + if (! $proxyDir) { + throw InvalidArgumentException::proxyDirectoryRequired(); + } + + if (! $proxyNs) { + throw InvalidArgumentException::proxyNamespaceRequired(); + } + + if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) { + throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate); + } + + $this->uow = $dm->getUnitOfWork(); + $this->autoGenerate = (int) $autoGenerate; } - /** - * @param class-string $className - * @param array $identifier - * - * @return InternalProxy - */ - public function getProxy(ClassMetadata $metadata, mixed $identifier): GhostObjectInterface|InternalProxy + /** @param array $identifier */ + public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy { - $className = $metadata->getName(); + $className = $metadata->getName(); + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); return $proxyFactory($identifier); @@ -113,7 +169,7 @@ public function getProxy(ClassMetadata $metadata, mixed $identifier): GhostObjec * * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the - * directory configured on the Configuration of the DocumentManager used + * directory configured on the Configuration of the EntityManager used * by this factory is used. * * @return int Number of generated proxies. @@ -127,8 +183,8 @@ public function generateProxyClasses(array $classes, string|null $proxyDir = nul continue; } - $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->getProxyDir()); - $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->proxyDir); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); $this->generateProxyClass($class, $proxyFileName, $proxyClassName); @@ -141,7 +197,7 @@ public function generateProxyClasses(array $classes, string|null $proxyDir = nul protected function skipClass(ClassMetadata $metadata): bool { return $metadata->isMappedSuperclass - || $metadata->isEmbeddedDocument + || $metadata->isEmbeddedClass || $metadata->getReflectionClass()->isAbstract(); } @@ -182,7 +238,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPer private function getProxyFileName(string $className, string $baseDirectory): string { - $baseDirectory = $baseDirectory ?: $this->getProxyDir(); + $baseDirectory = $baseDirectory ?: $this->proxyDir; return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER . str_replace('\\', '', $className) . '.php'; @@ -190,11 +246,11 @@ private function getProxyFileName(string $className, string $baseDirectory): str private function getProxyFactory(string $className): Closure { - $skippedProperties = []; - $class = $this->dm->getClassMetadata($className); - $identifierFieldName = $class->getIdentifierFieldNames()[0]; - $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; - $reflector = $class->getReflectionClass(); + $skippedProperties = []; + $class = $this->dm->getClassMetadata($className); + $identifiers = array_flip($class->getIdentifierFieldNames()); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); while ($reflector) { foreach ($reflector->getProperties($filter) as $property) { @@ -213,18 +269,25 @@ private function getProxyFactory(string $className): Closure $reflector = $reflector->getParentClass(); } - $className = $class->getName(); // aliases and case sensitivity - $documentPersister = $this->uow->getDocumentPersister($className); - $initializer = $this->createLazyInitializer($class, $documentPersister); - $proxyClassName = $this->loadProxyClass($class); - $reflector = $class->getReflectionProperties()[$identifierFieldName]; + $className = $class->getName(); // aliases and case sensitivity + $entityPersister = $this->uow->getDocumentPersister($className); + $initializer = $this->createLazyInitializer($class, $entityPersister); + $proxyClassName = $this->loadProxyClass($class); - $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $reflector): InternalProxy { + $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, , $className): InternalProxy { $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { $initializer($object, $identifier); }, $skippedProperties); - $reflector->setValue($proxy, $identifier); + + foreach ($identifierFields as $idField => $reflector) { + if (! isset($identifier[$idField])) { + throw InvalidArgumentException::missingPrimaryKeyValue($className, $idField); + } + + assert($reflector !== null); + $reflector->setValue($proxy, $identifier[$idField]); + } return $proxy; }, null, $proxyClassName); @@ -234,32 +297,32 @@ private function getProxyFactory(string $className): Closure private function loadProxyClass(ClassMetadata $class): string { - $proxyClassName = self::generateProxyClassName($class->getName(), $this->getProxyNs()); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); if (class_exists($proxyClassName, false)) { return $proxyClassName; } - if ($this->configuration->getAutoGenerateProxyClasses() === Configuration::AUTOGENERATE_EVAL) { + if ($this->autoGenerate === self::AUTOGENERATE_EVAL) { $this->generateProxyClass($class, null, $proxyClassName); return $proxyClassName; } - $fileName = $this->getProxyFileName($class->getName(), $this->getProxyDir()); + $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); - switch ($this->configuration->getAutoGenerateProxyClasses()) { - case Configuration::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: + switch ($this->autoGenerate) { + case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { break; } // no break - case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: + case self::AUTOGENERATE_FILE_NOT_EXISTS: if (file_exists($fileName)) { break; } // no break - case Configuration::AUTOGENERATE_ALWAYS: + case self::AUTOGENERATE_ALWAYS: $this->generateProxyClass($class, $fileName, $proxyClassName); break; } @@ -298,11 +361,11 @@ private function generateProxyClass(ClassMetadata $class, string|null $fileName, $parentDirectory = dirname($fileName); if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->getProxyDir()); + throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); } if (! is_writable($parentDirectory)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->getProxyDir()); + throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); } $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); @@ -367,14 +430,4 @@ private static function generateProxyClassName(string $className, string $proxyN { return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); } - - private function getProxyDir(): string - { - return $this->configuration->getProxyDir(); - } - - private function getProxyNs(): string - { - return $this->configuration->getProxyNamespace(); - } } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php similarity index 63% rename from lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php rename to lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php index 5754ee4499..da5601f1c4 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Factory/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php @@ -2,15 +2,13 @@ declare(strict_types=1); -namespace Doctrine\ODM\MongoDB\Proxy\Factory; +namespace Doctrine\ODM\MongoDB\Proxy; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; -use ProxyManager\Proxy\GhostObjectInterface; interface ProxyFactory { - /** @param ClassMetadata[] $classes */ + /** @param ClassMetadata $classes */ public function generateProxyClasses(array $classes): int; /** @@ -20,9 +18,9 @@ public function generateProxyClasses(array $classes): int; * @param mixed $identifier * @psalm-param ClassMetadata $metadata * - * @return T&(GhostObjectInterface|InternalProxy) + * @return T&InternalProxy * * @template T of object */ - public function getProxy(ClassMetadata $metadata, $identifier): GhostObjectInterface|InternalProxy; + public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy; } diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/CachingClassNameResolver.php b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/CachingClassNameResolver.php deleted file mode 100644 index 8c2879ed34..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/CachingClassNameResolver.php +++ /dev/null @@ -1,35 +0,0 @@ - */ - private array $resolvedNames = []; - - public function __construct(private ProxyClassNameResolver $resolver) - { - } - - /** - * Gets the real class name of a class name that could be a proxy. - */ - public function getRealClass(string $class): string - { - return $this->resolveClassName($class); - } - - public function resolveClassName(string $className): string - { - if (! isset($this->resolvedNames[$className])) { - $this->resolvedNames[$className] = $this->resolver->resolveClassName($className); - } - - return $this->resolvedNames[$className]; - } -} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php b/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php deleted file mode 100644 index 36ffaafc59..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Resolver/ClassNameResolver.php +++ /dev/null @@ -1,16 +0,0 @@ -resolveClassName($class); - } - - /** - * @psalm-param class-string|class-string> $className - * - * @psalm-return class-string - * - * @psalm-template RealClassName of object - */ - public function resolveClassName(string $className): string - { - return $this->getClassNameInflector()->getUserClassName($className); - } - - private function getClassNameInflector(): ClassNameInflectorInterface - { - return $this->configuration->getProxyManagerConfiguration()->getClassNameInflector(); - } -} diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index 001dceb176..ccdbe99b50 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -28,7 +28,6 @@ use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Session; use MongoDB\Driver\WriteConcern; -use ProxyManager\Proxy\GhostObjectInterface; use ReflectionProperty; use Throwable; use UnexpectedValueException; @@ -972,7 +971,7 @@ private function computeAssociationChanges(object $parentDocument, array $assoc, $class = $this->dm->getClassMetadata($parentDocument::class); $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument); - if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) { + if ($value instanceof InternalProxy && ! $value->isProxyInitialized()) { return; } @@ -3062,7 +3061,7 @@ public function getScheduledCollectionUpdates(): array */ public function initializeObject(object $obj): void { - if ($obj instanceof GhostObjectInterface && $obj->isProxyInitialized() === false) { + if ($obj instanceof InternalProxy && $obj->isProxyInitialized() === false) { $obj->initializeProxy(); } elseif ($obj instanceof PersistentCollectionInterface) { $obj->initialize(); @@ -3077,7 +3076,7 @@ public function initializeObject(object $obj): void public function isUninitializedObject(object $obj): bool { return match (true) { - $obj instanceof GhostObjectInterface => $obj->isProxyInitialized() === false, + $obj instanceof InternalProxy => $obj->isProxyInitialized() === false, $obj instanceof PersistentCollectionInterface => $obj->isInitialized() === false, $obj instanceof Proxy\InternalProxy => $obj->__isInitialized() === false, default => false diff --git a/tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php index 832037e53c..5ce2a53c1c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/DocumentManagerTest.php @@ -13,7 +13,7 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory; use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\ODM\MongoDB\MongoDBException; -use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; use Doctrine\ODM\MongoDB\Query\Builder as QueryBuilder; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\SchemaManager; From 2bcf197dff33487f1f5a215c72e92db63633c07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 01:25:58 +0200 Subject: [PATCH 04/14] Use the InternalProxy methods --- lib/Doctrine/ODM/MongoDB/Configuration.php | 3 -- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 5 +-- .../ODM/MongoDB/Hydrator/HydratorFactory.php | 18 +++-------- .../ODM/MongoDB/Mapping/ClassMetadata.php | 10 +++--- .../ODM/MongoDB/Proxy/InternalProxy.php | 2 ++ .../MongoDB/Proxy/LazyGhostProxyFactory.php | 32 +++++++------------ lib/Doctrine/ODM/MongoDB/UnitOfWork.php | 15 +++++---- .../Tests/Mapping/ClassMetadataTest.php | 11 ++++--- 8 files changed, 37 insertions(+), 59 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index 0e096a92f5..aef85a97f2 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -23,9 +23,6 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\ObjectRepository; use MongoDB\Driver\WriteConcern; -use project\Controller\TodoController; -use ProxyManager\Configuration as ProxyManagerConfiguration; -use ProxyManager\Factory\LazyLoadingGhostFactory; use Psr\Cache\CacheItemPoolInterface; use ReflectionClass; diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 3d75238af0..2fb31ed828 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -10,13 +10,9 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface; use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\ODM\MongoDB\Proxy\DefaultProxyClassNameResolver; -use Doctrine\ODM\MongoDB\Proxy\Factory\CopiedLazyGhostProxyFactory; -use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory; use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Proxy\LazyGhostProxyFactory; use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; -use Doctrine\ODM\MongoDB\Proxy\ProxyManagerClassNameResolver; -use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Repository\GridFSRepository; @@ -34,6 +30,7 @@ use MongoDB\GridFS\Bucket; use RuntimeException; use Throwable; + use function array_search; use function assert; use function gettype; diff --git a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php index 51e5b13bde..3603ae4497 100644 --- a/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php @@ -11,9 +11,9 @@ use Doctrine\ODM\MongoDB\Event\PreLoadEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\UnitOfWork; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use function array_key_exists; use function chmod; @@ -448,19 +448,9 @@ public function hydrate(object $document, array $data, array $hints = []): array } } - if ($document instanceof InternalProxy && $document->getProxyInitializer() !== null) { - // Inject an empty initialiser to not load any object data - $document->setProxyInitializer(static function ( - InternalProxy $ghostObject, - string $method, // we don't care - array $parameters, // we don't care - &$initializer, - array $properties, // we currently do not use this - ): bool { - $initializer = null; - - return true; - }); + if ($document instanceof InternalProxy) { + // Skip initialization to not load any object data + $document->__setInitialized(true); } $data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints); diff --git a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php index e89027914b..a581c54b74 100644 --- a/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php @@ -13,6 +13,7 @@ use Doctrine\Instantiator\InstantiatorInterface; use Doctrine\ODM\MongoDB\Id\IdGenerator; use Doctrine\ODM\MongoDB\LockException; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Types\Incrementable; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\Types\Versionable; @@ -23,7 +24,6 @@ use Doctrine\Persistence\Reflection\EnumReflectionProperty; use InvalidArgumentException; use LogicException; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use ReflectionClass; use ReflectionEnum; use ReflectionNamedType; @@ -1898,10 +1898,10 @@ public function getIdentifierObject(object $document) */ public function setFieldValue(object $document, string $field, $value): void { - if ($document instanceof InternalProxy && ! $document->isProxyInitialized()) { + if ($document instanceof InternalProxy && ! $document->__isInitialized()) { //property changes to an uninitialized proxy will not be tracked or persisted, //so the proxy needs to be loaded first. - $document->initializeProxy(); + $document->__load(); } $this->reflFields[$field]->setValue($document, $value); @@ -1914,8 +1914,8 @@ public function setFieldValue(object $document, string $field, $value): void */ public function getFieldValue(object $document, string $field) { - if ($document instanceof InternalProxy && $field !== $this->identifier && ! $document->isProxyInitialized()) { - $document->initializeProxy(); + if ($document instanceof InternalProxy && $field !== $this->identifier && ! $document->__isInitialized()) { + $document->__load(); } return $this->reflFields[$field]->getValue($document); diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php index 36c159269e..90af928ca8 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/InternalProxy.php @@ -1,5 +1,7 @@ uow = $dm->getUnitOfWork(); - $this->autoGenerate = (int) $autoGenerate; + $this->autoGenerate = self::AUTOGENERATE_ALWAYS; // @todo (int) $autoGenerate; } /** @param array $identifier */ @@ -210,11 +208,11 @@ protected function skipClass(ClassMetadata $metadata): bool */ private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $documentPersister): Closure { - return static function (InternalProxy $proxy, array $identifier) use ($documentPersister, $classMetadata): void { - $original = $documentPersister->loadById($identifier); + return static function (InternalProxy $proxy, mixed $identifier) use ($documentPersister, $classMetadata): void { + $original = $documentPersister->load(['_id' => $identifier]); if ($original === null) { - throw DocumentNotFoundException::fromClassNameAndIdentifier( + throw DocumentNotFoundException::documentNotFound( $classMetadata->getName(), $identifier, ); @@ -256,7 +254,7 @@ private function getProxyFactory(string $className): Closure foreach ($reflector->getProperties($filter) as $property) { $name = $property->name; - if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)))) { continue; } @@ -269,25 +267,17 @@ private function getProxyFactory(string $className): Closure $reflector = $reflector->getParentClass(); } - $className = $class->getName(); // aliases and case sensitivity - $entityPersister = $this->uow->getDocumentPersister($className); - $initializer = $this->createLazyInitializer($class, $entityPersister); - $proxyClassName = $this->loadProxyClass($class); + $className = $class->getName(); // aliases and case sensitivity + $entityPersister = $this->uow->getDocumentPersister($className); + $initializer = $this->createLazyInitializer($class, $entityPersister); + $proxyClassName = $this->loadProxyClass($class); - $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, , $className): InternalProxy { + $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $class): InternalProxy { $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { $initializer($object, $identifier); }, $skippedProperties); - - foreach ($identifierFields as $idField => $reflector) { - if (! isset($identifier[$idField])) { - throw InvalidArgumentException::missingPrimaryKeyValue($className, $idField); - } - - assert($reflector !== null); - $reflector->setValue($proxy, $identifier[$idField]); - } + $class->setIdentifierValue($proxy, $identifier); return $proxy; }, null, $proxyClassName); diff --git a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php index ccdbe99b50..7b0909f950 100644 --- a/lib/Doctrine/ODM/MongoDB/UnitOfWork.php +++ b/lib/Doctrine/ODM/MongoDB/UnitOfWork.php @@ -14,16 +14,17 @@ use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; use Doctrine\ODM\MongoDB\Persisters\CollectionPersister; use Doctrine\ODM\MongoDB\Persisters\PersistenceBuilder; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; use Doctrine\ODM\MongoDB\Types\DateType; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\Utility\CollectionHelper; use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager; -use Doctrine\ORM\Proxy\InternalProxy; use Doctrine\Persistence\Mapping\ReflectionService; use Doctrine\Persistence\Mapping\RuntimeReflectionService; use Doctrine\Persistence\NotifyPropertyChanged; use Doctrine\Persistence\PropertyChangedListener; +use Doctrine\Persistence\Proxy; use InvalidArgumentException; use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Session; @@ -971,7 +972,7 @@ private function computeAssociationChanges(object $parentDocument, array $assoc, $class = $this->dm->getClassMetadata($parentDocument::class); $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument); - if ($value instanceof InternalProxy && ! $value->isProxyInitialized()) { + if ($value instanceof Proxy && ! $value->__isInitialized()) { return; } @@ -2781,7 +2782,8 @@ public function getOrCreateDocument(string $className, array $data, array &$hint $document = $this->identityMap[$class->name][$serializedId]; $oid = spl_object_hash($document); if ($this->isUninitializedObject($document)) { - $document->setProxyInitializer(null); + assert($document instanceof InternalProxy); + $document->__setInitialized(true); $overrideLocalValues = true; if ($document instanceof NotifyPropertyChanged) { $document->addPropertyChangedListener($this); @@ -3061,8 +3063,8 @@ public function getScheduledCollectionUpdates(): array */ public function initializeObject(object $obj): void { - if ($obj instanceof InternalProxy && $obj->isProxyInitialized() === false) { - $obj->initializeProxy(); + if ($obj instanceof Proxy && $obj->__isInitialized() === false) { + $obj->__load(); } elseif ($obj instanceof PersistentCollectionInterface) { $obj->initialize(); } @@ -3076,9 +3078,8 @@ public function initializeObject(object $obj): void public function isUninitializedObject(object $obj): bool { return match (true) { - $obj instanceof InternalProxy => $obj->isProxyInitialized() === false, $obj instanceof PersistentCollectionInterface => $obj->isInitialized() === false, - $obj instanceof Proxy\InternalProxy => $obj->__isInitialized() === false, + $obj instanceof Proxy => $obj->__isInitialized() === false, default => false }; } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index adf80fb6a6..fdebe7bd9b 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -8,6 +8,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil; @@ -36,7 +37,6 @@ use Generator; use InvalidArgumentException; use PHPUnit\Framework\Attributes\DataProvider; -use ProxyManager\Proxy\GhostObjectInterface; use ReflectionClass; use ReflectionException; use stdClass; @@ -493,7 +493,7 @@ public function testGetFieldValueInitializesProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getName(), $metadata->getFieldValue($proxy, 'name')); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(InternalProxy::class, $proxy); self::assertFalse($this->uow->isUninitializedObject($proxy)); } @@ -508,7 +508,7 @@ public function testGetFieldValueOfIdentifierDoesNotInitializeProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getId(), $metadata->getFieldValue($proxy, 'id')); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(InternalProxy::class, $proxy); self::assertTrue($this->uow->isUninitializedObject($proxy)); } @@ -530,7 +530,7 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(InternalProxy::class, $proxy); $metadata = $this->dm->getClassMetadata(Album::class); $metadata->setFieldValue($proxy, 'name', 'nevermind'); @@ -539,7 +539,8 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(InternalProxy::class, $proxy); + self::assertInstanceOf(Album::class, $proxy); self::assertEquals('nevermind', $proxy->getName()); } From 8079f538f11a81c4bfad6e266e60d405dbb28b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 01:42:05 +0200 Subject: [PATCH 05/14] Fix tests --- .../Tests/Functional/IdentifiersTest.php | 6 +-- .../Tests/Functional/ReferencePrimerTest.php | 48 +++++++++---------- .../Tests/Functional/ReferencesTest.php | 28 ++++++----- .../Tests/Functional/SimpleReferencesTest.php | 6 +-- .../Tests/Functional/Ticket/GH520Test.php | 4 +- .../Tests/Functional/Ticket/GH593Test.php | 8 ++-- .../Tests/Functional/Ticket/GH602Test.php | 8 ++-- .../Tests/Functional/Ticket/GH852Test.php | 8 ++-- .../Tests/Functional/Ticket/GH936Test.php | 4 +- .../ODM/MongoDB/Tests/Functional/ViewTest.php | 6 +-- .../ODM/MongoDB/Tests/HydratorTest.php | 10 ++-- .../Proxy/Factory/StaticProxyFactoryTest.php | 4 +- .../ODM/MongoDB/Tests/UnitOfWorkTest.php | 4 +- 13 files changed, 74 insertions(+), 70 deletions(-) diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php index 66cec01810..0b60b44874 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php @@ -4,10 +4,10 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Event; use Documents\User; -use ProxyManager\Proxy\LazyLoadingInterface; use function assert; use function get_class; @@ -30,7 +30,7 @@ public function testGetIdentifierValue(): void $userTest = $test->getUser(); self::assertEquals($user->getId(), $userTest->getId()); - self::assertInstanceOf(LazyLoadingInterface::class, $userTest); + self::assertInstanceOf(InternalProxy::class, $userTest); self::assertTrue($this->uow->isUninitializedObject($userTest)); $this->dm->clear(); @@ -42,7 +42,7 @@ public function testGetIdentifierValue(): void $foundUser = $test->getUser(); self::assertEquals($user->getId(), $class->getIdentifierValue($user)); self::assertEquals($user->getId(), $class->getFieldValue($foundUser, 'id')); - self::assertInstanceOf(LazyLoadingInterface::class, $foundUser); + self::assertInstanceOf(InternalProxy::class, $foundUser); self::assertTrue($this->uow->isUninitializedObject($foundUser)); self::assertEquals('jwage', $foundUser->getUsername()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php index 44c2c5c40e..a8b96bf99c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php @@ -7,6 +7,7 @@ use DateTime; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil; @@ -33,7 +34,6 @@ use InvalidArgumentException; use MongoDB\Driver\ReadPreference; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; -use ProxyManager\Proxy\GhostObjectInterface; use function assert; use function func_get_args; @@ -95,7 +95,7 @@ public function testPrimeReferencesWithDBRefObjects(): void ->field('groups')->prime(true); foreach ($qb->getQuery() as $user) { - self::assertInstanceOf(GhostObjectInterface::class, $user->getAccount()); + self::assertInstanceOf(InternalProxy::class, $user->getAccount()); self::assertFalse($this->uow->isUninitializedObject($user->getAccount())); self::assertCount(2, $user->getGroups()); @@ -104,7 +104,7 @@ public function testPrimeReferencesWithDBRefObjects(): void * initialized, they will not be hydrated as proxy objects. */ foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertNotInstanceOf(InternalProxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -133,13 +133,13 @@ public function testPrimeReferencesWithSimpleReferences(): void ->field('users')->prime(true); foreach ($qb->getQuery() as $simpleUser) { - self::assertInstanceOf(GhostObjectInterface::class, $simpleUser->getUser()); + self::assertInstanceOf(InternalProxy::class, $simpleUser->getUser()); self::assertFalse($this->uow->isUninitializedObject($simpleUser->getUser())); self::assertCount(2, $simpleUser->getUsers()); foreach ($simpleUser->getUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertNotInstanceOf(InternalProxy::class, $user); self::assertInstanceOf(User::class, $user); } } @@ -188,20 +188,20 @@ public function testPrimeReferencesNestedInNamedEmbeddedReference(): void ->field('embeddedDocs.referencedDocs')->prime(true); foreach ($qb->getQuery() as $root) { - self::assertNotInstanceOf(GhostObjectInterface::class, $root->embeddedDoc); + self::assertNotInstanceOf(InternalProxy::class, $root->embeddedDoc); self::assertInstanceOf(EmbeddedWhichReferences::class, $root->embeddedDoc); self::assertCount(2, $root->embeddedDocs); foreach ($root->embeddedDocs as $embeddedDoc) { - self::assertNotInstanceOf(GhostObjectInterface::class, $embeddedDoc); + self::assertNotInstanceOf(InternalProxy::class, $embeddedDoc); self::assertInstanceOf(EmbeddedWhichReferences::class, $embeddedDoc); - self::assertInstanceOf(GhostObjectInterface::class, $embeddedDoc->referencedDoc); + self::assertInstanceOf(InternalProxy::class, $embeddedDoc->referencedDoc); self::assertFalse($this->uow->isUninitializedObject($embeddedDoc->referencedDoc)); self::assertCount(2, $embeddedDoc->referencedDocs); foreach ($embeddedDoc->referencedDocs as $referencedDoc) { - self::assertNotInstanceOf(GhostObjectInterface::class, $referencedDoc); + self::assertNotInstanceOf(InternalProxy::class, $referencedDoc); self::assertInstanceOf(Reference::class, $referencedDoc); } } @@ -252,37 +252,37 @@ public function testPrimeReferencesWithDifferentStoreAsReferences(): void assert($referenceUser instanceof ReferenceUser); $user = $referenceUser->getUser(); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertInstanceOf(InternalProxy::class, $user); self::assertFalse($this->uow->isUninitializedObject($user)); self::assertCount(1, $referenceUser->getUsers()); foreach ($referenceUser->getUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertNotInstanceOf(InternalProxy::class, $user); self::assertInstanceOf(User::class, $user); } $parentUser = $referenceUser->getParentUser(); - self::assertInstanceOf(GhostObjectInterface::class, $parentUser); + self::assertInstanceOf(InternalProxy::class, $parentUser); self::assertInstanceOf(User::class, $parentUser); self::assertFalse($this->uow->isUninitializedObject($parentUser)); self::assertCount(1, $referenceUser->getParentUsers()); foreach ($referenceUser->getParentUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertNotInstanceOf(InternalProxy::class, $user); self::assertInstanceOf(User::class, $user); } $otherUser = $referenceUser->getOtherUser(); self::assertInstanceOf(User::class, $otherUser); - self::assertInstanceOf(GhostObjectInterface::class, $otherUser); + self::assertInstanceOf(InternalProxy::class, $otherUser); self::assertFalse($this->uow->isUninitializedObject($otherUser)); self::assertCount(1, $referenceUser->getOtherUsers()); foreach ($referenceUser->getOtherUsers() as $user) { - self::assertNotInstanceOf(GhostObjectInterface::class, $user); + self::assertNotInstanceOf(InternalProxy::class, $user); self::assertInstanceOf(User::class, $user); } } @@ -309,10 +309,10 @@ public function testPrimeReferencesWithDiscriminatedReferenceMany(): void foreach ($qb->getQuery() as $user) { $favorites = $user->getFavorites()->toArray(); - self::assertNotInstanceOf(GhostObjectInterface::class, $favorites[0]); + self::assertNotInstanceOf(InternalProxy::class, $favorites[0]); self::assertInstanceOf(Group::class, $favorites[0]); - self::assertNotInstanceOf(GhostObjectInterface::class, $favorites[1]); + self::assertNotInstanceOf(InternalProxy::class, $favorites[1]); self::assertInstanceOf(Project::class, $favorites[1]); } } @@ -331,7 +331,7 @@ public function testPrimeReferencesWithDiscriminatedReferenceOne(): void ->field('server')->prime(true); foreach ($qb->getQuery() as $agent) { - self::assertInstanceOf(GhostObjectInterface::class, $agent->server); + self::assertInstanceOf(InternalProxy::class, $agent->server); self::assertFalse($this->uow->isUninitializedObject($agent->server)); } } @@ -360,7 +360,7 @@ public function testPrimeReferencesIgnoresInitializedProxyObjects(): void self::assertCount(2, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertNotInstanceOf(InternalProxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -440,7 +440,7 @@ public function testPrimeReferencesInFindAndModifyResult(): void self::assertCount(1, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(GhostObjectInterface::class, $group); + self::assertNotInstanceOf(InternalProxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -472,7 +472,7 @@ public function testPrimeEmbeddedReferenceOneLevelDeep(): void $phonenumber = $phonenumbers->current(); - self::assertNotInstanceOf(GhostObjectInterface::class, $phonenumber); + self::assertNotInstanceOf(InternalProxy::class, $phonenumber); self::assertInstanceOf(Phonenumber::class, $phonenumber); } @@ -523,7 +523,7 @@ public function testPrimeEmbeddedReferenceTwoLevelsDeep(): void $currency = $money->getCurrency(); - self::assertInstanceOf(GhostObjectInterface::class, $currency); + self::assertInstanceOf(InternalProxy::class, $currency); self::assertInstanceOf(Currency::class, $currency); self::assertFalse($this->uow->isUninitializedObject($currency)); } @@ -551,7 +551,7 @@ public function testPrimeReferencesInReferenceMany(): void self::assertInstanceOf(BlogPost::class, $post); $comment = $post->comments->first(); - self::assertInstanceOf(GhostObjectInterface::class, $comment->author); + self::assertInstanceOf(InternalProxy::class, $comment->author); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } @@ -578,7 +578,7 @@ public function testPrimeReferencesInReferenceManyWithRepositoryMethodEager(): v self::assertInstanceOf(BlogPost::class, $post); $comment = $post->repoCommentsWithPrimer->first(); - self::assertInstanceOf(GhostObjectInterface::class, $comment->author); + self::assertInstanceOf(InternalProxy::class, $comment->author); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php index 10de475900..f72efa8dc5 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php @@ -12,6 +12,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\PersistentCollection; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Account; use Documents\Address; @@ -22,8 +23,6 @@ use Documents\User; use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectId; -use ProxyManager\Proxy\GhostObjectInterface; -use ProxyManager\Proxy\LazyLoadingInterface; use function assert; @@ -82,7 +81,7 @@ public function testLazyLoadReference(): void assert($profile instanceof Profile); self::assertInstanceOf(Profile::class, $profile); - self::assertInstanceOf(GhostObjectInterface::class, $profile); + self::assertInstanceOf(InternalProxy::class, $profile); $profile->getFirstName(); @@ -104,7 +103,7 @@ public function testLazyLoadedWithNotifyPropertyChanged(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfileNotify(); - self::assertInstanceOf(GhostObjectInterface::class, $profile); + self::assertInstanceOf(InternalProxy::class, $profile); self::assertTrue($this->uow->isUninitializedObject($profile)); $user->getProfileNotify()->setLastName('Malarz'); @@ -396,13 +395,13 @@ public function testDocumentNotFoundExceptionWithArrayId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(LazyLoadingInterface::class, $test->referenceOne); + self::assertInstanceOf(InternalProxy::class, $test->referenceOne); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithArrayId" document with identifier ' . '{"identifier":2} could not be found.', ); - $test->referenceOne->initializeProxy(); + $test->referenceOne->__load(); } public function testDocumentNotFoundExceptionWithObjectId(): void @@ -429,12 +428,12 @@ public function testDocumentNotFoundExceptionWithObjectId(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfile(); - self::assertInstanceOf(LazyLoadingInterface::class, $profile); + self::assertInstanceOf(InternalProxy::class, $profile); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Documents\Profile" document with identifier "abcdefabcdefabcdefabcdef" could not be found.', ); - $profile->initializeProxy(); + $profile->__load(); } public function testDocumentNotFoundExceptionWithMongoBinDataId(): void @@ -460,13 +459,13 @@ public function testDocumentNotFoundExceptionWithMongoBinDataId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(LazyLoadingInterface::class, $test->referenceOne); + self::assertInstanceOf(InternalProxy::class, $test->referenceOne); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithMongoBinDataId" document with identifier ' . '"testbindata" could not be found.', ); - $test->referenceOne->initializeProxy(); + $test->referenceOne->__load(); } public function testDocumentNotFoundEvent(): void @@ -502,8 +501,13 @@ public function testDocumentNotFoundEvent(): void $this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure)); - self::assertInstanceOf(LazyLoadingInterface::class, $profile); - $profile->initializeProxy(); + self::assertInstanceOf(InternalProxy::class, $profile); + $this->expectException(DocumentNotFoundException::class); + $this->expectExceptionMessage( + 'The "Documents\Profile" document with identifier ' . + '"abcdefabcdefabcdefabcdef" could not be found.', + ); + $profile->__load(); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php index 4d69bff53f..0a1e8ebfcf 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php @@ -4,11 +4,11 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\SimpleReferenceUser; use Documents\User; use MongoDB\BSON\ObjectId; -use ProxyManager\Proxy\GhostObjectInterface; use stdClass; use function assert; @@ -84,10 +84,10 @@ public function testProxy(): void self::assertNotNull($test); $user = $test->getUser(); - assert($user instanceof User && $user instanceof GhostObjectInterface); + assert($user instanceof User && $user instanceof InternalProxy); self::assertNotNull($user); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertInstanceOf(InternalProxy::class, $user); self::assertTrue($this->uow->isUninitializedObject($user)); self::assertEquals('jwage', $user->getUsername()); self::assertFalse($this->uow->isUninitializedObject($user)); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php index 6ff6348414..c0aa3c18d7 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; class GH520Test extends BaseTestCase { @@ -29,7 +29,7 @@ public function testPrimeWithGetSingleResult(): void $document = $query->getSingleResult(); self::assertInstanceOf(GH520Document::class, $document); - self::assertInstanceOf(GhostObjectInterface::class, $document->ref); + self::assertInstanceOf(InternalProxy::class, $document->ref); self::assertFalse($this->uow->isUninitializedObject($document->ref)); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php index 0248d9fd87..0b6e653c3d 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php @@ -8,8 +8,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; use function iterator_to_array; @@ -58,16 +58,16 @@ public function testReferenceManyOwningSidePreparesFilterCriteria(): void */ self::assertCount(2, $user1following); - self::assertInstanceOf(GhostObjectInterface::class, $user1following[0]); + self::assertInstanceOf(InternalProxy::class, $user1following[0]); self::assertFalse($this->uow->isUninitializedObject($user1following[0])); self::assertEquals($user2->getId(), $user1following[0]->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $user1following[1]); + self::assertInstanceOf(InternalProxy::class, $user1following[1]); self::assertTrue($this->uow->isUninitializedObject($user1following[1])); self::assertEquals($user3->getId(), $user1following[1]->getId()); $this->expectException(DocumentNotFoundException::class); - $user1following[1]->initializeProxy(); + $user1following[1]->__load(); } public function testReferenceManyInverseSidePreparesFilterCriteria(): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php index ce38f44edb..5427f7f49c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php @@ -8,8 +8,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; use function iterator_to_array; @@ -49,16 +49,16 @@ public function testReferenceManyOwningSidePreparesFilterCriteriaForDifferentCla */ self::assertCount(2, $user1likes); - self::assertInstanceOf(GhostObjectInterface::class, $user1likes[0]); + self::assertInstanceOf(InternalProxy::class, $user1likes[0]); self::assertFalse($this->uow->isUninitializedObject($user1likes[0])); self::assertEquals($thing1->getId(), $user1likes[0]->getId()); - self::assertInstanceOf(GhostObjectInterface::class, $user1likes[1]); + self::assertInstanceOf(InternalProxy::class, $user1likes[1]); self::assertTrue($this->uow->isUninitializedObject($user1likes[1])); self::assertEquals($thing2->getId(), $user1likes[1]->getId()); $this->expectException(DocumentNotFoundException::class); - $user1likes[1]->initializeProxy(); + $user1likes[1]->__load(); } public function testReferenceManyInverseSidePreparesFilterCriteriaForDifferentClass(): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php index 0b4e912e82..8d2927da73 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php @@ -10,10 +10,10 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use MongoDB\BSON\Binary; use PHPUnit\Framework\Attributes\DataProvider; -use ProxyManager\Proxy\GhostObjectInterface; class GH852Test extends BaseTestCase { @@ -49,7 +49,7 @@ public function testA(Closure $idGenerator): void self::assertEquals($idGenerator('parent'), $parent->id); self::assertEquals('parent', $parent->name); - self::assertInstanceOf(GhostObjectInterface::class, $parent->refOne); + self::assertInstanceOf(InternalProxy::class, $parent->refOne); self::assertInstanceOf(GH852Document::class, $parent->refOne); self::assertTrue($this->uow->isUninitializedObject($parent->refOne)); self::assertEquals($idGenerator('childA'), $parent->refOne->id); @@ -61,13 +61,13 @@ public function testA(Closure $idGenerator): void /* These proxies will be initialized when we first access the collection * by DocumentPersister::loadReferenceManyCollectionOwningSide(). */ - self::assertInstanceOf(GhostObjectInterface::class, $parent->refMany[0]); + self::assertInstanceOf(InternalProxy::class, $parent->refMany[0]); self::assertInstanceOf(GH852Document::class, $parent->refMany[0]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[0])); self::assertEquals($idGenerator('childB'), $parent->refMany[0]->id); self::assertEquals('childB', $parent->refMany[0]->name); - self::assertInstanceOf(GhostObjectInterface::class, $parent->refMany[1]); + self::assertInstanceOf(InternalProxy::class, $parent->refMany[1]); self::assertInstanceOf(GH852Document::class, $parent->refMany[1]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[1])); self::assertEquals($idGenerator('childC'), $parent->refMany[1]->id); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php index b2eb71704c..480c8a19ec 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; -use ProxyManager\Proxy\GhostObjectInterface; class GH936Test extends BaseTestCase { @@ -27,7 +27,7 @@ public function testRemoveCascadesThroughProxyDocuments(): void $foo = $this->dm->find(GH936Document::class, $foo->id); - self::assertInstanceOf(GhostObjectInterface::class, $foo->ref); + self::assertInstanceOf(InternalProxy::class, $foo->ref); $this->dm->remove($foo); $this->dm->flush(); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php index 608fc45a40..768440338b 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php @@ -4,13 +4,13 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Repository\ViewRepository; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\UnitOfWork; use Documents\CmsUser; use Documents\UserName; use Documents\ViewReference; -use ProxyManager\Proxy\GhostObjectInterface; use function assert; @@ -112,7 +112,7 @@ public function testViewReferences(): void $viewReference = $this->dm->find(ViewReference::class, $alcaeus->getId()); self::assertInstanceOf(ViewReference::class, $viewReference); - self::assertInstanceOf(GhostObjectInterface::class, $viewReference->getReferenceOneView()); + self::assertInstanceOf(InternalProxy::class, $viewReference->getReferenceOneView()); self::assertSame($malarzm->getId(), $viewReference->getReferenceOneView()->getId()); // No proxies for inverse referenceOne @@ -120,7 +120,7 @@ public function testViewReferences(): void self::assertSame($alcaeus->getId(), $viewReference->getReferenceOneViewMappedBy()->getId()); self::assertCount(1, $viewReference->getReferenceManyView()); - self::assertInstanceOf(GhostObjectInterface::class, $viewReference->getReferenceManyView()[0]); + self::assertInstanceOf(InternalProxy::class, $viewReference->getReferenceManyView()[0]); self::assertSame($malarzm->getId(), $viewReference->getReferenceManyView()[0]->getId()); // No proxies for inverse referenceMany diff --git a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php index 30191a6da6..560e6e391c 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php @@ -10,8 +10,8 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\PersistentCollection; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; -use ProxyManager\Proxy\GhostObjectInterface; class HydratorTest extends BaseTestCase { @@ -41,10 +41,10 @@ public function testHydrator(): void self::assertEquals('jon', $user->name); self::assertInstanceOf(DateTime::class, $user->birthdate); self::assertInstanceOf(HydrationClosureReferenceOne::class, $user->referenceOne); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceOne); + self::assertInstanceOf(InternalProxy::class, $user->referenceOne); self::assertInstanceOf(PersistentCollection::class, $user->referenceMany); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceMany[0]); - self::assertInstanceOf(GhostObjectInterface::class, $user->referenceMany[1]); + self::assertInstanceOf(InternalProxy::class, $user->referenceMany[0]); + self::assertInstanceOf(InternalProxy::class, $user->referenceMany[1]); self::assertInstanceOf(HydrationClosureEmbedOne::class, $user->embedOne); self::assertInstanceOf(PersistentCollection::class, $user->embedMany); self::assertEquals('jon', $user->embedOne->name); @@ -54,7 +54,7 @@ public function testHydrator(): void public function testHydrateProxyWithMissingAssociations(): void { $user = $this->dm->getReference(HydrationClosureUser::class, 1); - self::assertInstanceOf(GhostObjectInterface::class, $user); + self::assertInstanceOf(InternalProxy::class, $user); $this->dm->getHydratorFactory()->hydrate($user, [ '_id' => 1, diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php index 2be5e3fc1a..52e8fd3906 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php @@ -9,13 +9,13 @@ use Doctrine\ODM\MongoDB\Event\DocumentNotFoundEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\LockException; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Cart; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; use PHPUnit\Framework\MockObject\MockObject; -use ProxyManager\Proxy\GhostObjectInterface; class StaticProxyFactoryTest extends BaseTestCase { @@ -49,7 +49,7 @@ public function testProxyInitializeWithException(): void $uow = $this->dm->getUnitOfWork(); $proxy = $this->dm->getReference(Cart::class, '123'); - self::assertInstanceOf(GhostObjectInterface::class, $proxy); + self::assertInstanceOf(InternalProxy::class, $proxy); $closure = static function (DocumentNotFoundEventArgs $eventArgs) { self::fail('DocumentNotFoundListener should not be called'); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php index 55a9257f8d..96119aafbe 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php @@ -11,6 +11,7 @@ use Doctrine\ODM\MongoDB\APM\CommandLogger; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\MongoDBException; +use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\Mocks\ExceptionThrowingListenerMock; use Doctrine\ODM\MongoDB\Tests\Mocks\PreUpdateListenerMock; use Doctrine\ODM\MongoDB\UnitOfWork; @@ -28,7 +29,6 @@ use MongoDB\Driver\WriteConcern; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; -use ProxyManager\Proxy\GhostObjectInterface; use ReflectionProperty; use Throwable; @@ -489,7 +489,7 @@ public function testRecomputeChangesetForUninitializedProxyDoesNotCreateChangese $user = $this->dm->find(ForumUser::class, $id); self::assertInstanceOf(ForumUser::class, $user); - self::assertInstanceOf(GhostObjectInterface::class, $user->getAvatar()); + self::assertInstanceOf(InternalProxy::class, $user->getAvatar()); $classMetadata = $this->dm->getClassMetadata(ForumAvatar::class); From dc51d1e49134a3c587a3acf9e0e7067d9f294990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 02:13:33 +0200 Subject: [PATCH 06/14] Static analysis --- lib/Doctrine/ODM/MongoDB/Configuration.php | 2 + .../MongoDB/Proxy/LazyGhostProxyFactory.php | 16 +++++--- phpstan-baseline.neon | 37 +++++++++++++------ ...Test.php => LazyGhostProxyFactoryTest.php} | 6 +-- 4 files changed, 41 insertions(+), 20 deletions(-) rename tests/Doctrine/ODM/MongoDB/Tests/Proxy/{Factory/StaticProxyFactoryTest.php => LazyGhostProxyFactoryTest.php} (93%) diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index aef85a97f2..8c321cf88e 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -263,6 +263,8 @@ public function getProxyDir(): ?string /** * Gets an int flag that indicates whether proxy classes should always be regenerated * during each script execution. + * + * @return self::AUTOGENERATE_* */ public function getAutoGenerateProxyClasses(): int { diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php index 0ee5322f75..652b54b368 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php @@ -195,21 +195,25 @@ public function generateProxyClasses(array $classes, string|null $proxyDir = nul protected function skipClass(ClassMetadata $metadata): bool { return $metadata->isMappedSuperclass - || $metadata->isEmbeddedClass + || $metadata->isEmbeddedDocument || $metadata->getReflectionClass()->isAbstract(); } /** * Creates a closure capable of initializing a proxy * - * @return Closure(InternalProxy, array):void + * @param ClassMetadata $classMetadata + * + * @return Closure(InternalProxy&T, array):void * * @throws DocumentNotFoundException + * + * @template T of object */ - private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $documentPersister): Closure + private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $persister): Closure { - return static function (InternalProxy $proxy, mixed $identifier) use ($documentPersister, $classMetadata): void { - $original = $documentPersister->load(['_id' => $identifier]); + return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata): void { + $original = $persister->load(['_id' => $identifier]); if ($original === null) { throw DocumentNotFoundException::documentNotFound( @@ -222,7 +226,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPer return; } - $class = $documentPersister->getClassMetadata(); + $class = $persister->getClassMetadata(); foreach ($class->getReflectionProperties() as $property) { if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ae58dfd951..3682c7fcc5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -425,6 +425,11 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/UnionWith.php + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Configuration\\:\\:getAutoGenerateProxyClasses\\(\\) should return 0\\|1\\|2\\|3\\|4 but returns int\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Configuration.php + - message: "#^Return type \\(Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadataFactoryInterface\\) of method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getMetadataFactory\\(\\) should be compatible with return type \\(Doctrine\\\\Persistence\\\\Mapping\\\\ClassMetadataFactory\\\\>\\) of method Doctrine\\\\Persistence\\\\ObjectManager\\:\\:getMetadataFactory\\(\\)$#" count: 1 @@ -440,11 +445,6 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Event/OnClearEventArgs.php - - - message: "#^Parameter \\#1 \\$initializer of method ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\:\\:setProxyInitializer\\(\\) expects \\(Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool\\)\\|null, Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true given\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\Annotations\\\\SearchIndex\\:\\:__construct\\(\\) has parameter \\$analyzers with no value type specified in iterable type array\\.$#" count: 1 @@ -531,24 +531,39 @@ parameters: path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php - - message: "#^Right side of && is always true\\.$#" + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" count: 1 path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Persisters\\\\DocumentPersister\\:\\:isInTransaction\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php + + - + message: "#^Call to an undefined static method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\LazyGhostProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php + + - + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\LazyGhostProxyFactory\\:\\:createLazyInitializer\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php + - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" count: 1 - path: lib/Doctrine/ODM/MongoDB/PersistentCollection.php + path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Persisters\\\\DocumentPersister\\:\\:isInTransaction\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\ProxyFactory\\:\\:generateProxyClasses\\(\\) has parameter \\$classes with no value type specified in iterable type array\\.$#" count: 1 - path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php + path: lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\Factory\\\\StaticProxyFactory\\:\\:createInitializer\\(\\) should return Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface\\&TDocument\\=, string\\=, array\\\\=, Closure\\|null\\=, array\\\\=\\)\\: bool but returns Closure\\(ProxyManager\\\\Proxy\\\\GhostObjectInterface, string, array, mixed, array\\)\\: true\\.$#" + message: "#^PHPDoc tag @param for parameter \\$classes with type Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata is incompatible with native type array\\.$#" count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/Factory/StaticProxyFactory.php + path: lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php - message: "#^Unsafe call to private method Doctrine\\\\ODM\\\\MongoDB\\\\Query\\\\Expr\\:\\:convertExpression\\(\\) through static\\:\\:\\.$#" diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php similarity index 93% rename from tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php rename to tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php index 52e8fd3906..825bee977b 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/Factory/StaticProxyFactoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php @@ -17,7 +17,7 @@ use MongoDB\Database; use PHPUnit\Framework\MockObject\MockObject; -class StaticProxyFactoryTest extends BaseTestCase +class LazyGhostProxyFactoryTest extends BaseTestCase { /** @var Client|MockObject */ private Client $client; @@ -57,7 +57,7 @@ public function testProxyInitializeWithException(): void $this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure)); try { - $proxy->initializeProxy(); + $proxy->__load(); self::fail('An exception should have been thrown'); } catch (LockException $exception) { self::assertInstanceOf(LockException::class, $exception); @@ -65,7 +65,7 @@ public function testProxyInitializeWithException(): void $uow->computeChangeSets(); - self::assertFalse($proxy->isProxyInitialized(), 'Proxy should not be initialized'); + self::assertFalse($proxy->__isInitialized(), 'Proxy should not be initialized'); } public function tearDown(): void From 4285136dbb804a0039d3d5b0f812ef2f13c3d710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 02:31:03 +0200 Subject: [PATCH 07/14] Upgrade doc --- UPGRADE-3.0.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 2b8f298592..3e2df83b12 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -31,6 +31,16 @@ no longer implements the `PropertyChangedListener` interface. `AnnotationDriver` class defined in `doctrine/persistence` (or in ODM's compatibility layer) +## Proxy objects + +The proxy implementation changed back to Doctrine proxies. +If you are checking for proxies, the following changed: +* Proxies no longer implement `ProxyManager\Proxy\GhostObjectInterface`. + To check whether a returned object is a proxy, check for the + `Doctrine\Persistence\Proxy` interface. +* The `initializeProxy` method has been replaced by `__load`. +* The `isProxyInitialized` method has been replaced by `__isInitialized`. + ## Proxy Class Name Resolution The `Doctrine\ODM\MongoDB\Proxy\Resolver\ClassNameResolver` interface has been From 6bd757ba8d9a6dd116237fa2d4365eed399437dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 02:41:45 +0200 Subject: [PATCH 08/14] symfony/var-exporter is required --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ece00f6185..8bb6178a3d 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "psr/cache": "^1.0 || ^2.0 || ^3.0", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.2 || ^3.0", - "symfony/var-dumper": "^6.4 || ^7.0" + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" }, "require-dev": { "ext-bcmath": "*", From 85ecbd7781fff615fcbd2f4f9aeac5f6166ea111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 03:15:48 +0200 Subject: [PATCH 09/14] Remove InvalidArgumentException --- composer.json | 2 +- .../ODM/MongoDB/InvalidArgumentException.php | 47 ------------------- lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php | 3 +- .../MongoDB/Proxy/LazyGhostProxyFactory.php | 19 ++++---- 4 files changed, 12 insertions(+), 59 deletions(-) delete mode 100644 lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php diff --git a/composer.json b/composer.json index 8bb6178a3d..935b96fe18 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "doctrine/collections": "^1.5 || ^2.0", "doctrine/event-manager": "^1.0 || ^2.0", "doctrine/instantiator": "^1.1 || ^2", - "doctrine/persistence": "^3.2 || ^4.0", + "doctrine/persistence": "^3.2", "jean85/pretty-package-versions": "^1.3.0 || ^2.0.1", "mongodb/mongodb": "^1.17.0", "psr/cache": "^1.0 || ^2.0 || ^3.0", diff --git a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php b/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php deleted file mode 100644 index a6adfe95b3..0000000000 --- a/lib/Doctrine/ODM/MongoDB/InvalidArgumentException.php +++ /dev/null @@ -1,47 +0,0 @@ - 4 : ! is_bool($autoGenerate)) { - throw InvalidArgumentException::invalidAutoGenerateMode($autoGenerate); + throw new InvalidArgumentException(sprintf('Invalid auto generate mode "%s" given.', is_scalar($autoGenerate) ? (string) $autoGenerate : get_debug_type($autoGenerate))); } $this->uow = $dm->getUnitOfWork(); @@ -354,12 +357,8 @@ private function generateProxyClass(ClassMetadata $class, string|null $fileName, $parentDirectory = dirname($fileName); - if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); - } - - if (! is_writable($parentDirectory)) { - throw InvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir); + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true) || ! is_writable($parentDirectory)) { + throw new InvalidArgumentException(sprintf('Your proxy directory "%s" must be writable', $this->proxyDir)); } $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); From 51f41eac424eed0174acdca002bdea2a09733077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 03:19:30 +0200 Subject: [PATCH 10/14] Remove interface ProxyFactory and use it as class name --- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 4 +- .../MongoDB/Proxy/LazyGhostProxyFactory.php | 426 ------------------ .../ODM/MongoDB/Proxy/ProxyFactory.php | 418 ++++++++++++++++- 3 files changed, 411 insertions(+), 437 deletions(-) delete mode 100644 lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index 2fb31ed828..f6196e860b 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -11,7 +11,7 @@ use Doctrine\ODM\MongoDB\Mapping\MappingException; use Doctrine\ODM\MongoDB\Proxy\DefaultProxyClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\InternalProxy; -use Doctrine\ODM\MongoDB\Proxy\LazyGhostProxyFactory; +use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; @@ -179,7 +179,7 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory); $this->schemaManager = new SchemaManager($this, $this->metadataFactory); - $this->proxyFactory = new LazyGhostProxyFactory( + $this->proxyFactory = new ProxyFactory( $this, $config->getProxyDir(), $config->getProxyNamespace(), diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php deleted file mode 100644 index 16201316ac..0000000000 --- a/lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php +++ /dev/null @@ -1,426 +0,0 @@ -; - -/** - * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR - */ -class extends \ implements \ -{ - - - public function __isInitialized(): bool - { - return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); - } - - public function __serialize(): array - { - - } -} - -EOPHP; - - /** The UnitOfWork this factory uses to retrieve persisters */ - private readonly UnitOfWork $uow; - - /** @var self::AUTOGENERATE_* */ - private $autoGenerate; - - /** @var array */ - private array $proxyFactories = []; - - /** - * Initializes a new instance of the ProxyFactory class that is - * connected to the given EntityManager. - * - * @param DocumentManager $dm The EntityManager the new factory works for. - * @param string $proxyDir The directory to use for the proxy classes. It must exist. - * @param string $proxyNs The namespace to use for the proxy classes. - * @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. - */ - public function __construct( - private readonly DocumentManager $dm, - private readonly string $proxyDir, - private readonly string $proxyNs, - bool|int $autoGenerate = self::AUTOGENERATE_NEVER, - ) { - if (! $proxyDir) { - throw new InvalidArgumentException('You must configure a proxy directory. See docs for details'); - } - - if (! $proxyNs) { - throw new InvalidArgumentException('You must configure a proxy namespace'); - } - - if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) { - throw new InvalidArgumentException(sprintf('Invalid auto generate mode "%s" given.', is_scalar($autoGenerate) ? (string) $autoGenerate : get_debug_type($autoGenerate))); - } - - $this->uow = $dm->getUnitOfWork(); - $this->autoGenerate = self::AUTOGENERATE_ALWAYS; // @todo (int) $autoGenerate; - } - - /** @param array $identifier */ - public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy - { - $className = $metadata->getName(); - - $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); - - return $proxyFactory($identifier); - } - - /** - * Generates proxy classes for all given classes. - * - * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. - * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the - * directory configured on the Configuration of the EntityManager used - * by this factory is used. - * - * @return int Number of generated proxies. - */ - public function generateProxyClasses(array $classes, string|null $proxyDir = null): int - { - $generated = 0; - - foreach ($classes as $class) { - if ($this->skipClass($class)) { - continue; - } - - $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->proxyDir); - $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); - - $this->generateProxyClass($class, $proxyFileName, $proxyClassName); - - ++$generated; - } - - return $generated; - } - - protected function skipClass(ClassMetadata $metadata): bool - { - return $metadata->isMappedSuperclass - || $metadata->isEmbeddedDocument - || $metadata->getReflectionClass()->isAbstract(); - } - - /** - * Creates a closure capable of initializing a proxy - * - * @param ClassMetadata $classMetadata - * - * @return Closure(InternalProxy&T, array):void - * - * @throws DocumentNotFoundException - * - * @template T of object - */ - private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $persister): Closure - { - return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata): void { - $original = $persister->load(['_id' => $identifier]); - - if ($original === null) { - throw DocumentNotFoundException::documentNotFound( - $classMetadata->getName(), - $identifier, - ); - } - - if ($proxy === $original) { - return; - } - - $class = $persister->getClassMetadata(); - - foreach ($class->getReflectionProperties() as $property) { - if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) { - continue; - } - - $property->setValue($proxy, $property->getValue($original)); - } - }; - } - - private function getProxyFileName(string $className, string $baseDirectory): string - { - $baseDirectory = $baseDirectory ?: $this->proxyDir; - - return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER - . str_replace('\\', '', $className) . '.php'; - } - - private function getProxyFactory(string $className): Closure - { - $skippedProperties = []; - $class = $this->dm->getClassMetadata($className); - $identifiers = array_flip($class->getIdentifierFieldNames()); - $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; - $reflector = $class->getReflectionClass(); - - while ($reflector) { - foreach ($reflector->getProperties($filter) as $property) { - $name = $property->name; - - if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)))) { - continue; - } - - $prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : ''); - - $skippedProperties[$prefix . $name] = true; - } - - $filter = ReflectionProperty::IS_PRIVATE; - $reflector = $reflector->getParentClass(); - } - - $className = $class->getName(); // aliases and case sensitivity - $entityPersister = $this->uow->getDocumentPersister($className); - $initializer = $this->createLazyInitializer($class, $entityPersister); - $proxyClassName = $this->loadProxyClass($class); - - $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $class): InternalProxy { - $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { - $initializer($object, $identifier); - }, $skippedProperties); - - $class->setIdentifierValue($proxy, $identifier); - - return $proxy; - }, null, $proxyClassName); - - return $this->proxyFactories[$className] = $proxyFactory; - } - - private function loadProxyClass(ClassMetadata $class): string - { - $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); - - if (class_exists($proxyClassName, false)) { - return $proxyClassName; - } - - if ($this->autoGenerate === self::AUTOGENERATE_EVAL) { - $this->generateProxyClass($class, null, $proxyClassName); - - return $proxyClassName; - } - - $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); - - switch ($this->autoGenerate) { - case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: - if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { - break; - } - // no break - case self::AUTOGENERATE_FILE_NOT_EXISTS: - if (file_exists($fileName)) { - break; - } - // no break - case self::AUTOGENERATE_ALWAYS: - $this->generateProxyClass($class, $fileName, $proxyClassName); - break; - } - - require $fileName; - - return $proxyClassName; - } - - private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void - { - $i = strrpos($proxyClassName, '\\'); - $placeholders = [ - '' => $class->getName(), - '' => substr($proxyClassName, 0, $i), - '' => substr($proxyClassName, 1 + $i), - '' => InternalProxy::class, - ]; - - preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); - - foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { - $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); - } - - $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); - - if (! $fileName) { - if (! class_exists($proxyClassName)) { - eval(substr($proxyCode, 5)); - } - - return; - } - - $parentDirectory = dirname($fileName); - - if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true) || ! is_writable($parentDirectory)) { - throw new InvalidArgumentException(sprintf('Your proxy directory "%s" must be writable', $this->proxyDir)); - } - - $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); - - file_put_contents($tmpFileName, $proxyCode); - @chmod($tmpFileName, 0664); - rename($tmpFileName, $fileName); - } - - private function generateUseLazyGhostTrait(ClassMetadata $class): string - { - $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); - $code = substr($code, 7 + (int) strpos($code, "\n{")); - $code = substr($code, 0, (int) strpos($code, "\n}")); - $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { - initializeLazyObject as private; - setLazyObjectAsInitialized as public __setInitialized; - isLazyObjectInitialized as private; - createLazyGhost as private; - resetLazyObject as private; - } - - public function __load(): void - { - $this->initializeLazyObject(); - } - '), $code); - - return $code; - } - - private function generateSerializeImpl(ClassMetadata $class): string - { - $reflector = $class->getReflectionClass(); - $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; - - $code = '$properties = ' . $properties . '; - unset($properties["\0" . self::class . "\0lazyObjectState"]); - - '; - - if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { - return $code . 'return $properties;'; - } - - return $code . '$data = []; - - foreach (parent::__sleep() as $name) { - $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null; - - if (null === $k) { - trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); - } else { - $data[$k] = $value; - } - } - - return $data;'; - } - - private static function generateProxyClassName(string $className, string $proxyNamespace): string - { - return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); - } -} diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php index da5601f1c4..0b3ea9e64c 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php @@ -4,23 +4,423 @@ namespace Doctrine\ODM\MongoDB\Proxy; +use Closure; +use Doctrine\ODM\MongoDB\DocumentManager; +use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Persisters\DocumentPersister; +use Doctrine\ODM\MongoDB\UnitOfWork; +use Doctrine\Persistence\Proxy; +use InvalidArgumentException; +use ReflectionProperty; +use Symfony\Component\VarExporter\ProxyHelper; -interface ProxyFactory +use function array_combine; +use function array_flip; +use function bin2hex; +use function chmod; +use function class_exists; +use function dirname; +use function file_exists; +use function file_put_contents; +use function filemtime; +use function get_debug_type; +use function is_bool; +use function is_dir; +use function is_int; +use function is_scalar; +use function is_writable; +use function ltrim; +use function mkdir; +use function preg_match_all; +use function random_bytes; +use function rename; +use function rtrim; +use function sprintf; +use function str_replace; +use function strpos; +use function strrpos; +use function strtr; +use function substr; +use function ucfirst; + +use const DIRECTORY_SEPARATOR; + +/** + * This factory is used to create proxy objects for entities at runtime. + */ +class ProxyFactory +{ + /** + * Never autogenerate a proxy and rely that it was generated by some + * process before deployment. + */ + public const AUTOGENERATE_NEVER = 0; + + /** + * Always generates a new proxy in every request. + * + * This is only sane during development. + */ + public const AUTOGENERATE_ALWAYS = 1; + + /** + * Autogenerate the proxy class when the proxy file does not exist. + * + * This strategy causes a file_exists() call whenever any proxy is used the + * first time in a request. + */ + public const AUTOGENERATE_FILE_NOT_EXISTS = 2; + + /** + * Generate the proxy classes using eval(). + * + * This strategy is only sane for development, and even then it gives me + * the creeps a little. + */ + public const AUTOGENERATE_EVAL = 3; + + /** + * Autogenerate the proxy class when the proxy file does not exist or + * when the proxied file changed. + * + * This strategy causes a file_exists() call whenever any proxy is used the + * first time in a request. When the proxied file is changed, the proxy will + * be updated. + */ + public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4; + + private const PROXY_CLASS_TEMPLATE = <<<'EOPHP' +; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR + */ +class extends \ implements \ { - /** @param ClassMetadata $classes */ - public function generateProxyClasses(array $classes): int; + + + public function __isInitialized(): bool + { + return isset($this->lazyObjectState) && $this->isLazyObjectInitialized(); + } + + public function __serialize(): array + { + + } +} + +EOPHP; + + /** The UnitOfWork this factory uses to retrieve persisters */ + private readonly UnitOfWork $uow; + + /** @var self::AUTOGENERATE_* */ + private $autoGenerate; + + /** @var array */ + private array $proxyFactories = []; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param DocumentManager $dm The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. + */ + public function __construct( + private readonly DocumentManager $dm, + private readonly string $proxyDir, + private readonly string $proxyNs, + bool|int $autoGenerate = self::AUTOGENERATE_NEVER, + ) { + if (! $proxyDir) { + throw new InvalidArgumentException('You must configure a proxy directory. See docs for details'); + } + + if (! $proxyNs) { + throw new InvalidArgumentException('You must configure a proxy namespace'); + } + + if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) { + throw new InvalidArgumentException(sprintf('Invalid auto generate mode "%s" given.', is_scalar($autoGenerate) ? (string) $autoGenerate : get_debug_type($autoGenerate))); + } + + $this->uow = $dm->getUnitOfWork(); + $this->autoGenerate = self::AUTOGENERATE_ALWAYS; // @todo (int) $autoGenerate; + } + + /** @param array $identifier */ + public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy + { + $className = $metadata->getName(); + + $proxyFactory = $this->proxyFactories[$className] ?? $this->getProxyFactory($className); + + return $proxyFactory($identifier); + } + + /** + * Generates proxy classes for all given classes. + * + * @param ClassMetadata[] $classes The classes (ClassMetadata instances) for which to generate proxies. + * @param string|null $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, string|null $proxyDir = null): int + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->getProxyFileName($class->getName(), $proxyDir ?: $this->proxyDir); + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + $this->generateProxyClass($class, $proxyFileName, $proxyClassName); + + ++$generated; + } + + return $generated; + } + + protected function skipClass(ClassMetadata $metadata): bool + { + return $metadata->isMappedSuperclass + || $metadata->isEmbeddedDocument + || $metadata->getReflectionClass()->isAbstract(); + } /** - * Gets a reference proxy instance for the entity of the given type and identified by - * the given identifier. + * Creates a closure capable of initializing a proxy * - * @param mixed $identifier - * @psalm-param ClassMetadata $metadata + * @param ClassMetadata $classMetadata * - * @return T&InternalProxy + * @return Closure(InternalProxy&T, array):void + * + * @throws DocumentNotFoundException * * @template T of object */ - public function getProxy(ClassMetadata $metadata, $identifier): InternalProxy; + private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $persister): Closure + { + return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata): void { + $original = $persister->load(['_id' => $identifier]); + + if ($original === null) { + throw DocumentNotFoundException::documentNotFound( + $classMetadata->getName(), + $identifier, + ); + } + + if ($proxy === $original) { + return; + } + + $class = $persister->getClassMetadata(); + + foreach ($class->getReflectionProperties() as $property) { + if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) { + continue; + } + + $property->setValue($proxy, $property->getValue($original)); + } + }; + } + + private function getProxyFileName(string $className, string $baseDirectory): string + { + $baseDirectory = $baseDirectory ?: $this->proxyDir; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . InternalProxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + private function getProxyFactory(string $className): Closure + { + $skippedProperties = []; + $class = $this->dm->getClassMetadata($className); + $identifiers = array_flip($class->getIdentifierFieldNames()); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); + + while ($reflector) { + foreach ($reflector->getProperties($filter) as $property) { + $name = $property->name; + + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)))) { + continue; + } + + $prefix = $property->isPrivate() ? "\0" . $property->class . "\0" : ($property->isProtected() ? "\0*\0" : ''); + + $skippedProperties[$prefix . $name] = true; + } + + $filter = ReflectionProperty::IS_PRIVATE; + $reflector = $reflector->getParentClass(); + } + + $className = $class->getName(); // aliases and case sensitivity + $entityPersister = $this->uow->getDocumentPersister($className); + $initializer = $this->createLazyInitializer($class, $entityPersister); + $proxyClassName = $this->loadProxyClass($class); + + $proxyFactory = Closure::bind(static function (mixed $identifier) use ($initializer, $skippedProperties, $class): InternalProxy { + $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, $identifier): void { + $initializer($object, $identifier); + }, $skippedProperties); + + $class->setIdentifierValue($proxy, $identifier); + + return $proxy; + }, null, $proxyClassName); + + return $this->proxyFactories[$className] = $proxyFactory; + } + + private function loadProxyClass(ClassMetadata $class): string + { + $proxyClassName = self::generateProxyClassName($class->getName(), $this->proxyNs); + + if (class_exists($proxyClassName, false)) { + return $proxyClassName; + } + + if ($this->autoGenerate === self::AUTOGENERATE_EVAL) { + $this->generateProxyClass($class, null, $proxyClassName); + + return $proxyClassName; + } + + $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); + + switch ($this->autoGenerate) { + case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: + if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { + break; + } + // no break + case self::AUTOGENERATE_FILE_NOT_EXISTS: + if (file_exists($fileName)) { + break; + } + // no break + case self::AUTOGENERATE_ALWAYS: + $this->generateProxyClass($class, $fileName, $proxyClassName); + break; + } + + require $fileName; + + return $proxyClassName; + } + + private function generateProxyClass(ClassMetadata $class, string|null $fileName, string $proxyClassName): void + { + $i = strrpos($proxyClassName, '\\'); + $placeholders = [ + '' => $class->getName(), + '' => substr($proxyClassName, 0, $i), + '' => substr($proxyClassName, 1 + $i), + '' => InternalProxy::class, + ]; + + preg_match_all('(<([a-zA-Z]+)>)', self::PROXY_CLASS_TEMPLATE, $placeholderMatches); + + foreach (array_combine($placeholderMatches[0], $placeholderMatches[1]) as $placeholder => $name) { + $placeholders[$placeholder] ?? $placeholders[$placeholder] = $this->{'generate' . ucfirst($name)}($class); + } + + $proxyCode = strtr(self::PROXY_CLASS_TEMPLATE, $placeholders); + + if (! $fileName) { + if (! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if (! is_dir($parentDirectory) && ! @mkdir($parentDirectory, 0775, true) || ! is_writable($parentDirectory)) { + throw new InvalidArgumentException(sprintf('Your proxy directory "%s" must be writable', $this->proxyDir)); + } + + $tmpFileName = $fileName . '.' . bin2hex(random_bytes(12)); + + file_put_contents($tmpFileName, $proxyCode); + @chmod($tmpFileName, 0664); + rename($tmpFileName, $fileName); + } + + private function generateUseLazyGhostTrait(ClassMetadata $class): string + { + $code = ProxyHelper::generateLazyGhost($class->getReflectionClass()); + $code = substr($code, 7 + (int) strpos($code, "\n{")); + $code = substr($code, 0, (int) strpos($code, "\n}")); + $code = str_replace('LazyGhostTrait;', str_replace("\n ", "\n", 'LazyGhostTrait { + initializeLazyObject as private; + setLazyObjectAsInitialized as public __setInitialized; + isLazyObjectInitialized as private; + createLazyGhost as private; + resetLazyObject as private; + } + + public function __load(): void + { + $this->initializeLazyObject(); + } + '), $code); + + return $code; + } + + private function generateSerializeImpl(ClassMetadata $class): string + { + $reflector = $class->getReflectionClass(); + $properties = $reflector->hasMethod('__serialize') ? 'parent::__serialize()' : '(array) $this'; + + $code = '$properties = ' . $properties . '; + unset($properties["\0" . self::class . "\0lazyObjectState"]); + + '; + + if ($reflector->hasMethod('__serialize') || ! $reflector->hasMethod('__sleep')) { + return $code . 'return $properties;'; + } + + return $code . '$data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0' . $reflector->name . '\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(sprintf(\'serialize(): "%s" returned as member variable from __sleep() but does not exist\', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data;'; + } + + private static function generateProxyClassName(string $className, string $proxyNamespace): string + { + return rtrim($proxyNamespace, '\\') . '\\' . Proxy::MARKER . '\\' . ltrim($className, '\\'); + } } From c884a95061fffebb7c620b38c5560e38c3b5079e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 03:28:12 +0200 Subject: [PATCH 11/14] Assert on Proxy class --- lib/Doctrine/ODM/MongoDB/DocumentManager.php | 1 - .../Tests/Functional/IdentifiersTest.php | 6 +-- .../Tests/Functional/ReferencePrimerTest.php | 48 +++++++++---------- .../Tests/Functional/ReferencesTest.php | 14 +++--- .../Tests/Functional/SimpleReferencesTest.php | 3 +- .../Tests/Functional/Ticket/GH520Test.php | 4 +- .../Tests/Functional/Ticket/GH593Test.php | 6 +-- .../Tests/Functional/Ticket/GH602Test.php | 6 +-- .../Tests/Functional/Ticket/GH852Test.php | 8 ++-- .../Tests/Functional/Ticket/GH936Test.php | 4 +- .../ODM/MongoDB/Tests/Functional/ViewTest.php | 6 +-- .../ODM/MongoDB/Tests/HydratorTest.php | 7 +-- .../Tests/Mapping/ClassMetadataTest.php | 10 ++-- ...xyFactoryTest.php => ProxyFactoryTest.php} | 4 +- .../ODM/MongoDB/Tests/UnitOfWorkTest.php | 4 +- 15 files changed, 66 insertions(+), 65 deletions(-) rename tests/Doctrine/ODM/MongoDB/Tests/Proxy/{LazyGhostProxyFactoryTest.php => ProxyFactoryTest.php} (96%) diff --git a/lib/Doctrine/ODM/MongoDB/DocumentManager.php b/lib/Doctrine/ODM/MongoDB/DocumentManager.php index f6196e860b..06c28caf6b 100644 --- a/lib/Doctrine/ODM/MongoDB/DocumentManager.php +++ b/lib/Doctrine/ODM/MongoDB/DocumentManager.php @@ -12,7 +12,6 @@ use Doctrine\ODM\MongoDB\Proxy\DefaultProxyClassNameResolver; use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; -use Doctrine\ODM\MongoDB\Proxy\ProxyFactory; use Doctrine\ODM\MongoDB\Query\FilterCollection; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Repository\GridFSRepository; diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php index 0b60b44874..cc0c0594c8 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/IdentifiersTest.php @@ -4,8 +4,8 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use Documents\Event; use Documents\User; @@ -30,7 +30,7 @@ public function testGetIdentifierValue(): void $userTest = $test->getUser(); self::assertEquals($user->getId(), $userTest->getId()); - self::assertInstanceOf(InternalProxy::class, $userTest); + self::assertInstanceOf(Proxy::class, $userTest); self::assertTrue($this->uow->isUninitializedObject($userTest)); $this->dm->clear(); @@ -42,7 +42,7 @@ public function testGetIdentifierValue(): void $foundUser = $test->getUser(); self::assertEquals($user->getId(), $class->getIdentifierValue($user)); self::assertEquals($user->getId(), $class->getFieldValue($foundUser, 'id')); - self::assertInstanceOf(InternalProxy::class, $foundUser); + self::assertInstanceOf(Proxy::class, $foundUser); self::assertTrue($this->uow->isUninitializedObject($foundUser)); self::assertEquals('jwage', $foundUser->getUsername()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php index a8b96bf99c..503d458729 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencePrimerTest.php @@ -7,10 +7,10 @@ use DateTime; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil; +use Doctrine\Persistence\Proxy; use Documents\Account; use Documents\Agent; use Documents\BlogPost; @@ -95,7 +95,7 @@ public function testPrimeReferencesWithDBRefObjects(): void ->field('groups')->prime(true); foreach ($qb->getQuery() as $user) { - self::assertInstanceOf(InternalProxy::class, $user->getAccount()); + self::assertInstanceOf(Proxy::class, $user->getAccount()); self::assertFalse($this->uow->isUninitializedObject($user->getAccount())); self::assertCount(2, $user->getGroups()); @@ -104,7 +104,7 @@ public function testPrimeReferencesWithDBRefObjects(): void * initialized, they will not be hydrated as proxy objects. */ foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(InternalProxy::class, $group); + self::assertNotInstanceOf(Proxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -133,13 +133,13 @@ public function testPrimeReferencesWithSimpleReferences(): void ->field('users')->prime(true); foreach ($qb->getQuery() as $simpleUser) { - self::assertInstanceOf(InternalProxy::class, $simpleUser->getUser()); + self::assertInstanceOf(Proxy::class, $simpleUser->getUser()); self::assertFalse($this->uow->isUninitializedObject($simpleUser->getUser())); self::assertCount(2, $simpleUser->getUsers()); foreach ($simpleUser->getUsers() as $user) { - self::assertNotInstanceOf(InternalProxy::class, $user); + self::assertNotInstanceOf(Proxy::class, $user); self::assertInstanceOf(User::class, $user); } } @@ -188,20 +188,20 @@ public function testPrimeReferencesNestedInNamedEmbeddedReference(): void ->field('embeddedDocs.referencedDocs')->prime(true); foreach ($qb->getQuery() as $root) { - self::assertNotInstanceOf(InternalProxy::class, $root->embeddedDoc); + self::assertNotInstanceOf(Proxy::class, $root->embeddedDoc); self::assertInstanceOf(EmbeddedWhichReferences::class, $root->embeddedDoc); self::assertCount(2, $root->embeddedDocs); foreach ($root->embeddedDocs as $embeddedDoc) { - self::assertNotInstanceOf(InternalProxy::class, $embeddedDoc); + self::assertNotInstanceOf(Proxy::class, $embeddedDoc); self::assertInstanceOf(EmbeddedWhichReferences::class, $embeddedDoc); - self::assertInstanceOf(InternalProxy::class, $embeddedDoc->referencedDoc); + self::assertInstanceOf(Proxy::class, $embeddedDoc->referencedDoc); self::assertFalse($this->uow->isUninitializedObject($embeddedDoc->referencedDoc)); self::assertCount(2, $embeddedDoc->referencedDocs); foreach ($embeddedDoc->referencedDocs as $referencedDoc) { - self::assertNotInstanceOf(InternalProxy::class, $referencedDoc); + self::assertNotInstanceOf(Proxy::class, $referencedDoc); self::assertInstanceOf(Reference::class, $referencedDoc); } } @@ -252,37 +252,37 @@ public function testPrimeReferencesWithDifferentStoreAsReferences(): void assert($referenceUser instanceof ReferenceUser); $user = $referenceUser->getUser(); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(InternalProxy::class, $user); + self::assertInstanceOf(Proxy::class, $user); self::assertFalse($this->uow->isUninitializedObject($user)); self::assertCount(1, $referenceUser->getUsers()); foreach ($referenceUser->getUsers() as $user) { - self::assertNotInstanceOf(InternalProxy::class, $user); + self::assertNotInstanceOf(Proxy::class, $user); self::assertInstanceOf(User::class, $user); } $parentUser = $referenceUser->getParentUser(); - self::assertInstanceOf(InternalProxy::class, $parentUser); + self::assertInstanceOf(Proxy::class, $parentUser); self::assertInstanceOf(User::class, $parentUser); self::assertFalse($this->uow->isUninitializedObject($parentUser)); self::assertCount(1, $referenceUser->getParentUsers()); foreach ($referenceUser->getParentUsers() as $user) { - self::assertNotInstanceOf(InternalProxy::class, $user); + self::assertNotInstanceOf(Proxy::class, $user); self::assertInstanceOf(User::class, $user); } $otherUser = $referenceUser->getOtherUser(); self::assertInstanceOf(User::class, $otherUser); - self::assertInstanceOf(InternalProxy::class, $otherUser); + self::assertInstanceOf(Proxy::class, $otherUser); self::assertFalse($this->uow->isUninitializedObject($otherUser)); self::assertCount(1, $referenceUser->getOtherUsers()); foreach ($referenceUser->getOtherUsers() as $user) { - self::assertNotInstanceOf(InternalProxy::class, $user); + self::assertNotInstanceOf(Proxy::class, $user); self::assertInstanceOf(User::class, $user); } } @@ -309,10 +309,10 @@ public function testPrimeReferencesWithDiscriminatedReferenceMany(): void foreach ($qb->getQuery() as $user) { $favorites = $user->getFavorites()->toArray(); - self::assertNotInstanceOf(InternalProxy::class, $favorites[0]); + self::assertNotInstanceOf(Proxy::class, $favorites[0]); self::assertInstanceOf(Group::class, $favorites[0]); - self::assertNotInstanceOf(InternalProxy::class, $favorites[1]); + self::assertNotInstanceOf(Proxy::class, $favorites[1]); self::assertInstanceOf(Project::class, $favorites[1]); } } @@ -331,7 +331,7 @@ public function testPrimeReferencesWithDiscriminatedReferenceOne(): void ->field('server')->prime(true); foreach ($qb->getQuery() as $agent) { - self::assertInstanceOf(InternalProxy::class, $agent->server); + self::assertInstanceOf(Proxy::class, $agent->server); self::assertFalse($this->uow->isUninitializedObject($agent->server)); } } @@ -360,7 +360,7 @@ public function testPrimeReferencesIgnoresInitializedProxyObjects(): void self::assertCount(2, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(InternalProxy::class, $group); + self::assertNotInstanceOf(Proxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -440,7 +440,7 @@ public function testPrimeReferencesInFindAndModifyResult(): void self::assertCount(1, $user->getGroups()); foreach ($user->getGroups() as $group) { - self::assertNotInstanceOf(InternalProxy::class, $group); + self::assertNotInstanceOf(Proxy::class, $group); self::assertInstanceOf(Group::class, $group); } } @@ -472,7 +472,7 @@ public function testPrimeEmbeddedReferenceOneLevelDeep(): void $phonenumber = $phonenumbers->current(); - self::assertNotInstanceOf(InternalProxy::class, $phonenumber); + self::assertNotInstanceOf(Proxy::class, $phonenumber); self::assertInstanceOf(Phonenumber::class, $phonenumber); } @@ -523,7 +523,7 @@ public function testPrimeEmbeddedReferenceTwoLevelsDeep(): void $currency = $money->getCurrency(); - self::assertInstanceOf(InternalProxy::class, $currency); + self::assertInstanceOf(Proxy::class, $currency); self::assertInstanceOf(Currency::class, $currency); self::assertFalse($this->uow->isUninitializedObject($currency)); } @@ -551,7 +551,7 @@ public function testPrimeReferencesInReferenceMany(): void self::assertInstanceOf(BlogPost::class, $post); $comment = $post->comments->first(); - self::assertInstanceOf(InternalProxy::class, $comment->author); + self::assertInstanceOf(Proxy::class, $comment->author); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } @@ -578,7 +578,7 @@ public function testPrimeReferencesInReferenceManyWithRepositoryMethodEager(): v self::assertInstanceOf(BlogPost::class, $post); $comment = $post->repoCommentsWithPrimer->first(); - self::assertInstanceOf(InternalProxy::class, $comment->author); + self::assertInstanceOf(Proxy::class, $comment->author); self::assertFalse($this->uow->isUninitializedObject($comment->author)); } } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php index f72efa8dc5..b3804acfb5 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ReferencesTest.php @@ -12,8 +12,8 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\PersistentCollection; use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use Documents\Account; use Documents\Address; use Documents\Group; @@ -81,7 +81,7 @@ public function testLazyLoadReference(): void assert($profile instanceof Profile); self::assertInstanceOf(Profile::class, $profile); - self::assertInstanceOf(InternalProxy::class, $profile); + self::assertInstanceOf(Proxy::class, $profile); $profile->getFirstName(); @@ -103,7 +103,7 @@ public function testLazyLoadedWithNotifyPropertyChanged(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfileNotify(); - self::assertInstanceOf(InternalProxy::class, $profile); + self::assertInstanceOf(Proxy::class, $profile); self::assertTrue($this->uow->isUninitializedObject($profile)); $user->getProfileNotify()->setLastName('Malarz'); @@ -395,7 +395,7 @@ public function testDocumentNotFoundExceptionWithArrayId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(InternalProxy::class, $test->referenceOne); + self::assertInstanceOf(Proxy::class, $test->referenceOne); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithArrayId" document with identifier ' . @@ -428,7 +428,7 @@ public function testDocumentNotFoundExceptionWithObjectId(): void $user = $this->dm->find($user::class, $user->getId()); $profile = $user->getProfile(); - self::assertInstanceOf(InternalProxy::class, $profile); + self::assertInstanceOf(Proxy::class, $profile); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Documents\Profile" document with identifier "abcdefabcdefabcdefabcdef" could not be found.', @@ -459,7 +459,7 @@ public function testDocumentNotFoundExceptionWithMongoBinDataId(): void ); $test = $this->dm->find($test::class, $test->id); - self::assertInstanceOf(InternalProxy::class, $test->referenceOne); + self::assertInstanceOf(Proxy::class, $test->referenceOne); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Doctrine\ODM\MongoDB\Tests\Functional\DocumentWithMongoBinDataId" document with identifier ' . @@ -501,7 +501,7 @@ public function testDocumentNotFoundEvent(): void $this->dm->getEventManager()->addEventListener(Events::documentNotFound, new DocumentNotFoundListener($closure)); - self::assertInstanceOf(InternalProxy::class, $profile); + self::assertInstanceOf(Proxy::class, $profile); $this->expectException(DocumentNotFoundException::class); $this->expectExceptionMessage( 'The "Documents\Profile" document with identifier ' . diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php index 0a1e8ebfcf..b50c54e5cb 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/SimpleReferencesTest.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use Documents\SimpleReferenceUser; use Documents\User; use MongoDB\BSON\ObjectId; @@ -87,7 +88,7 @@ public function testProxy(): void assert($user instanceof User && $user instanceof InternalProxy); self::assertNotNull($user); self::assertInstanceOf(User::class, $user); - self::assertInstanceOf(InternalProxy::class, $user); + self::assertInstanceOf(Proxy::class, $user); self::assertTrue($this->uow->isUninitializedObject($user)); self::assertEquals('jwage', $user->getUsername()); self::assertFalse($this->uow->isUninitializedObject($user)); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php index c0aa3c18d7..6b83e26082 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH520Test.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; class GH520Test extends BaseTestCase { @@ -29,7 +29,7 @@ public function testPrimeWithGetSingleResult(): void $document = $query->getSingleResult(); self::assertInstanceOf(GH520Document::class, $document); - self::assertInstanceOf(InternalProxy::class, $document->ref); + self::assertInstanceOf(Proxy::class, $document->ref); self::assertFalse($this->uow->isUninitializedObject($document->ref)); } diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php index 0b6e653c3d..8b24404a36 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH593Test.php @@ -8,8 +8,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use function iterator_to_array; @@ -58,11 +58,11 @@ public function testReferenceManyOwningSidePreparesFilterCriteria(): void */ self::assertCount(2, $user1following); - self::assertInstanceOf(InternalProxy::class, $user1following[0]); + self::assertInstanceOf(Proxy::class, $user1following[0]); self::assertFalse($this->uow->isUninitializedObject($user1following[0])); self::assertEquals($user2->getId(), $user1following[0]->getId()); - self::assertInstanceOf(InternalProxy::class, $user1following[1]); + self::assertInstanceOf(Proxy::class, $user1following[1]); self::assertTrue($this->uow->isUninitializedObject($user1following[1])); self::assertEquals($user3->getId(), $user1following[1]->getId()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php index 5427f7f49c..299f84237a 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH602Test.php @@ -8,8 +8,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use function iterator_to_array; @@ -49,11 +49,11 @@ public function testReferenceManyOwningSidePreparesFilterCriteriaForDifferentCla */ self::assertCount(2, $user1likes); - self::assertInstanceOf(InternalProxy::class, $user1likes[0]); + self::assertInstanceOf(Proxy::class, $user1likes[0]); self::assertFalse($this->uow->isUninitializedObject($user1likes[0])); self::assertEquals($thing1->getId(), $user1likes[0]->getId()); - self::assertInstanceOf(InternalProxy::class, $user1likes[1]); + self::assertInstanceOf(Proxy::class, $user1likes[1]); self::assertTrue($this->uow->isUninitializedObject($user1likes[1])); self::assertEquals($thing2->getId(), $user1likes[1]->getId()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php index 8d2927da73..eeb81be210 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH852Test.php @@ -10,8 +10,8 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\ODM\MongoDB\Iterator\Iterator; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; use MongoDB\BSON\Binary; use PHPUnit\Framework\Attributes\DataProvider; @@ -49,7 +49,7 @@ public function testA(Closure $idGenerator): void self::assertEquals($idGenerator('parent'), $parent->id); self::assertEquals('parent', $parent->name); - self::assertInstanceOf(InternalProxy::class, $parent->refOne); + self::assertInstanceOf(Proxy::class, $parent->refOne); self::assertInstanceOf(GH852Document::class, $parent->refOne); self::assertTrue($this->uow->isUninitializedObject($parent->refOne)); self::assertEquals($idGenerator('childA'), $parent->refOne->id); @@ -61,13 +61,13 @@ public function testA(Closure $idGenerator): void /* These proxies will be initialized when we first access the collection * by DocumentPersister::loadReferenceManyCollectionOwningSide(). */ - self::assertInstanceOf(InternalProxy::class, $parent->refMany[0]); + self::assertInstanceOf(Proxy::class, $parent->refMany[0]); self::assertInstanceOf(GH852Document::class, $parent->refMany[0]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[0])); self::assertEquals($idGenerator('childB'), $parent->refMany[0]->id); self::assertEquals('childB', $parent->refMany[0]->name); - self::assertInstanceOf(InternalProxy::class, $parent->refMany[1]); + self::assertInstanceOf(Proxy::class, $parent->refMany[1]); self::assertInstanceOf(GH852Document::class, $parent->refMany[1]); self::assertFalse($this->uow->isUninitializedObject($parent->refMany[1])); self::assertEquals($idGenerator('childC'), $parent->refMany[1]->id); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php index 480c8a19ec..200992faee 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH936Test.php @@ -7,8 +7,8 @@ use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; use Doctrine\ODM\MongoDB\Events; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; +use Doctrine\Persistence\Proxy; class GH936Test extends BaseTestCase { @@ -27,7 +27,7 @@ public function testRemoveCascadesThroughProxyDocuments(): void $foo = $this->dm->find(GH936Document::class, $foo->id); - self::assertInstanceOf(InternalProxy::class, $foo->ref); + self::assertInstanceOf(Proxy::class, $foo->ref); $this->dm->remove($foo); $this->dm->flush(); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php index 768440338b..e7e0050d63 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Functional/ViewTest.php @@ -4,10 +4,10 @@ namespace Doctrine\ODM\MongoDB\Tests\Functional; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Repository\ViewRepository; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\UnitOfWork; +use Doctrine\Persistence\Proxy; use Documents\CmsUser; use Documents\UserName; use Documents\ViewReference; @@ -112,7 +112,7 @@ public function testViewReferences(): void $viewReference = $this->dm->find(ViewReference::class, $alcaeus->getId()); self::assertInstanceOf(ViewReference::class, $viewReference); - self::assertInstanceOf(InternalProxy::class, $viewReference->getReferenceOneView()); + self::assertInstanceOf(Proxy::class, $viewReference->getReferenceOneView()); self::assertSame($malarzm->getId(), $viewReference->getReferenceOneView()->getId()); // No proxies for inverse referenceOne @@ -120,7 +120,7 @@ public function testViewReferences(): void self::assertSame($alcaeus->getId(), $viewReference->getReferenceOneViewMappedBy()->getId()); self::assertCount(1, $viewReference->getReferenceManyView()); - self::assertInstanceOf(InternalProxy::class, $viewReference->getReferenceManyView()[0]); + self::assertInstanceOf(Proxy::class, $viewReference->getReferenceManyView()[0]); self::assertSame($malarzm->getId(), $viewReference->getReferenceManyView()[0]->getId()); // No proxies for inverse referenceMany diff --git a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php index 560e6e391c..cfbd946506 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/HydratorTest.php @@ -12,6 +12,7 @@ use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface; use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Query\Query; +use Doctrine\Persistence\Proxy; class HydratorTest extends BaseTestCase { @@ -41,10 +42,10 @@ public function testHydrator(): void self::assertEquals('jon', $user->name); self::assertInstanceOf(DateTime::class, $user->birthdate); self::assertInstanceOf(HydrationClosureReferenceOne::class, $user->referenceOne); - self::assertInstanceOf(InternalProxy::class, $user->referenceOne); + self::assertInstanceOf(Proxy::class, $user->referenceOne); self::assertInstanceOf(PersistentCollection::class, $user->referenceMany); - self::assertInstanceOf(InternalProxy::class, $user->referenceMany[0]); - self::assertInstanceOf(InternalProxy::class, $user->referenceMany[1]); + self::assertInstanceOf(Proxy::class, $user->referenceMany[0]); + self::assertInstanceOf(Proxy::class, $user->referenceMany[1]); self::assertInstanceOf(HydrationClosureEmbedOne::class, $user->embedOne); self::assertInstanceOf(PersistentCollection::class, $user->embedMany); self::assertEquals('jon', $user->embedOne->name); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php index fdebe7bd9b..2a0d72d5ca 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Mapping/ClassMetadataTest.php @@ -8,12 +8,12 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\MappingException; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Doctrine\ODM\MongoDB\Tests\ClassMetadataTestUtil; use Doctrine\ODM\MongoDB\Types\Type; use Doctrine\ODM\MongoDB\Utility\CollectionHelper; +use Doctrine\Persistence\Proxy; use Doctrine\Persistence\Reflection\EnumReflectionProperty; use DoctrineGlobal_Article; use DoctrineGlobal_User; @@ -493,7 +493,7 @@ public function testGetFieldValueInitializesProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getName(), $metadata->getFieldValue($proxy, 'name')); - self::assertInstanceOf(InternalProxy::class, $proxy); + self::assertInstanceOf(Proxy::class, $proxy); self::assertFalse($this->uow->isUninitializedObject($proxy)); } @@ -508,7 +508,7 @@ public function testGetFieldValueOfIdentifierDoesNotInitializeProxy(): void $metadata = $this->dm->getClassMetadata(Album::class); self::assertEquals($document->getId(), $metadata->getFieldValue($proxy, 'id')); - self::assertInstanceOf(InternalProxy::class, $proxy); + self::assertInstanceOf(Proxy::class, $proxy); self::assertTrue($this->uow->isUninitializedObject($proxy)); } @@ -530,7 +530,7 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(InternalProxy::class, $proxy); + self::assertInstanceOf(Proxy::class, $proxy); $metadata = $this->dm->getClassMetadata(Album::class); $metadata->setFieldValue($proxy, 'name', 'nevermind'); @@ -539,7 +539,7 @@ public function testSetFieldValueWithProxy(): void $this->dm->clear(); $proxy = $this->dm->getReference(Album::class, $document->getId()); - self::assertInstanceOf(InternalProxy::class, $proxy); + self::assertInstanceOf(Proxy::class, $proxy); self::assertInstanceOf(Album::class, $proxy); self::assertEquals('nevermind', $proxy->getName()); diff --git a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/ProxyFactoryTest.php similarity index 96% rename from tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php rename to tests/Doctrine/ODM/MongoDB/Tests/Proxy/ProxyFactoryTest.php index 825bee977b..62562def78 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/Proxy/LazyGhostProxyFactoryTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/Proxy/ProxyFactoryTest.php @@ -17,9 +17,9 @@ use MongoDB\Database; use PHPUnit\Framework\MockObject\MockObject; -class LazyGhostProxyFactoryTest extends BaseTestCase +class ProxyFactoryTest extends BaseTestCase { - /** @var Client|MockObject */ + /** @var Client&MockObject */ private Client $client; public function setUp(): void diff --git a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php index 96119aafbe..d1c0ab7b4f 100644 --- a/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php +++ b/tests/Doctrine/ODM/MongoDB/Tests/UnitOfWorkTest.php @@ -11,12 +11,12 @@ use Doctrine\ODM\MongoDB\APM\CommandLogger; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\MongoDBException; -use Doctrine\ODM\MongoDB\Proxy\InternalProxy; use Doctrine\ODM\MongoDB\Tests\Mocks\ExceptionThrowingListenerMock; use Doctrine\ODM\MongoDB\Tests\Mocks\PreUpdateListenerMock; use Doctrine\ODM\MongoDB\UnitOfWork; use Doctrine\Persistence\NotifyPropertyChanged; use Doctrine\Persistence\PropertyChangedListener; +use Doctrine\Persistence\Proxy; use Documents\Address; use Documents\File; use Documents\FileWithoutMetadata; @@ -489,7 +489,7 @@ public function testRecomputeChangesetForUninitializedProxyDoesNotCreateChangese $user = $this->dm->find(ForumUser::class, $id); self::assertInstanceOf(ForumUser::class, $user); - self::assertInstanceOf(InternalProxy::class, $user->getAvatar()); + self::assertInstanceOf(Proxy::class, $user->getAvatar()); $classMetadata = $this->dm->getClassMetadata(ForumAvatar::class); From f8f59565d41937c49479edd42c7ff6f290808414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 03:31:11 +0200 Subject: [PATCH 12/14] CS --- lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php | 2 +- phpstan-baseline.neon | 28 +++---------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php index 5d51eb7abf..d346a0c194 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/Autoloader.php @@ -5,7 +5,7 @@ namespace Doctrine\ODM\MongoDB\Proxy; use Closure; -use Doctrine\ODM\MongoDB\InvalidArgumentException; +use InvalidArgumentException; use function file_exists; use function ltrim; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3682c7fcc5..5fbaf29107 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -65,16 +65,6 @@ parameters: count: 1 path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Merge\\:\\:whenMatched\\(\\) has parameter \\$whenMatched with no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php - - - - message: "#^Property Doctrine\\\\ODM\\\\MongoDB\\\\Aggregation\\\\Stage\\\\Merge\\:\\:\\$whenMatched type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Merge.php - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" count: 1 @@ -541,27 +531,17 @@ parameters: path: lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php - - message: "#^Call to an undefined static method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\LazyGhostProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" + message: "#^Call to an undefined static method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\ProxyFactory\\:\\:createLazyGhost\\(\\)\\.$#" count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php - - - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\LazyGhostProxyFactory\\:\\:createLazyInitializer\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php - - - - message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" - count: 1 - path: lib/Doctrine/ODM/MongoDB/Proxy/LazyGhostProxyFactory.php + path: lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php - - message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\ProxyFactory\\:\\:generateProxyClasses\\(\\) has parameter \\$classes with no value type specified in iterable type array\\.$#" + message: "#^Method Doctrine\\\\ODM\\\\MongoDB\\\\Proxy\\\\ProxyFactory\\:\\:createLazyInitializer\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php - - message: "#^PHPDoc tag @param for parameter \\$classes with type Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata is incompatible with native type array\\.$#" + message: "#^Unable to resolve the template type T in call to method Doctrine\\\\ODM\\\\MongoDB\\\\DocumentManager\\:\\:getClassMetadata\\(\\)$#" count: 1 path: lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php From 606d3bcf608926f0e040f08991c9093b5bc6812e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 03:33:44 +0200 Subject: [PATCH 13/14] Remove duplicate constants --- lib/Doctrine/ODM/MongoDB/Configuration.php | 19 +++--- .../ODM/MongoDB/Proxy/ProxyFactory.php | 65 ++++--------------- psalm-baseline.xml | 35 ++++++++-- 3 files changed, 54 insertions(+), 65 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Configuration.php b/lib/Doctrine/ODM/MongoDB/Configuration.php index 8c321cf88e..8d390434b7 100644 --- a/lib/Doctrine/ODM/MongoDB/Configuration.php +++ b/lib/Doctrine/ODM/MongoDB/Configuration.php @@ -46,31 +46,31 @@ class Configuration { /** - * Never autogenerate a proxy and rely that it was generated by some - * process before deployment. + * Never autogenerate proxy/hydrator/persistent collection and rely that it + * was generated by some process before deployment. */ public const AUTOGENERATE_NEVER = 0; /** - * Always generates a new proxy in every request. + * Always generates a new proxy/hydrator/persistent collection in every request. * * This is only sane during development. */ public const AUTOGENERATE_ALWAYS = 1; /** - * Autogenerate the proxy class when the proxy file does not exist. + * Autogenerate the proxy/hydrator/persistent collection class when the + * proxy file does not exist. * - * This strategy causes a file_exists() call whenever any proxy is used the - * first time in a request. + * This strategy causes a file_exists() call whenever any proxy/hydrator is + * used the first time in a request. */ public const AUTOGENERATE_FILE_NOT_EXISTS = 2; /** - * Generate the proxy classes using eval(). + * Generate the proxy/hydrator/persistent collection classes using eval(). * - * This strategy is only sane for development, and even then it gives me - * the creeps a little. + * This strategy is only sane for development. */ public const AUTOGENERATE_EVAL = 3; @@ -90,6 +90,7 @@ class Configuration * @psalm-var array{ * autoGenerateHydratorClasses?: self::AUTOGENERATE_*, * autoGeneratePersistentCollectionClasses?: self::AUTOGENERATE_*, + * autoGenerateProxyClasses?: self::AUTOGENERATE_*, * classMetadataFactoryName?: class-string, * defaultCommitOptions?: CommitOptions, * defaultDocumentRepositoryClassName?: class-string>, diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php index 0b3ea9e64c..ef2bc91902 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php @@ -5,6 +5,7 @@ namespace Doctrine\ODM\MongoDB\Proxy; use Closure; +use Doctrine\ODM\MongoDB\Configuration; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\DocumentNotFoundException; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -25,7 +26,6 @@ use function file_put_contents; use function filemtime; use function get_debug_type; -use function is_bool; use function is_dir; use function is_int; use function is_scalar; @@ -51,45 +51,6 @@ */ class ProxyFactory { - /** - * Never autogenerate a proxy and rely that it was generated by some - * process before deployment. - */ - public const AUTOGENERATE_NEVER = 0; - - /** - * Always generates a new proxy in every request. - * - * This is only sane during development. - */ - public const AUTOGENERATE_ALWAYS = 1; - - /** - * Autogenerate the proxy class when the proxy file does not exist. - * - * This strategy causes a file_exists() call whenever any proxy is used the - * first time in a request. - */ - public const AUTOGENERATE_FILE_NOT_EXISTS = 2; - - /** - * Generate the proxy classes using eval(). - * - * This strategy is only sane for development, and even then it gives me - * the creeps a little. - */ - public const AUTOGENERATE_EVAL = 3; - - /** - * Autogenerate the proxy class when the proxy file does not exist or - * when the proxied file changed. - * - * This strategy causes a file_exists() call whenever any proxy is used the - * first time in a request. When the proxied file is changed, the proxy will - * be updated. - */ - public const AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED = 4; - private const PROXY_CLASS_TEMPLATE = <<<'EOPHP' */ @@ -128,16 +89,16 @@ public function __serialize(): array * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. * - * @param DocumentManager $dm The EntityManager the new factory works for. - * @param string $proxyDir The directory to use for the proxy classes. It must exist. - * @param string $proxyNs The namespace to use for the proxy classes. - * @param bool|self::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. + * @param DocumentManager $dm The EntityManager the new factory works for. + * @param string $proxyDir The directory to use for the proxy classes. It must exist. + * @param string $proxyNs The namespace to use for the proxy classes. + * @param bool|Configuration::AUTOGENERATE_* $autoGenerate The strategy for automatically generating proxy classes. */ public function __construct( private readonly DocumentManager $dm, private readonly string $proxyDir, private readonly string $proxyNs, - bool|int $autoGenerate = self::AUTOGENERATE_NEVER, + bool|int $autoGenerate = Configuration::AUTOGENERATE_NEVER, ) { if (! $proxyDir) { throw new InvalidArgumentException('You must configure a proxy directory. See docs for details'); @@ -147,12 +108,12 @@ public function __construct( throw new InvalidArgumentException('You must configure a proxy namespace'); } - if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) { + if (is_int($autoGenerate) && ($autoGenerate < 0 || $autoGenerate > 4)) { throw new InvalidArgumentException(sprintf('Invalid auto generate mode "%s" given.', is_scalar($autoGenerate) ? (string) $autoGenerate : get_debug_type($autoGenerate))); } $this->uow = $dm->getUnitOfWork(); - $this->autoGenerate = self::AUTOGENERATE_ALWAYS; // @todo (int) $autoGenerate; + $this->autoGenerate = (int) $autoGenerate; } /** @param array $identifier */ @@ -300,7 +261,7 @@ private function loadProxyClass(ClassMetadata $class): string return $proxyClassName; } - if ($this->autoGenerate === self::AUTOGENERATE_EVAL) { + if ($this->autoGenerate === Configuration::AUTOGENERATE_EVAL) { $this->generateProxyClass($class, null, $proxyClassName); return $proxyClassName; @@ -309,17 +270,17 @@ private function loadProxyClass(ClassMetadata $class): string $fileName = $this->getProxyFileName($class->getName(), $this->proxyDir); switch ($this->autoGenerate) { - case self::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: + case Configuration::AUTOGENERATE_FILE_NOT_EXISTS_OR_CHANGED: if (file_exists($fileName) && filemtime($fileName) >= filemtime($class->getReflectionClass()->getFileName())) { break; } // no break - case self::AUTOGENERATE_FILE_NOT_EXISTS: + case Configuration::AUTOGENERATE_FILE_NOT_EXISTS: if (file_exists($fileName)) { break; } // no break - case self::AUTOGENERATE_ALWAYS: + case Configuration::AUTOGENERATE_ALWAYS: $this->generateProxyClass($class, $fileName, $proxyClassName); break; } diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5a9cfda35a..d438ddf759 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -160,10 +160,37 @@ - - - - + + + + + + + + + + + + proxyFactories]]> + + + proxyFactories[$className] = $proxyFactory]]> + + + + + + + 4]]> + 4)]]> + 4)]]> + + + + + From 3b1c32edf57791a6bf27d02135494c24f67dfc7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Sat, 19 Oct 2024 04:22:21 +0200 Subject: [PATCH 14/14] Remove unused var --- lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php | 4 +--- psalm-baseline.xml | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php index ef2bc91902..145edaf539 100644 --- a/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ODM/MongoDB/Proxy/ProxyFactory.php @@ -17,7 +17,6 @@ use Symfony\Component\VarExporter\ProxyHelper; use function array_combine; -use function array_flip; use function bin2hex; use function chmod; use function class_exists; @@ -177,7 +176,7 @@ protected function skipClass(ClassMetadata $metadata): bool private function createLazyInitializer(ClassMetadata $classMetadata, DocumentPersister $persister): Closure { return static function (InternalProxy $proxy, mixed $identifier) use ($persister, $classMetadata): void { - $original = $persister->load(['_id' => $identifier]); + $original = $persister->load([$classMetadata->identifier => $identifier]); if ($original === null) { throw DocumentNotFoundException::documentNotFound( @@ -214,7 +213,6 @@ private function getProxyFactory(string $className): Closure { $skippedProperties = []; $class = $this->dm->getClassMetadata($className); - $identifiers = array_flip($class->getIdentifierFieldNames()); $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; $reflector = $class->getReflectionClass(); diff --git a/psalm-baseline.xml b/psalm-baseline.xml index d438ddf759..0363b8b0b9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -164,6 +164,9 @@ + + identifier => $identifier]]]> +