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