vendor/doctrine/common/src/Proxy/ProxyGenerator.php line 328

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Common\Proxy;
  3. use BackedEnum;
  4. use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
  5. use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
  6. use Doctrine\Common\Util\ClassUtils;
  7. use Doctrine\Persistence\Mapping\ClassMetadata;
  8. use ReflectionIntersectionType;
  9. use ReflectionMethod;
  10. use ReflectionNamedType;
  11. use ReflectionParameter;
  12. use ReflectionProperty;
  13. use ReflectionType;
  14. use ReflectionUnionType;
  15. use function array_combine;
  16. use function array_diff;
  17. use function array_key_exists;
  18. use function array_map;
  19. use function array_slice;
  20. use function array_unique;
  21. use function assert;
  22. use function bin2hex;
  23. use function call_user_func;
  24. use function chmod;
  25. use function class_exists;
  26. use function dirname;
  27. use function explode;
  28. use function file;
  29. use function file_put_contents;
  30. use function get_class;
  31. use function implode;
  32. use function in_array;
  33. use function interface_exists;
  34. use function is_callable;
  35. use function is_dir;
  36. use function is_scalar;
  37. use function is_string;
  38. use function is_writable;
  39. use function lcfirst;
  40. use function ltrim;
  41. use function method_exists;
  42. use function mkdir;
  43. use function preg_match;
  44. use function preg_match_all;
  45. use function preg_replace;
  46. use function preg_split;
  47. use function random_bytes;
  48. use function rename;
  49. use function rtrim;
  50. use function sprintf;
  51. use function str_replace;
  52. use function strpos;
  53. use function strrev;
  54. use function strtolower;
  55. use function strtr;
  56. use function substr;
  57. use function trim;
  58. use function var_export;
  59. use const DIRECTORY_SEPARATOR;
  60. use const PHP_VERSION_ID;
  61. use const PREG_SPLIT_DELIM_CAPTURE;
  62. /**
  63.  * This factory is used to generate proxy classes.
  64.  * It builds proxies from given parameters, a template and class metadata.
  65.  */
  66. class ProxyGenerator
  67. {
  68.     /**
  69.      * Used to match very simple id methods that don't need
  70.      * to be decorated since the identifier is known.
  71.      */
  72.     public const PATTERN_MATCH_ID_METHOD = <<<'EOT'
  73. ((?(DEFINE)
  74.   (?<type>\\?[a-z_\x7f-\xff][\w\x7f-\xff]*(?:\\[a-z_\x7f-\xff][\w\x7f-\xff]*)*)
  75.   (?<intersection_type>(?&type)\s*&\s*(?&type))
  76.   (?<union_type>(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type))(?:\s*\|\s*(?:(?:\(\s*(?&intersection_type)\s*\))|(?&type)))+)
  77. )(?:public\s+)?(?:function\s+%s\s*\(\)\s*)\s*(?::\s*(?:(?&union_type)|(?&intersection_type)|(?:\??(?&type)))\s*)?{\s*return\s*\$this->%s;\s*})i
  78. EOT;
  79.     /**
  80.      * The namespace that contains all proxy classes.
  81.      *
  82.      * @var string
  83.      */
  84.     private $proxyNamespace;
  85.     /**
  86.      * The directory that contains all proxy classes.
  87.      *
  88.      * @var string
  89.      */
  90.     private $proxyDirectory;
  91.     /**
  92.      * Map of callables used to fill in placeholders set in the template.
  93.      *
  94.      * @var string[]|callable[]
  95.      */
  96.     protected $placeholders = [
  97.         'baseProxyInterface'   => Proxy::class,
  98.         'additionalProperties' => '',
  99.     ];
  100.     /**
  101.      * Template used as a blueprint to generate proxies.
  102.      *
  103.      * @var string
  104.      */
  105.     protected $proxyClassTemplate '<?php
  106. namespace <namespace>;
  107. <enumUseStatements>
  108. /**
  109.  * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
  110.  */
  111. class <proxyShortClassName> extends \<className> implements \<baseProxyInterface>
  112. {
  113.     /**
  114.      * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with
  115.      *      three parameters, being respectively the proxy object to be initialized, the method that triggered the
  116.      *      initialization process and an array of ordered parameters that were passed to that method.
  117.      *
  118.      * @see \Doctrine\Common\Proxy\Proxy::__setInitializer
  119.      */
  120.     public $__initializer__;
  121.     /**
  122.      * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
  123.      *
  124.      * @see \Doctrine\Common\Proxy\Proxy::__setCloner
  125.      */
  126.     public $__cloner__;
  127.     /**
  128.      * @var boolean flag indicating if this object was already initialized
  129.      *
  130.      * @see \Doctrine\Persistence\Proxy::__isInitialized
  131.      */
  132.     public $__isInitialized__ = false;
  133.     /**
  134.      * @var array<string, null> properties to be lazy loaded, indexed by property name
  135.      */
  136.     public static $lazyPropertiesNames = <lazyPropertiesNames>;
  137.     /**
  138.      * @var array<string, mixed> default values of properties to be lazy loaded, with keys being the property names
  139.      *
  140.      * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties
  141.      */
  142.     public static $lazyPropertiesDefaults = <lazyPropertiesDefaults>;
  143. <additionalProperties>
  144. <constructorImpl>
  145. <magicGet>
  146. <magicSet>
  147. <magicIsset>
  148. <sleepImpl>
  149. <wakeupImpl>
  150. <cloneImpl>
  151.     /**
  152.      * Forces initialization of the proxy
  153.      */
  154.     public function __load(): void
  155.     {
  156.         $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []);
  157.     }
  158.     /**
  159.      * {@inheritDoc}
  160.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  161.      */
  162.     public function __isInitialized(): bool
  163.     {
  164.         return $this->__isInitialized__;
  165.     }
  166.     /**
  167.      * {@inheritDoc}
  168.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  169.      */
  170.     public function __setInitialized($initialized): void
  171.     {
  172.         $this->__isInitialized__ = $initialized;
  173.     }
  174.     /**
  175.      * {@inheritDoc}
  176.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  177.      */
  178.     public function __setInitializer(\Closure $initializer = null): void
  179.     {
  180.         $this->__initializer__ = $initializer;
  181.     }
  182.     /**
  183.      * {@inheritDoc}
  184.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  185.      */
  186.     public function __getInitializer(): ?\Closure
  187.     {
  188.         return $this->__initializer__;
  189.     }
  190.     /**
  191.      * {@inheritDoc}
  192.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  193.      */
  194.     public function __setCloner(\Closure $cloner = null): void
  195.     {
  196.         $this->__cloner__ = $cloner;
  197.     }
  198.     /**
  199.      * {@inheritDoc}
  200.      * @internal generated method: use only when explicitly handling proxy specific cloning logic
  201.      */
  202.     public function __getCloner(): ?\Closure
  203.     {
  204.         return $this->__cloner__;
  205.     }
  206.     /**
  207.      * {@inheritDoc}
  208.      * @internal generated method: use only when explicitly handling proxy specific loading logic
  209.      * @deprecated no longer in use - generated code now relies on internal components rather than generated public API
  210.      * @static
  211.      */
  212.     public function __getLazyProperties(): array
  213.     {
  214.         return self::$lazyPropertiesDefaults;
  215.     }
  216.     <methods>
  217. }
  218. ';
  219.     /**
  220.      * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
  221.      * connected to the given <tt>EntityManager</tt>.
  222.      *
  223.      * @param string $proxyDirectory The directory to use for the proxy classes. It must exist.
  224.      * @param string $proxyNamespace The namespace to use for the proxy classes.
  225.      *
  226.      * @throws InvalidArgumentException
  227.      */
  228.     public function __construct($proxyDirectory$proxyNamespace)
  229.     {
  230.         if (! $proxyDirectory) {
  231.             throw InvalidArgumentException::proxyDirectoryRequired();
  232.         }
  233.         if (! $proxyNamespace) {
  234.             throw InvalidArgumentException::proxyNamespaceRequired();
  235.         }
  236.         $this->proxyDirectory $proxyDirectory;
  237.         $this->proxyNamespace $proxyNamespace;
  238.     }
  239.     /**
  240.      * Sets a placeholder to be replaced in the template.
  241.      *
  242.      * @param string          $name
  243.      * @param string|callable $placeholder
  244.      *
  245.      * @throws InvalidArgumentException
  246.      */
  247.     public function setPlaceholder($name$placeholder)
  248.     {
  249.         if (! is_string($placeholder) && ! is_callable($placeholder)) {
  250.             throw InvalidArgumentException::invalidPlaceholder($name);
  251.         }
  252.         $this->placeholders[$name] = $placeholder;
  253.     }
  254.     /**
  255.      * Sets the base template used to create proxy classes.
  256.      *
  257.      * @param string $proxyClassTemplate
  258.      */
  259.     public function setProxyClassTemplate($proxyClassTemplate)
  260.     {
  261.         $this->proxyClassTemplate = (string) $proxyClassTemplate;
  262.     }
  263.     /**
  264.      * Generates a proxy class file.
  265.      *
  266.      * @param ClassMetadata $class    Metadata for the original class.
  267.      * @param string|bool   $fileName Filename (full path) for the generated class. If none is given, eval() is used.
  268.      *
  269.      * @throws InvalidArgumentException
  270.      * @throws UnexpectedValueException
  271.      */
  272.     public function generateProxyClass(ClassMetadata $class$fileName false)
  273.     {
  274.         $this->verifyClassCanBeProxied($class);
  275.         preg_match_all('(<([a-zA-Z]+)>)'$this->proxyClassTemplate$placeholderMatches);
  276.         $placeholderMatches array_combine($placeholderMatches[0], $placeholderMatches[1]);
  277.         $placeholders       = [];
  278.         foreach ($placeholderMatches as $placeholder => $name) {
  279.             $placeholders[$placeholder] = $this->placeholders[$name] ?? [$this'generate' $name];
  280.         }
  281.         foreach ($placeholders as & $placeholder) {
  282.             if (! is_callable($placeholder)) {
  283.                 continue;
  284.             }
  285.             $placeholder call_user_func($placeholder$class);
  286.         }
  287.         $proxyCode strtr($this->proxyClassTemplate$placeholders);
  288.         if (! $fileName) {
  289.             $proxyClassName $this->generateNamespace($class) . '\\' $this->generateProxyShortClassName($class);
  290.             if (! class_exists($proxyClassName)) {
  291.                 eval(substr($proxyCode5));
  292.             }
  293.             return;
  294.         }
  295.         $parentDirectory dirname($fileName);
  296.         if (! is_dir($parentDirectory) && (@mkdir($parentDirectory0775true) === false)) {
  297.             throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
  298.         }
  299.         if (! is_writable($parentDirectory)) {
  300.             throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
  301.         }
  302.         $tmpFileName $fileName '.' bin2hex(random_bytes(12));
  303.         file_put_contents($tmpFileName$proxyCode);
  304.         @chmod($tmpFileName0664);
  305.         rename($tmpFileName$fileName);
  306.     }
  307.     /** @throws InvalidArgumentException */
  308.     private function verifyClassCanBeProxied(ClassMetadata $class)
  309.     {
  310.         if ($class->getReflectionClass()->isFinal()) {
  311.             throw InvalidArgumentException::classMustNotBeFinal($class->getName());
  312.         }
  313.         if ($class->getReflectionClass()->isAbstract()) {
  314.             throw InvalidArgumentException::classMustNotBeAbstract($class->getName());
  315.         }
  316.         if (PHP_VERSION_ID >= 80200 && $class->getReflectionClass()->isReadOnly()) {
  317.             throw InvalidArgumentException::classMustNotBeReadOnly($class->getName());
  318.         }
  319.     }
  320.     /**
  321.      * Generates the proxy short class name to be used in the template.
  322.      *
  323.      * @return string
  324.      */
  325.     private function generateProxyShortClassName(ClassMetadata $class)
  326.     {
  327.         $proxyClassName ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  328.         $parts          explode('\\'strrev($proxyClassName), 2);
  329.         return strrev($parts[0]);
  330.     }
  331.     /**
  332.      * Generates the proxy namespace.
  333.      *
  334.      * @return string
  335.      */
  336.     private function generateNamespace(ClassMetadata $class)
  337.     {
  338.         $proxyClassName ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
  339.         $parts          explode('\\'strrev($proxyClassName), 2);
  340.         return strrev($parts[1]);
  341.     }
  342.     /**
  343.      * Enums must have a use statement when used as public property defaults.
  344.      */
  345.     public function generateEnumUseStatements(ClassMetadata $class): string
  346.     {
  347.         if (PHP_VERSION_ID 80100) {
  348.             return "\n";
  349.         }
  350.         $defaultProperties          $class->getReflectionClass()->getDefaultProperties();
  351.         $lazyLoadedPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  352.         $enumClasses                = [];
  353.         foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
  354.             $name $property->getName();
  355.             if (! in_array($name$lazyLoadedPublicPropertiestrue)) {
  356.                 continue;
  357.             }
  358.             if (array_key_exists($name$defaultProperties) && $defaultProperties[$name] instanceof BackedEnum) {
  359.                 $enumClassNameParts explode('\\'get_class($defaultProperties[$name]));
  360.                 $enumClasses[]      = $enumClassNameParts[0];
  361.             }
  362.         }
  363.         return implode(
  364.             "\n",
  365.             array_map(
  366.                 static function ($className) {
  367.                     return 'use ' $className ';';
  368.                 },
  369.                 array_unique($enumClasses)
  370.             )
  371.         ) . "\n";
  372.     }
  373.     /**
  374.      * Generates the original class name.
  375.      *
  376.      * @return string
  377.      */
  378.     private function generateClassName(ClassMetadata $class)
  379.     {
  380.         return ltrim($class->getName(), '\\');
  381.     }
  382.     /**
  383.      * Generates the array representation of lazy loaded public properties and their default values.
  384.      *
  385.      * @return string
  386.      */
  387.     private function generateLazyPropertiesNames(ClassMetadata $class)
  388.     {
  389.         $lazyPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  390.         $values               = [];
  391.         foreach ($lazyPublicProperties as $name) {
  392.             $values[$name] = null;
  393.         }
  394.         return var_export($valuestrue);
  395.     }
  396.     /**
  397.      * Generates the array representation of lazy loaded public properties names.
  398.      *
  399.      * @return string
  400.      */
  401.     private function generateLazyPropertiesDefaults(ClassMetadata $class)
  402.     {
  403.         return var_export($this->getLazyLoadedPublicProperties($class), true);
  404.     }
  405.     /**
  406.      * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
  407.      *
  408.      * @return string
  409.      */
  410.     private function generateConstructorImpl(ClassMetadata $class)
  411.     {
  412.         $constructorImpl = <<<'EOT'
  413.     public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null)
  414.     {
  415. EOT;
  416.         $toUnset array_map(static function (string $name): string {
  417.             return '$this->' $name;
  418.         }, $this->getLazyLoadedPublicPropertiesNames($class));
  419.         return $constructorImpl . ($toUnset === [] ? '' '        unset(' implode(', '$toUnset) . ");\n")
  420.             . <<<'EOT'
  421.         $this->__initializer__ = $initializer;
  422.         $this->__cloner__      = $cloner;
  423.     }
  424. EOT;
  425.     }
  426.     /**
  427.      * Generates the magic getter invoked when lazy loaded public properties are requested.
  428.      *
  429.      * @return string
  430.      */
  431.     private function generateMagicGet(ClassMetadata $class)
  432.     {
  433.         $lazyPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  434.         $reflectionClass      $class->getReflectionClass();
  435.         $hasParentGet         false;
  436.         $returnReference      '';
  437.         $inheritDoc           '';
  438.         $name                 '$name';
  439.         $parametersString     '$name';
  440.         $returnTypeHint       null;
  441.         if ($reflectionClass->hasMethod('__get')) {
  442.             $hasParentGet     true;
  443.             $inheritDoc       '{@inheritDoc}';
  444.             $methodReflection $reflectionClass->getMethod('__get');
  445.             if ($methodReflection->returnsReference()) {
  446.                 $returnReference '& ';
  447.             }
  448.             $methodParameters $methodReflection->getParameters();
  449.             $name             '$' $methodParameters[0]->getName();
  450.             $parametersString $this->buildParametersString($methodReflection->getParameters(), ['name']);
  451.             $returnTypeHint   $this->getMethodReturnType($methodReflection);
  452.         }
  453.         if (empty($lazyPublicProperties) && ! $hasParentGet) {
  454.             return '';
  455.         }
  456.         $magicGet = <<<EOT
  457.     /**
  458.      * $inheritDoc
  459.      * @param string \$name
  460.      */
  461.     public function {$returnReference}__get($parametersString)$returnTypeHint
  462.     {
  463. EOT;
  464.         if (! empty($lazyPublicProperties)) {
  465.             $magicGet .= <<<'EOT'
  466.         if (\array_key_exists($name, self::$lazyPropertiesNames)) {
  467.             $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
  468. EOT;
  469.             if ($returnTypeHint === ': void') {
  470.                 $magicGet .= "\n            return;";
  471.             } else {
  472.                 $magicGet .= "\n            return \$this->\$name;";
  473.             }
  474.             $magicGet .= <<<'EOT'
  475.         }
  476. EOT;
  477.         }
  478.         if ($hasParentGet) {
  479.             $magicGet .= <<<'EOT'
  480.         $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
  481. EOT;
  482.             if ($returnTypeHint === ': void') {
  483.                 $magicGet .= <<<'EOT'
  484.         parent::__get($name);
  485.         return;
  486. EOT;
  487.             } elseif ($returnTypeHint === ': never') {
  488.                 $magicGet .= <<<'EOT'
  489.         parent::__get($name);
  490. EOT;
  491.             } else {
  492.                 $magicGet .= <<<'EOT'
  493.         return parent::__get($name);
  494. EOT;
  495.             }
  496.         } else {
  497.             $magicGet .= sprintf(<<<EOT
  498.         trigger_error(sprintf('Undefined property: %%s::$%%s', __CLASS__, %s), E_USER_NOTICE);
  499. EOT
  500.                 , $name);
  501.         }
  502.         return $magicGet "\n    }";
  503.     }
  504.     /**
  505.      * Generates the magic setter (currently unused).
  506.      *
  507.      * @return string
  508.      */
  509.     private function generateMagicSet(ClassMetadata $class)
  510.     {
  511.         $lazyPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  512.         $reflectionClass      $class->getReflectionClass();
  513.         $hasParentSet         false;
  514.         $inheritDoc           '';
  515.         $parametersString     '$name, $value';
  516.         $returnTypeHint       null;
  517.         if ($reflectionClass->hasMethod('__set')) {
  518.             $hasParentSet     true;
  519.             $inheritDoc       '{@inheritDoc}';
  520.             $methodReflection $reflectionClass->getMethod('__set');
  521.             $parametersString $this->buildParametersString($methodReflection->getParameters(), ['name''value']);
  522.             $returnTypeHint   $this->getMethodReturnType($methodReflection);
  523.         }
  524.         if (empty($lazyPublicProperties) && ! $hasParentSet) {
  525.             return '';
  526.         }
  527.         $magicSet = <<<EOT
  528.     /**
  529.      * $inheritDoc
  530.      * @param string \$name
  531.      * @param mixed  \$value
  532.      */
  533.     public function __set($parametersString)$returnTypeHint
  534.     {
  535. EOT;
  536.         if (! empty($lazyPublicProperties)) {
  537.             $magicSet .= <<<'EOT'
  538.         if (\array_key_exists($name, self::$lazyPropertiesNames)) {
  539.             $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
  540.             $this->$name = $value;
  541.             return;
  542.         }
  543. EOT;
  544.         }
  545.         if ($hasParentSet) {
  546.             $magicSet .= <<<'EOT'
  547.         $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
  548. EOT;
  549.             if ($returnTypeHint === ': void') {
  550.                 $magicSet .= <<<'EOT'
  551.         parent::__set($name, $value);
  552.         return;
  553. EOT;
  554.             } elseif ($returnTypeHint === ': never') {
  555.                 $magicSet .= <<<'EOT'
  556.         parent::__set($name, $value);
  557. EOT;
  558.             } else {
  559.                 $magicSet .= <<<'EOT'
  560.         return parent::__set($name, $value);
  561. EOT;
  562.             }
  563.         } else {
  564.             $magicSet .= '        $this->$name = $value;';
  565.         }
  566.         return $magicSet "\n    }";
  567.     }
  568.     /**
  569.      * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
  570.      *
  571.      * @return string
  572.      */
  573.     private function generateMagicIsset(ClassMetadata $class)
  574.     {
  575.         $lazyPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  576.         $hasParentIsset       $class->getReflectionClass()->hasMethod('__isset');
  577.         $parametersString     '$name';
  578.         $returnTypeHint       null;
  579.         if ($hasParentIsset) {
  580.             $methodReflection $class->getReflectionClass()->getMethod('__isset');
  581.             $parametersString $this->buildParametersString($methodReflection->getParameters(), ['name']);
  582.             $returnTypeHint   $this->getMethodReturnType($methodReflection);
  583.         }
  584.         if (empty($lazyPublicProperties) && ! $hasParentIsset) {
  585.             return '';
  586.         }
  587.         $inheritDoc $hasParentIsset '{@inheritDoc}' '';
  588.         $magicIsset = <<<EOT
  589.     /**
  590.      * $inheritDoc
  591.      * @param  string \$name
  592.      * @return boolean
  593.      */
  594.     public function __isset($parametersString)$returnTypeHint
  595.     {
  596. EOT;
  597.         if (! empty($lazyPublicProperties)) {
  598.             $magicIsset .= <<<'EOT'
  599.         if (\array_key_exists($name, self::$lazyPropertiesNames)) {
  600.             $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
  601.             return isset($this->$name);
  602.         }
  603. EOT;
  604.         }
  605.         if ($hasParentIsset) {
  606.             $magicIsset .= <<<'EOT'
  607.         $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
  608.         return parent::__isset($name);
  609. EOT;
  610.         } else {
  611.             $magicIsset .= '        return false;';
  612.         }
  613.         return $magicIsset "\n    }";
  614.     }
  615.     /**
  616.      * Generates implementation for the `__sleep` method of proxies.
  617.      *
  618.      * @return string
  619.      */
  620.     private function generateSleepImpl(ClassMetadata $class)
  621.     {
  622.         $reflectionClass $class->getReflectionClass();
  623.         $hasParentSleep $reflectionClass->hasMethod('__sleep');
  624.         $inheritDoc     $hasParentSleep '{@inheritDoc}' '';
  625.         $returnTypeHint $hasParentSleep $this->getMethodReturnType($reflectionClass->getMethod('__sleep')) : '';
  626.         $sleepImpl      = <<<EOT
  627.     /**
  628.      * $inheritDoc
  629.      * @return array
  630.      */
  631.     public function __sleep()$returnTypeHint
  632.     {
  633. EOT;
  634.         if ($hasParentSleep) {
  635.             return $sleepImpl . <<<'EOT'
  636.         $properties = array_merge(['__isInitialized__'], parent::__sleep());
  637.         if ($this->__isInitialized__) {
  638.             $properties = array_diff($properties, array_keys(self::$lazyPropertiesNames));
  639.         }
  640.         return $properties;
  641.     }
  642. EOT;
  643.         }
  644.         $allProperties = ['__isInitialized__'];
  645.         foreach ($class->getReflectionClass()->getProperties() as $prop) {
  646.             assert($prop instanceof ReflectionProperty);
  647.             if ($prop->isStatic()) {
  648.                 continue;
  649.             }
  650.             $allProperties[] = $prop->isPrivate()
  651.                 ? "\0" $prop->getDeclaringClass()->getName() . "\0" $prop->getName()
  652.                 : $prop->getName();
  653.         }
  654.         $lazyPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  655.         $protectedProperties  array_diff($allProperties$lazyPublicProperties);
  656.         foreach ($allProperties as &$property) {
  657.             $property var_export($propertytrue);
  658.         }
  659.         foreach ($protectedProperties as &$property) {
  660.             $property var_export($propertytrue);
  661.         }
  662.         $allProperties       implode(', '$allProperties);
  663.         $protectedProperties implode(', '$protectedProperties);
  664.         return $sleepImpl . <<<EOT
  665.         if (\$this->__isInitialized__) {
  666.             return [$allProperties];
  667.         }
  668.         return [$protectedProperties];
  669.     }
  670. EOT;
  671.     }
  672.     /**
  673.      * Generates implementation for the `__wakeup` method of proxies.
  674.      *
  675.      * @return string
  676.      */
  677.     private function generateWakeupImpl(ClassMetadata $class)
  678.     {
  679.         $reflectionClass $class->getReflectionClass();
  680.         $hasParentWakeup $reflectionClass->hasMethod('__wakeup');
  681.         $unsetPublicProperties = [];
  682.         foreach ($this->getLazyLoadedPublicPropertiesNames($class) as $lazyPublicProperty) {
  683.             $unsetPublicProperties[] = '$this->' $lazyPublicProperty;
  684.         }
  685.         $shortName      $this->generateProxyShortClassName($class);
  686.         $inheritDoc     $hasParentWakeup '{@inheritDoc}' '';
  687.         $returnTypeHint $hasParentWakeup $this->getMethodReturnType($reflectionClass->getMethod('__wakeup')) : '';
  688.         $wakeupImpl     = <<<EOT
  689.     /**
  690.      * $inheritDoc
  691.      */
  692.     public function __wakeup()$returnTypeHint
  693.     {
  694.         if ( ! \$this->__isInitialized__) {
  695.             \$this->__initializer__ = function ($shortName \$proxy) {
  696.                 \$proxy->__setInitializer(null);
  697.                 \$proxy->__setCloner(null);
  698.                 \$existingProperties = get_object_vars(\$proxy);
  699.                 foreach (\$proxy::\$lazyPropertiesDefaults as \$property => \$defaultValue) {
  700.                     if ( ! array_key_exists(\$property, \$existingProperties)) {
  701.                         \$proxy->\$property = \$defaultValue;
  702.                     }
  703.                 }
  704.             };
  705. EOT;
  706.         if (! empty($unsetPublicProperties)) {
  707.             $wakeupImpl .= "\n            unset(" implode(', '$unsetPublicProperties) . ');';
  708.         }
  709.         $wakeupImpl .= "\n        }";
  710.         if ($hasParentWakeup) {
  711.             $wakeupImpl .= "\n        parent::__wakeup();";
  712.         }
  713.         $wakeupImpl .= "\n    }";
  714.         return $wakeupImpl;
  715.     }
  716.     /**
  717.      * Generates implementation for the `__clone` method of proxies.
  718.      *
  719.      * @return string
  720.      */
  721.     private function generateCloneImpl(ClassMetadata $class)
  722.     {
  723.         $reflectionClass $class->getReflectionClass();
  724.         $hasParentClone  $reflectionClass->hasMethod('__clone');
  725.         $returnTypeHint  $hasParentClone $this->getMethodReturnType($reflectionClass->getMethod('__clone')) : '';
  726.         $inheritDoc      $hasParentClone '{@inheritDoc}' '';
  727.         $callParentClone $hasParentClone "\n        parent::__clone();\n" '';
  728.         return <<<EOT
  729.     /**
  730.      * $inheritDoc
  731.      */
  732.     public function __clone()$returnTypeHint
  733.     {
  734.         \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []);
  735. $callParentClone    }
  736. EOT;
  737.     }
  738.     /**
  739.      * Generates decorated methods by picking those available in the parent class.
  740.      *
  741.      * @return string
  742.      */
  743.     private function generateMethods(ClassMetadata $class)
  744.     {
  745.         $methods           '';
  746.         $methodNames       = [];
  747.         $reflectionMethods $class->getReflectionClass()->getMethods(ReflectionMethod::IS_PUBLIC);
  748.         $skippedMethods    = [
  749.             '__sleep'   => true,
  750.             '__clone'   => true,
  751.             '__wakeup'  => true,
  752.             '__get'     => true,
  753.             '__set'     => true,
  754.             '__isset'   => true,
  755.         ];
  756.         foreach ($reflectionMethods as $method) {
  757.             $name $method->getName();
  758.             if (
  759.                 $method->isConstructor() ||
  760.                 isset($skippedMethods[strtolower($name)]) ||
  761.                 isset($methodNames[$name]) ||
  762.                 $method->isFinal() ||
  763.                 $method->isStatic() ||
  764.                 ( ! $method->isPublic())
  765.             ) {
  766.                 continue;
  767.             }
  768.             $methodNames[$name] = true;
  769.             $methods           .= "\n    /**\n"
  770.                 "     * {@inheritDoc}\n"
  771.                 "     */\n"
  772.                 '    public function ';
  773.             if ($method->returnsReference()) {
  774.                 $methods .= '&';
  775.             }
  776.             $methods .= $name '(' $this->buildParametersString($method->getParameters()) . ')';
  777.             $methods .= $this->getMethodReturnType($method);
  778.             $methods .= "\n" '    {' "\n";
  779.             if ($this->isShortIdentifierGetter($method$class)) {
  780.                 $identifier lcfirst(substr($name3));
  781.                 $fieldType  $class->getTypeOfField($identifier);
  782.                 $cast       in_array($fieldType, ['integer''smallint']) ? '(int) ' '';
  783.                 $methods .= '        if ($this->__isInitialized__ === false) {' "\n";
  784.                 $methods .= '            ';
  785.                 $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' '';
  786.                 $methods .= $cast ' parent::' $method->getName() . "();\n";
  787.                 $methods .= '        }' "\n\n";
  788.             }
  789.             $invokeParamsString implode(', '$this->getParameterNamesForInvoke($method->getParameters()));
  790.             $callParamsString   implode(', '$this->getParameterNamesForParentCall($method->getParameters()));
  791.             $methods .= "\n        \$this->__initializer__ "
  792.                 '&& $this->__initializer__->__invoke($this, ' var_export($nametrue)
  793.                 . ', [' $invokeParamsString ']);'
  794.                 "\n\n        "
  795.                 . ($this->shouldProxiedMethodReturn($method) ? 'return ' '')
  796.                 . 'parent::' $name '(' $callParamsString ');'
  797.                 "\n" '    }' "\n";
  798.         }
  799.         return $methods;
  800.     }
  801.     /**
  802.      * Generates the Proxy file name.
  803.      *
  804.      * @param string $className
  805.      * @param string $baseDirectory Optional base directory for proxy file name generation.
  806.      *                              If not specified, the directory configured on the Configuration of the
  807.      *                              EntityManager will be used by this factory.
  808.      * @psalm-param class-string $className
  809.      *
  810.      * @return string
  811.      */
  812.     public function getProxyFileName($className$baseDirectory null)
  813.     {
  814.         $baseDirectory $baseDirectory ?: $this->proxyDirectory;
  815.         return rtrim($baseDirectoryDIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR Proxy::MARKER
  816.             str_replace('\\'''$className) . '.php';
  817.     }
  818.     /**
  819.      * Checks if the method is a short identifier getter.
  820.      *
  821.      * What does this mean? For proxy objects the identifier is already known,
  822.      * however accessing the getter for this identifier usually triggers the
  823.      * lazy loading, leading to a query that may not be necessary if only the
  824.      * ID is interesting for the userland code (for example in views that
  825.      * generate links to the entity, but do not display anything else).
  826.      *
  827.      * @param ReflectionMethod $method
  828.      *
  829.      * @return bool
  830.      */
  831.     private function isShortIdentifierGetter($methodClassMetadata $class)
  832.     {
  833.         $identifier lcfirst(substr($method->getName(), 3));
  834.         $startLine  $method->getStartLine();
  835.         $endLine    $method->getEndLine();
  836.         $cheapCheck $method->getNumberOfParameters() === 0
  837.             && substr($method->getName(), 03) === 'get'
  838.             && in_array($identifier$class->getIdentifier(), true)
  839.             && $class->hasField($identifier)
  840.             && ($endLine $startLine <= 4);
  841.         if ($cheapCheck) {
  842.             $code file($method->getFileName());
  843.             $code trim(implode(' 'array_slice($code$startLine 1$endLine $startLine 1)));
  844.             $pattern sprintf(self::PATTERN_MATCH_ID_METHOD$method->getName(), $identifier);
  845.             if (preg_match($pattern$code)) {
  846.                 return true;
  847.             }
  848.         }
  849.         return false;
  850.     }
  851.     /**
  852.      * Generates the list of public properties to be lazy loaded.
  853.      *
  854.      * @return array<int, string>
  855.      */
  856.     private function getLazyLoadedPublicPropertiesNames(ClassMetadata $class): array
  857.     {
  858.         $properties = [];
  859.         foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
  860.             $name $property->getName();
  861.             if ((! $class->hasField($name) && ! $class->hasAssociation($name)) || $class->isIdentifier($name)) {
  862.                 continue;
  863.             }
  864.             $properties[] = $name;
  865.         }
  866.         return $properties;
  867.     }
  868.     /**
  869.      * Generates the list of default values of public properties.
  870.      *
  871.      * @return mixed[]
  872.      */
  873.     private function getLazyLoadedPublicProperties(ClassMetadata $class)
  874.     {
  875.         $defaultProperties          $class->getReflectionClass()->getDefaultProperties();
  876.         $lazyLoadedPublicProperties $this->getLazyLoadedPublicPropertiesNames($class);
  877.         $defaultValues              = [];
  878.         foreach ($class->getReflectionClass()->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
  879.             $name $property->getName();
  880.             if (! in_array($name$lazyLoadedPublicPropertiestrue)) {
  881.                 continue;
  882.             }
  883.             if (array_key_exists($name$defaultProperties)) {
  884.                 $defaultValues[$name] = $defaultProperties[$name];
  885.             } elseif (method_exists($property'getType')) {
  886.                 $propertyType $property->getType();
  887.                 if ($propertyType !== null && $propertyType->allowsNull()) {
  888.                     $defaultValues[$name] = null;
  889.                 }
  890.             }
  891.         }
  892.         return $defaultValues;
  893.     }
  894.     /**
  895.      * @param ReflectionParameter[] $parameters
  896.      * @param string[]              $renameParameters
  897.      *
  898.      * @return string
  899.      */
  900.     private function buildParametersString(array $parameters, array $renameParameters = [])
  901.     {
  902.         $parameterDefinitions = [];
  903.         $i = -1;
  904.         foreach ($parameters as $param) {
  905.             assert($param instanceof ReflectionParameter);
  906.             $i++;
  907.             $parameterDefinition '';
  908.             $parameterType       $this->getParameterType($param);
  909.             if ($parameterType !== null) {
  910.                 $parameterDefinition .= $parameterType ' ';
  911.             }
  912.             if ($param->isPassedByReference()) {
  913.                 $parameterDefinition .= '&';
  914.             }
  915.             if ($param->isVariadic()) {
  916.                 $parameterDefinition .= '...';
  917.             }
  918.             $parameterDefinition .= '$' . ($renameParameters $renameParameters[$i] : $param->getName());
  919.             $parameterDefinition .= $this->getParameterDefaultValue($param);
  920.             $parameterDefinitions[] = $parameterDefinition;
  921.         }
  922.         return implode(', '$parameterDefinitions);
  923.     }
  924.     /** @return string|null */
  925.     private function getParameterType(ReflectionParameter $parameter)
  926.     {
  927.         if (! $parameter->hasType()) {
  928.             return null;
  929.         }
  930.         $declaringFunction $parameter->getDeclaringFunction();
  931.         assert($declaringFunction instanceof ReflectionMethod);
  932.         return $this->formatType($parameter->getType(), $declaringFunction$parameter);
  933.     }
  934.     /** @return string */
  935.     private function getParameterDefaultValue(ReflectionParameter $parameter)
  936.     {
  937.         if (! $parameter->isDefaultValueAvailable()) {
  938.             return '';
  939.         }
  940.         if (PHP_VERSION_ID 80100 || is_scalar($parameter->getDefaultValue())) {
  941.             return ' = ' var_export($parameter->getDefaultValue(), true);
  942.         }
  943.         $value rtrim(substr(explode('$' $parameter->getName() . ' = ', (string) $parameter2)[1], 0, -2));
  944.         if (strpos($value'\\') !== false || strpos($value'::') !== false) {
  945.             $value preg_split("/('(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"$value, -1PREG_SPLIT_DELIM_CAPTURE);
  946.             foreach ($value as $i => $part) {
  947.                 if ($i === 0) {
  948.                     $value[$i] = preg_replace('/(?<![a-zA-Z0-9_\x7f-\xff\\\\])[a-zA-Z0-9_\x7f-\xff]++(?:\\\\[a-zA-Z0-9_\x7f-\xff]++|::)++/''\\\\\0'$part);
  949.                 }
  950.             }
  951.             $value implode(''$value);
  952.         }
  953.         return ' = ' $value;
  954.     }
  955.     /**
  956.      * @param ReflectionParameter[] $parameters
  957.      *
  958.      * @return string[]
  959.      */
  960.     private function getParameterNamesForInvoke(array $parameters)
  961.     {
  962.         return array_map(
  963.             static function (ReflectionParameter $parameter) {
  964.                 return '$' $parameter->getName();
  965.             },
  966.             $parameters
  967.         );
  968.     }
  969.     /**
  970.      * @param ReflectionParameter[] $parameters
  971.      *
  972.      * @return string[]
  973.      */
  974.     private function getParameterNamesForParentCall(array $parameters)
  975.     {
  976.         return array_map(
  977.             static function (ReflectionParameter $parameter) {
  978.                 $name '';
  979.                 if ($parameter->isVariadic()) {
  980.                     $name .= '...';
  981.                 }
  982.                 $name .= '$' $parameter->getName();
  983.                 return $name;
  984.             },
  985.             $parameters
  986.         );
  987.     }
  988.     /** @return string */
  989.     private function getMethodReturnType(ReflectionMethod $method)
  990.     {
  991.         if (! $method->hasReturnType()) {
  992.             return '';
  993.         }
  994.         return ': ' $this->formatType($method->getReturnType(), $method);
  995.     }
  996.     /** @return bool */
  997.     private function shouldProxiedMethodReturn(ReflectionMethod $method)
  998.     {
  999.         if (! $method->hasReturnType()) {
  1000.             return true;
  1001.         }
  1002.         return ! in_array(
  1003.             strtolower($this->formatType($method->getReturnType(), $method)),
  1004.             ['void''never'],
  1005.             true
  1006.         );
  1007.     }
  1008.     /** @return string */
  1009.     private function formatType(
  1010.         ReflectionType $type,
  1011.         ReflectionMethod $method,
  1012.         ?ReflectionParameter $parameter null
  1013.     ) {
  1014.         if ($type instanceof ReflectionUnionType) {
  1015.             return implode('|'array_map(
  1016.                 function (ReflectionType $unionedType) use ($method$parameter) {
  1017.                     if ($unionedType instanceof ReflectionIntersectionType) {
  1018.                         return '(' $this->formatType($unionedType$method$parameter) . ')';
  1019.                     }
  1020.                     return $this->formatType($unionedType$method$parameter);
  1021.                 },
  1022.                 $type->getTypes()
  1023.             ));
  1024.         }
  1025.         if ($type instanceof ReflectionIntersectionType) {
  1026.             return implode('&'array_map(
  1027.                 function (ReflectionType $intersectedType) use ($method$parameter) {
  1028.                     return $this->formatType($intersectedType$method$parameter);
  1029.                 },
  1030.                 $type->getTypes()
  1031.             ));
  1032.         }
  1033.         assert($type instanceof ReflectionNamedType);
  1034.         $name      $type->getName();
  1035.         $nameLower strtolower($name);
  1036.         if ($nameLower === 'static') {
  1037.             $name 'static';
  1038.         }
  1039.         if ($nameLower === 'self') {
  1040.             $name $method->getDeclaringClass()->getName();
  1041.         }
  1042.         if ($nameLower === 'parent') {
  1043.             $name $method->getDeclaringClass()->getParentClass()->getName();
  1044.         }
  1045.         if (! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name) && $name !== 'static') {
  1046.             if ($parameter !== null) {
  1047.                 throw UnexpectedValueException::invalidParameterTypeHint(
  1048.                     $method->getDeclaringClass()->getName(),
  1049.                     $method->getName(),
  1050.                     $parameter->getName()
  1051.                 );
  1052.             }
  1053.             throw UnexpectedValueException::invalidReturnTypeHint(
  1054.                 $method->getDeclaringClass()->getName(),
  1055.                 $method->getName()
  1056.             );
  1057.         }
  1058.         if (! $type->isBuiltin() && $name !== 'static') {
  1059.             $name '\\' $name;
  1060.         }
  1061.         if (
  1062.             $type->allowsNull()
  1063.             && ! in_array($name, ['mixed''null'], true)
  1064.             && ($parameter === null || ! $parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== null)
  1065.         ) {
  1066.             $name '?' $name;
  1067.         }
  1068.         return $name;
  1069.     }
  1070. }