vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php line 291

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