62f4167f37cbfa6db6a70ab76f2e92d8d174787c
[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 UnexpectedValueException
260      */
261     public function generateProxyClass(ClassMetadata $class, $fileName = false)
262     {
263         preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);
264
265         $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]);
266         $placeholders       = [];
267
268         foreach ($placeholderMatches as $placeholder => $name) {
269             $placeholders[$placeholder] = isset($this->placeholders[$name])
270                 ? $this->placeholders[$name]
271                 : [$this, 'generate' . $name];
272         }
273
274         foreach ($placeholders as & $placeholder) {
275             if (is_callable($placeholder)) {
276                 $placeholder = call_user_func($placeholder, $class);
277             }
278         }
279
280         $proxyCode = strtr($this->proxyClassTemplate, $placeholders);
281
282         if ( ! $fileName) {
283             $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class);
284
285             if ( ! class_exists($proxyClassName)) {
286                 eval(substr($proxyCode, 5));
287             }
288
289             return;
290         }
291
292         $parentDirectory = dirname($fileName);
293
294         if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
295             throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
296         }
297
298         if ( ! is_writable($parentDirectory)) {
299             throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
300         }
301
302         $tmpFileName = $fileName . '.' . uniqid('', true);
303
304         file_put_contents($tmpFileName, $proxyCode);
305         @chmod($tmpFileName, 0664);
306         rename($tmpFileName, $fileName);
307     }
308
309     /**
310      * Generates the proxy short class name to be used in the template.
311      *
312      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
313      *
314      * @return string
315      */
316     private function generateProxyShortClassName(ClassMetadata $class)
317     {
318         $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
319         $parts          = explode('\\', strrev($proxyClassName), 2);
320
321         return strrev($parts[0]);
322     }
323
324     /**
325      * Generates the proxy namespace.
326      *
327      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
328      *
329      * @return string
330      */
331     private function generateNamespace(ClassMetadata $class)
332     {
333         $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
334         $parts = explode('\\', strrev($proxyClassName), 2);
335
336         return strrev($parts[1]);
337     }
338
339     /**
340      * Generates the original class name.
341      *
342      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
343      *
344      * @return string
345      */
346     private function generateClassName(ClassMetadata $class)
347     {
348         return ltrim($class->getName(), '\\');
349     }
350
351     /**
352      * Generates the array representation of lazy loaded public properties and their default values.
353      *
354      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
355      *
356      * @return string
357      */
358     private function generateLazyPropertiesDefaults(ClassMetadata $class)
359     {
360         $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
361         $values               = [];
362
363         foreach ($lazyPublicProperties as $key => $value) {
364             $values[] = var_export($key, true) . ' => ' . var_export($value, true);
365         }
366
367         return implode(', ', $values);
368     }
369
370     /**
371      * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
372      *
373      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
374      *
375      * @return string
376      */
377     private function generateConstructorImpl(ClassMetadata $class)
378     {
379         $constructorImpl = <<<'EOT'
380     /**
381      * @param \Closure $initializer
382      * @param \Closure $cloner
383      */
384     public function __construct($initializer = null, $cloner = null)
385     {
386
387 EOT;
388         $toUnset = [];
389
390         foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
391             $toUnset[] = '$this->' . $lazyPublicProperty;
392         }
393
394         $constructorImpl .= (empty($toUnset) ? '' : '        unset(' . implode(', ', $toUnset) . ");\n")
395             . <<<'EOT'
396
397         $this->__initializer__ = $initializer;
398         $this->__cloner__      = $cloner;
399     }
400 EOT;
401
402         return $constructorImpl;
403     }
404
405     /**
406      * Generates the magic getter invoked when lazy loaded public properties are requested.
407      *
408      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
409      *
410      * @return string
411      */
412     private function generateMagicGet(ClassMetadata $class)
413     {
414         $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
415         $reflectionClass      = $class->getReflectionClass();
416         $hasParentGet         = false;
417         $returnReference      = '';
418         $inheritDoc           = '';
419
420         if ($reflectionClass->hasMethod('__get')) {
421             $hasParentGet = true;
422             $inheritDoc   = '{@inheritDoc}';
423
424             if ($reflectionClass->getMethod('__get')->returnsReference()) {
425                 $returnReference = '& ';
426             }
427         }
428
429         if (empty($lazyPublicProperties) && ! $hasParentGet) {
430             return '';
431         }
432
433         $magicGet = <<<EOT
434     /**
435      * $inheritDoc
436      * @param string \$name
437      */
438     public function {$returnReference}__get(\$name)
439     {
440
441 EOT;
442
443         if ( ! empty($lazyPublicProperties)) {
444             $magicGet .= <<<'EOT'
445         if (array_key_exists($name, $this->__getLazyProperties())) {
446             $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
447
448             return $this->$name;
449         }
450
451
452 EOT;
453         }
454
455         if ($hasParentGet) {
456             $magicGet .= <<<'EOT'
457         $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
458
459         return parent::__get($name);
460
461 EOT;
462         } else {
463             $magicGet .= <<<'EOT'
464         trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
465
466 EOT;
467         }
468
469         $magicGet .= "    }";
470
471         return $magicGet;
472     }
473
474     /**
475      * Generates the magic setter (currently unused).
476      *
477      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
478      *
479      * @return string
480      */
481     private function generateMagicSet(ClassMetadata $class)
482     {
483         $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
484         $hasParentSet         = $class->getReflectionClass()->hasMethod('__set');
485
486         if (empty($lazyPublicProperties) && ! $hasParentSet) {
487             return '';
488         }
489
490         $inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
491         $magicSet   = <<<EOT
492     /**
493      * $inheritDoc
494      * @param string \$name
495      * @param mixed  \$value
496      */
497     public function __set(\$name, \$value)
498     {
499
500 EOT;
501
502         if ( ! empty($lazyPublicProperties)) {
503             $magicSet .= <<<'EOT'
504         if (array_key_exists($name, $this->__getLazyProperties())) {
505             $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
506
507             $this->$name = $value;
508
509             return;
510         }
511
512
513 EOT;
514         }
515
516         if ($hasParentSet) {
517             $magicSet .= <<<'EOT'
518         $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
519
520         return parent::__set($name, $value);
521 EOT;
522         } else {
523             $magicSet .= "        \$this->\$name = \$value;";
524         }
525
526         $magicSet .= "\n    }";
527
528         return $magicSet;
529     }
530
531     /**
532      * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
533      *
534      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
535      *
536      * @return string
537      */
538     private function generateMagicIsset(ClassMetadata $class)
539     {
540         $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
541         $hasParentIsset       = $class->getReflectionClass()->hasMethod('__isset');
542
543         if (empty($lazyPublicProperties) && ! $hasParentIsset) {
544             return '';
545         }
546
547         $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
548         $magicIsset = <<<EOT
549     /**
550      * $inheritDoc
551      * @param  string \$name
552      * @return boolean
553      */
554     public function __isset(\$name)
555     {
556
557 EOT;
558
559         if ( ! empty($lazyPublicProperties)) {
560             $magicIsset .= <<<'EOT'
561         if (array_key_exists($name, $this->__getLazyProperties())) {
562             $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
563
564             return isset($this->$name);
565         }
566
567
568 EOT;
569         }
570
571         if ($hasParentIsset) {
572             $magicIsset .= <<<'EOT'
573         $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
574
575         return parent::__isset($name);
576
577 EOT;
578         } else {
579             $magicIsset .= "        return false;";
580         }
581
582         return $magicIsset . "\n    }";
583     }
584
585     /**
586      * Generates implementation for the `__sleep` method of proxies.
587      *
588      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
589      *
590      * @return string
591      */
592     private function generateSleepImpl(ClassMetadata $class)
593     {
594         $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep');
595         $inheritDoc     = $hasParentSleep ? '{@inheritDoc}' : '';
596         $sleepImpl      = <<<EOT
597     /**
598      * $inheritDoc
599      * @return array
600      */
601     public function __sleep()
602     {
603
604 EOT;
605
606         if ($hasParentSleep) {
607             return $sleepImpl . <<<'EOT'
608         $properties = array_merge(['__isInitialized__'], parent::__sleep());
609
610         if ($this->__isInitialized__) {
611             $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
612         }
613
614         return $properties;
615     }
616 EOT;
617         }
618
619         $allProperties = ['__isInitialized__'];
620
621         /* @var $prop \ReflectionProperty */
622         foreach ($class->getReflectionClass()->getProperties() as $prop) {
623             if ($prop->isStatic()) {
624                 continue;
625             }
626
627             $allProperties[] = $prop->isPrivate()
628                 ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName()
629                 : $prop->getName();
630         }
631
632         $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
633         $protectedProperties  = array_diff($allProperties, $lazyPublicProperties);
634
635         foreach ($allProperties as &$property) {
636             $property = var_export($property, true);
637         }
638
639         foreach ($protectedProperties as &$property) {
640             $property = var_export($property, true);
641         }
642
643         $allProperties       = implode(', ', $allProperties);
644         $protectedProperties = implode(', ', $protectedProperties);
645
646         return $sleepImpl . <<<EOT
647         if (\$this->__isInitialized__) {
648             return [$allProperties];
649         }
650
651         return [$protectedProperties];
652     }
653 EOT;
654     }
655
656     /**
657      * Generates implementation for the `__wakeup` method of proxies.
658      *
659      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
660      *
661      * @return string
662      */
663     private function generateWakeupImpl(ClassMetadata $class)
664     {
665         $unsetPublicProperties = [];
666         $hasWakeup             = $class->getReflectionClass()->hasMethod('__wakeup');
667
668         foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
669             $unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
670         }
671
672         $shortName  = $this->generateProxyShortClassName($class);
673         $inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
674         $wakeupImpl = <<<EOT
675     /**
676      * $inheritDoc
677      */
678     public function __wakeup()
679     {
680         if ( ! \$this->__isInitialized__) {
681             \$this->__initializer__ = function ($shortName \$proxy) {
682                 \$proxy->__setInitializer(null);
683                 \$proxy->__setCloner(null);
684
685                 \$existingProperties = get_object_vars(\$proxy);
686
687                 foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
688                     if ( ! array_key_exists(\$property, \$existingProperties)) {
689                         \$proxy->\$property = \$defaultValue;
690                     }
691                 }
692             };
693
694 EOT;
695
696         if ( ! empty($unsetPublicProperties)) {
697             $wakeupImpl .= "\n            unset(" . implode(', ', $unsetPublicProperties) . ");";
698         }
699
700         $wakeupImpl .= "\n        }";
701
702         if ($hasWakeup) {
703             $wakeupImpl .= "\n        parent::__wakeup();";
704         }
705
706         $wakeupImpl .= "\n    }";
707
708         return $wakeupImpl;
709     }
710
711     /**
712      * Generates implementation for the `__clone` method of proxies.
713      *
714      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
715      *
716      * @return string
717      */
718     private function generateCloneImpl(ClassMetadata $class)
719     {
720         $hasParentClone  = $class->getReflectionClass()->hasMethod('__clone');
721         $inheritDoc      = $hasParentClone ? '{@inheritDoc}' : '';
722         $callParentClone = $hasParentClone ? "\n        parent::__clone();\n" : '';
723
724         return <<<EOT
725     /**
726      * $inheritDoc
727      */
728     public function __clone()
729     {
730         \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []);
731 $callParentClone    }
732 EOT;
733     }
734
735     /**
736      * Generates decorated methods by picking those available in the parent class.
737      *
738      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
739      *
740      * @return string
741      */
742     private function generateMethods(ClassMetadata $class)
743     {
744         $methods           = '';
745         $methodNames       = [];
746         $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
747         $skippedMethods    = [
748             '__sleep'   => true,
749             '__clone'   => true,
750             '__wakeup'  => true,
751             '__get'     => true,
752             '__set'     => true,
753             '__isset'   => true,
754         ];
755
756         foreach ($reflectionMethods as $method) {
757             $name = $method->getName();
758
759             if (
760                 $method->isConstructor() ||
761                 isset($skippedMethods[strtolower($name)]) ||
762                 isset($methodNames[$name]) ||
763                 $method->isFinal() ||
764                 $method->isStatic() ||
765                 ( ! $method->isPublic())
766             ) {
767                 continue;
768             }
769
770             $methodNames[$name] = true;
771             $methods .= "\n    /**\n"
772                 . "     * {@inheritDoc}\n"
773                 . "     */\n"
774                 . '    public function ';
775
776             if ($method->returnsReference()) {
777                 $methods .= '&';
778             }
779
780             $methods .= $name . '(' . $this->buildParametersString($class, $method, $method->getParameters()) . ')';
781             $methods .= $this->getMethodReturnType($method);
782             $methods .= "\n" . '    {' . "\n";
783
784             if ($this->isShortIdentifierGetter($method, $class)) {
785                 $identifier = lcfirst(substr($name, 3));
786                 $fieldType  = $class->getTypeOfField($identifier);
787                 $cast       = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : '';
788
789                 $methods .= '        if ($this->__isInitialized__ === false) {' . "\n";
790                 $methods .= '            ';
791                 $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : '';
792                 $methods .= $cast . ' parent::' . $method->getName() . "();\n";
793                 $methods .= '        }' . "\n\n";
794             }
795
796             $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters()));
797             $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters()));
798
799             $methods .= "\n        \$this->__initializer__ "
800                 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true)
801                 . ", [" . $invokeParamsString . "]);"
802                 . "\n\n        "
803                 . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '')
804                 . "parent::" . $name . '(' . $callParamsString . ');'
805                 . "\n" . '    }' . "\n";
806         }
807
808         return $methods;
809     }
810
811     /**
812      * Generates the Proxy file name.
813      *
814      * @param string $className
815      * @param string $baseDirectory Optional base directory for proxy file name generation.
816      *                              If not specified, the directory configured on the Configuration of the
817      *                              EntityManager will be used by this factory.
818      *
819      * @return string
820      */
821     public function getProxyFileName($className, $baseDirectory = null)
822     {
823         $baseDirectory = $baseDirectory ?: $this->proxyDirectory;
824
825         return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER
826             . str_replace('\\', '', $className) . '.php';
827     }
828
829     /**
830      * Checks if the method is a short identifier getter.
831      *
832      * What does this mean? For proxy objects the identifier is already known,
833      * however accessing the getter for this identifier usually triggers the
834      * lazy loading, leading to a query that may not be necessary if only the
835      * ID is interesting for the userland code (for example in views that
836      * generate links to the entity, but do not display anything else).
837      *
838      * @param \ReflectionMethod                                  $method
839      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
840      *
841      * @return boolean
842      */
843     private function isShortIdentifierGetter($method, ClassMetadata $class)
844     {
845         $identifier = lcfirst(substr($method->getName(), 3));
846         $startLine = $method->getStartLine();
847         $endLine = $method->getEndLine();
848         $cheapCheck = (
849             $method->getNumberOfParameters() == 0
850             && substr($method->getName(), 0, 3) == 'get'
851             && in_array($identifier, $class->getIdentifier(), true)
852             && $class->hasField($identifier)
853             && (($endLine - $startLine) <= 4)
854         );
855
856         if ($cheapCheck) {
857             $code = file($method->getDeclaringClass()->getFileName());
858             $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
859
860             $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
861
862             if (preg_match($pattern, $code)) {
863                 return true;
864             }
865         }
866
867         return false;
868     }
869
870     /**
871      * Generates the list of public properties to be lazy loaded, with their default values.
872      *
873      * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
874      *
875      * @return mixed[]
876      */
877     private function getLazyLoadedPublicProperties(ClassMetadata $class)
878     {
879         $defaultProperties = $class->getReflectionClass()->getDefaultProperties();
880         $properties = [];
881
882         foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
883             $name = $property->getName();
884
885             if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) {
886                 $properties[$name] = $defaultProperties[$name];
887             }
888         }
889
890         return $properties;
891     }
892
893     /**
894      * @param ClassMetadata          $class
895      * @param \ReflectionMethod      $method
896      * @param \ReflectionParameter[] $parameters
897      *
898      * @return string
899      */
900     private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters)
901     {
902         $parameterDefinitions = [];
903
904         /* @var $param \ReflectionParameter */
905         foreach ($parameters as $param) {
906             $parameterDefinition = '';
907
908             if ($parameterType = $this->getParameterType($class, $method, $param)) {
909                 $parameterDefinition .= $parameterType . ' ';
910             }
911
912             if ($param->isPassedByReference()) {
913                 $parameterDefinition .= '&';
914             }
915
916             if (method_exists($param, 'isVariadic') && $param->isVariadic()) {
917                 $parameterDefinition .= '...';
918             }
919
920             $parameters[]     = '$' . $param->getName();
921             $parameterDefinition .= '$' . $param->getName();
922
923             if ($param->isDefaultValueAvailable()) {
924                 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
925             }
926
927             $parameterDefinitions[] = $parameterDefinition;
928         }
929
930         return implode(', ', $parameterDefinitions);
931     }
932
933     /**
934      * @param ClassMetadata $class
935      * @param \ReflectionMethod $method
936      * @param \ReflectionParameter $parameter
937      *
938      * @return string|null
939      */
940     private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter)
941     {
942         if (method_exists($parameter, 'hasType')) {
943             if ( ! $parameter->hasType()) {
944                 return '';
945             }
946
947             return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter);
948         }
949
950         // For PHP 5.x, we need to pick the type hint in the old way (to be removed for PHP 7.0+)
951         if ($parameter->isArray()) {
952             return 'array';
953         }
954
955         if ($parameter->isCallable()) {
956             return 'callable';
957         }
958
959         try {
960             $parameterClass = $parameter->getClass();
961
962             if ($parameterClass) {
963                 return '\\' . $parameterClass->getName();
964             }
965         } catch (\ReflectionException $previous) {
966             throw UnexpectedValueException::invalidParameterTypeHint(
967                 $class->getName(),
968                 $method->getName(),
969                 $parameter->getName(),
970                 $previous
971             );
972         }
973
974         return null;
975     }
976
977     /**
978      * @param \ReflectionParameter[] $parameters
979      *
980      * @return string[]
981      */
982     private function getParameterNamesForInvoke(array $parameters)
983     {
984         return array_map(
985             function (\ReflectionParameter $parameter) {
986                 return '$' . $parameter->getName();
987             },
988             $parameters
989         );
990     }
991
992     /**
993      * @param \ReflectionParameter[] $parameters
994      *
995      * @return string[]
996      */
997     private function getParameterNamesForParentCall(array $parameters)
998     {
999         return array_map(
1000             function (\ReflectionParameter $parameter) {
1001                 $name = '';
1002
1003                 if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) {
1004                     $name .= '...';
1005                 }
1006
1007                 $name .= '$' . $parameter->getName();
1008
1009                 return $name;
1010             },
1011             $parameters
1012         );
1013     }
1014
1015     /**
1016      * @Param \ReflectionMethod $method
1017      *
1018      * @return string
1019      */
1020     private function getMethodReturnType(\ReflectionMethod $method)
1021     {
1022         if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
1023             return '';
1024         }
1025
1026         return ': ' . $this->formatType($method->getReturnType(), $method);
1027     }
1028
1029     /**
1030      * @param \ReflectionMethod $method
1031      *
1032      * @return bool
1033      */
1034     private function shouldProxiedMethodReturn(\ReflectionMethod $method)
1035     {
1036         if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
1037             return true;
1038         }
1039
1040         return 'void' !== strtolower($this->formatType($method->getReturnType(), $method));
1041     }
1042
1043     /**
1044      * @param \ReflectionType $type
1045      * @param \ReflectionMethod $method
1046      * @param \ReflectionParameter|null $parameter
1047      *
1048      * @return string
1049      */
1050     private function formatType(
1051         \ReflectionType $type,
1052         \ReflectionMethod $method,
1053         \ReflectionParameter $parameter = null
1054     ) {
1055         $name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
1056         $nameLower = strtolower($name);
1057
1058         if ('self' === $nameLower) {
1059             $name = $method->getDeclaringClass()->getName();
1060         }
1061
1062         if ('parent' === $nameLower) {
1063             $name = $method->getDeclaringClass()->getParentClass()->getName();
1064         }
1065
1066         if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
1067             if (null !== $parameter) {
1068                 throw UnexpectedValueException::invalidParameterTypeHint(
1069                     $method->getDeclaringClass()->getName(),
1070                     $method->getName(),
1071                     $parameter->getName()
1072                 );
1073             }
1074
1075             throw UnexpectedValueException::invalidReturnTypeHint(
1076                 $method->getDeclaringClass()->getName(),
1077                 $method->getName()
1078             );
1079         }
1080
1081         if ( ! $type->isBuiltin()) {
1082             $name = '\\' . $name;
1083         }
1084
1085         if ($type->allowsNull()
1086             && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
1087         ) {
1088             $name = '?' . $name;
1089         }
1090
1091         return $name;
1092     }
1093 }