vendor/doctrine/orm/src/Proxy/ProxyFactory.php line 365
<?phpdeclare(strict_types=1);namespace Doctrine\ORM\Proxy;use Closure;use Doctrine\Common\Proxy\AbstractProxyFactory;use Doctrine\Common\Proxy\Proxy as CommonProxy;use Doctrine\Common\Proxy\ProxyDefinition;use Doctrine\Common\Proxy\ProxyGenerator;use Doctrine\Deprecations\Deprecation;use Doctrine\ORM\EntityManagerInterface;use Doctrine\ORM\EntityNotFoundException;use Doctrine\ORM\ORMInvalidArgumentException;use Doctrine\ORM\Persisters\Entity\EntityPersister;use Doctrine\ORM\Proxy\Proxy as LegacyProxy;use Doctrine\ORM\UnitOfWork;use Doctrine\ORM\Utility\IdentifierFlattener;use Doctrine\Persistence\Mapping\ClassMetadata;use Doctrine\Persistence\Proxy;use ReflectionProperty;use Symfony\Component\VarExporter\ProxyHelper;use Throwable;use function array_combine;use function array_flip;use function array_intersect_key;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 is_bool;use function is_dir;use function is_int;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 str_replace;use function strpos;use function strrpos;use function strtr;use function substr;use function ucfirst;use const DIRECTORY_SEPARATOR;use const PHP_VERSION_ID;/*** This factory is used to create proxy objects for entities at runtime.*/class ProxyFactory extends 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 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'<?phpnamespace <namespace>;/*** DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE'S PROXY GENERATOR*/class <proxyShortClassName> extends \<className> implements \<baseProxyInterface>{<useLazyGhostTrait>public function __isInitialized(): bool{return isset($this->lazyObjectState) && $this->isLazyObjectInitialized();}public function __serialize(): array{<serializeImpl>}}EOPHP;/** @var EntityManagerInterface The EntityManager this factory is bound to. */private $em;/** @var UnitOfWork The UnitOfWork this factory uses to retrieve persisters */private $uow;/** @var string */private $proxyDir;/** @var string */private $proxyNs;/** @var self::AUTOGENERATE_* */private $autoGenerate;/*** The IdentifierFlattener used for manipulating identifiers** @var IdentifierFlattener*/private $identifierFlattener;/** @var array<class-string, Closure> */private $proxyFactories = [];/** @var bool */private $isLazyGhostObjectEnabled = true;/*** Initializes a new instance of the <tt>ProxyFactory</tt> class that is* connected to the given <tt>EntityManager</tt>.** @param EntityManagerInterface $em 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(EntityManagerInterface $em, $proxyDir, $proxyNs, $autoGenerate = self::AUTOGENERATE_NEVER){if (! $em->getConfiguration()->isLazyGhostObjectEnabled()) {if (PHP_VERSION_ID >= 80100) {Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/pull/10837/','Not enabling lazy ghost objects is deprecated and will not be supported in Doctrine ORM 3.0. Ensure Doctrine\ORM\Configuration::setLazyGhostObjectEnabled(true) is called to enable them.');}$this->isLazyGhostObjectEnabled = false;$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);$proxyGenerator->setPlaceholder('baseProxyInterface', LegacyProxy::class);parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate);}if (! $proxyDir) {throw ORMInvalidArgumentException::proxyDirectoryRequired();}if (! $proxyNs) {throw ORMInvalidArgumentException::proxyNamespaceRequired();}if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) {throw ORMInvalidArgumentException::invalidAutoGenerateMode($autoGenerate);}$this->em = $em;$this->uow = $em->getUnitOfWork();$this->proxyDir = $proxyDir;$this->proxyNs = $proxyNs;$this->autoGenerate = (int) $autoGenerate;$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());}/*** {@inheritDoc}*/public function getProxy($className, array $identifier){if (! $this->isLazyGhostObjectEnabled) {return parent::getProxy($className, $identifier);}$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, $proxyDir = null){if (! $this->isLazyGhostObjectEnabled) {return parent::generateProxyClasses($classes, $proxyDir);}$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;}/*** {@inheritDoc}** @deprecated ProxyFactory::resetUninitializedProxy() is deprecated and will be removed in version 3.0 of doctrine/orm.*/public function resetUninitializedProxy(CommonProxy $proxy){return parent::resetUninitializedProxy($proxy);}/*** {@inheritDoc}*/protected function skipClass(ClassMetadata $metadata){return $metadata->isMappedSuperclass|| $metadata->isEmbeddedClass|| $metadata->getReflectionClass()->isAbstract();}/*** {@inheritDoc}** @deprecated ProxyFactory::createProxyDefinition() is deprecated and will be removed in version 3.0 of doctrine/orm.*/protected function createProxyDefinition($className){$classMetadata = $this->em->getClassMetadata($className);$entityPersister = $this->uow->getEntityPersister($className);$initializer = $this->createInitializer($classMetadata, $entityPersister);$cloner = $this->createCloner($classMetadata, $entityPersister);return new ProxyDefinition(self::generateProxyClassName($className, $this->proxyNs),$classMetadata->getIdentifierFieldNames(),$classMetadata->getReflectionProperties(),$initializer,$cloner);}/*** Creates a closure capable of initializing a proxy** @deprecated ProxyFactory::createInitializer() is deprecated and will be removed in version 3.0 of doctrine/orm.** @psalm-return Closure(CommonProxy):void** @throws EntityNotFoundException*/private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure{$wakeupProxy = $classMetadata->getReflectionClass()->hasMethod('__wakeup');return function (CommonProxy $proxy) use ($entityPersister, $classMetadata, $wakeupProxy): void {$initializer = $proxy->__getInitializer();$cloner = $proxy->__getCloner();$proxy->__setInitializer(null);$proxy->__setCloner(null);if ($proxy->__isInitialized()) {return;}$properties = $proxy->__getLazyProperties();foreach ($properties as $propertyName => $property) {if (! isset($proxy->$propertyName)) {$proxy->$propertyName = $properties[$propertyName];}}$proxy->__setInitialized(true);if ($wakeupProxy) {$proxy->__wakeup();}$identifier = $classMetadata->getIdentifierValues($proxy);try {$entity = $entityPersister->loadById($identifier, $proxy);} catch (Throwable $exception) {$proxy->__setInitializer($initializer);$proxy->__setCloner($cloner);$proxy->__setInitialized(false);throw $exception;}if ($entity === null) {$proxy->__setInitializer($initializer);$proxy->__setCloner($cloner);$proxy->__setInitialized(false);throw EntityNotFoundException::fromClassNameAndIdentifier($classMetadata->getName(),$this->identifierFlattener->flattenIdentifier($classMetadata, $identifier));}};}/*** Creates a closure capable of initializing a proxy** @return Closure(InternalProxy, InternalProxy):void** @throws EntityNotFoundException*/private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure{return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {$identifier = $classMetadata->getIdentifierValues($proxy);$original = $entityPersister->loadById($identifier);if ($original === null) {throw EntityNotFoundException::fromClassNameAndIdentifier($classMetadata->getName(),$identifierFlattener->flattenIdentifier($classMetadata, $identifier));}if ($proxy === $original) {return;}$class = $entityPersister->getClassMetadata();foreach ($class->getReflectionProperties() as $property) {if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {continue;}$property->setValue($proxy, $property->getValue($original));}};}/*** Creates a closure capable of finalizing state a cloned proxy** @deprecated ProxyFactory::createCloner() is deprecated and will be removed in version 3.0 of doctrine/orm.** @psalm-return Closure(CommonProxy):void** @throws EntityNotFoundException*/private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister): Closure{return function (CommonProxy $proxy) use ($entityPersister, $classMetadata): void {if ($proxy->__isInitialized()) {return;}$proxy->__setInitialized(true);$proxy->__setInitializer(null);$class = $entityPersister->getClassMetadata();$identifier = $classMetadata->getIdentifierValues($proxy);$original = $entityPersister->loadById($identifier);if ($original === null) {throw EntityNotFoundException::fromClassNameAndIdentifier($classMetadata->getName(),$this->identifierFlattener->flattenIdentifier($classMetadata, $identifier));}foreach ($class->getReflectionProperties() as $property) {if (! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {continue;}$property->setAccessible(true);$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->em->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)) && ! 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$entityPersister = $this->uow->getEntityPersister($className);$initializer = $this->createLazyInitializer($class, $entityPersister, $this->identifierFlattener);$proxyClassName = $this->loadProxyClass($class);$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {$proxy = self::createLazyGhost($initializer, $skippedProperties);foreach ($identifierFields as $idField => $reflector) {if (! isset($identifier[$idField])) {throw ORMInvalidArgumentException::missingPrimaryKeyValue($className, $idField);}$reflector->setValue($proxy, $identifier[$idField]);}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 breakcase self::AUTOGENERATE_FILE_NOT_EXISTS:if (file_exists($fileName)) {break;}// no breakcase self::AUTOGENERATE_ALWAYS:$this->generateProxyClass($class, $fileName, $proxyClassName);break;}require $fileName;return $proxyClassName;}private function generateProxyClass(ClassMetadata $class, ?string $fileName, string $proxyClassName): void{$i = strrpos($proxyClassName, '\\');$placeholders = ['<className>' => $class->getName(),'<namespace>' => substr($proxyClassName, 0, $i),'<proxyShortClassName>' => substr($proxyClassName, 1 + $i),'<baseProxyInterface>' => 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 ORMInvalidArgumentException::proxyDirectoryNotWritable($this->proxyDir);}if (! is_writable($parentDirectory)) {throw ORMInvalidArgumentException::proxyDirectoryNotWritable($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 __load;setLazyObjectAsInitialized as public __setInitialized;isLazyObjectInitialized as private;createLazyGhost as private;resetLazyObject as private;}'), $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, '\\');}}