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
20namespace Doctrine\Common\Proxy;
21
22use Doctrine\Common\Persistence\Mapping\ClassMetadata;
23use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
24use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
25use 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 */
34class 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*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 = array(
62        'baseProxyInterface'   => 'Doctrine\Common\Proxy\Proxy',
63        'additionalProperties' => '',
64    );
65
66    /**
67     * Template used as a blueprint to generate proxies.
68     *
69     * @var string
70     */
71    protected $proxyClassTemplate = '<?php
72
73namespace <namespace>;
74
75/**
76 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
77 */
78class <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 = array(<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\', array());
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       = array();
267
268        foreach ($placeholderMatches as $placeholder => $name) {
269            $placeholders[$placeholder] = isset($this->placeholders[$name])
270                ? $this->placeholders[$name]
271                : array($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               = array();
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
387EOT;
388        $toUnset = array();
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    }
400EOT;
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
441EOT;
442
443        if ( ! empty($lazyPublicProperties)) {
444            $magicGet .= <<<'EOT'
445        if (array_key_exists($name, $this->__getLazyProperties())) {
446            $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
447
448            return $this->$name;
449        }
450
451
452EOT;
453        }
454
455        if ($hasParentGet) {
456            $magicGet .= <<<'EOT'
457        $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name));
458
459        return parent::__get($name);
460
461EOT;
462        } else {
463            $magicGet .= <<<'EOT'
464        trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
465
466EOT;
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
500EOT;
501
502        if ( ! empty($lazyPublicProperties)) {
503            $magicSet .= <<<'EOT'
504        if (array_key_exists($name, $this->__getLazyProperties())) {
505            $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
506
507            $this->$name = $value;
508
509            return;
510        }
511
512
513EOT;
514        }
515
516        if ($hasParentSet) {
517            $magicSet .= <<<'EOT'
518        $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value));
519
520        return parent::__set($name, $value);
521EOT;
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
557EOT;
558
559        if ( ! empty($lazyPublicProperties)) {
560            $magicIsset .= <<<'EOT'
561        if (array_key_exists($name, $this->__getLazyProperties())) {
562            $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
563
564            return isset($this->$name);
565        }
566
567
568EOT;
569        }
570
571        if ($hasParentIsset) {
572            $magicIsset .= <<<'EOT'
573        $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name));
574
575        return parent::__isset($name);
576
577EOT;
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
604EOT;
605
606        if ($hasParentSleep) {
607            return $sleepImpl . <<<'EOT'
608        $properties = array_merge(array('__isInitialized__'), parent::__sleep());
609
610        if ($this->__isInitialized__) {
611            $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
612        }
613
614        return $properties;
615    }
616EOT;
617        }
618
619        $allProperties = array('__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 array($allProperties);
649        }
650
651        return array($protectedProperties);
652    }
653EOT;
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 = array();
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
694EOT;
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', array());
731$callParentClone    }
732EOT;
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       = array();
746        $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
747        $skippedMethods    = array(
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 .= "\n" . '    {' . "\n";
782
783            if ($this->isShortIdentifierGetter($method, $class)) {
784                $identifier = lcfirst(substr($name, 3));
785                $fieldType  = $class->getTypeOfField($identifier);
786                $cast       = in_array($fieldType, array('integer', 'smallint')) ? '(int) ' : '';
787
788                $methods .= '        if ($this->__isInitialized__ === false) {' . "\n";
789                $methods .= '            return ' . $cast . ' parent::' . $method->getName() . "();\n";
790                $methods .= '        }' . "\n\n";
791            }
792
793            $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters()));
794            $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters()));
795
796            $methods .= "\n        \$this->__initializer__ "
797                . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true)
798                . ", array(" . $invokeParamsString . "));"
799                . "\n\n        return parent::" . $name . '(' . $callParamsString . ');'
800                . "\n" . '    }' . "\n";
801        }
802
803        return $methods;
804    }
805
806    /**
807     * Generates the Proxy file name.
808     *
809     * @param string $className
810     * @param string $baseDirectory Optional base directory for proxy file name generation.
811     *                              If not specified, the directory configured on the Configuration of the
812     *                              EntityManager will be used by this factory.
813     *
814     * @return string
815     */
816    public function getProxyFileName($className, $baseDirectory = null)
817    {
818        $baseDirectory = $baseDirectory ?: $this->proxyDirectory;
819
820        return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER
821            . str_replace('\\', '', $className) . '.php';
822    }
823
824    /**
825     * Checks if the method is a short identifier getter.
826     *
827     * What does this mean? For proxy objects the identifier is already known,
828     * however accessing the getter for this identifier usually triggers the
829     * lazy loading, leading to a query that may not be necessary if only the
830     * ID is interesting for the userland code (for example in views that
831     * generate links to the entity, but do not display anything else).
832     *
833     * @param \ReflectionMethod                                  $method
834     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
835     *
836     * @return boolean
837     */
838    private function isShortIdentifierGetter($method, ClassMetadata $class)
839    {
840        $identifier = lcfirst(substr($method->getName(), 3));
841        $startLine = $method->getStartLine();
842        $endLine = $method->getEndLine();
843        $cheapCheck = (
844            $method->getNumberOfParameters() == 0
845            && substr($method->getName(), 0, 3) == 'get'
846            && in_array($identifier, $class->getIdentifier(), true)
847            && $class->hasField($identifier)
848            && (($endLine - $startLine) <= 4)
849        );
850
851        if ($cheapCheck) {
852            $code = file($method->getDeclaringClass()->getFileName());
853            $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
854
855            $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
856
857            if (preg_match($pattern, $code)) {
858                return true;
859            }
860        }
861
862        return false;
863    }
864
865    /**
866     * Generates the list of public properties to be lazy loaded, with their default values.
867     *
868     * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
869     *
870     * @return mixed[]
871     */
872    private function getLazyLoadedPublicProperties(ClassMetadata $class)
873    {
874        $defaultProperties = $class->getReflectionClass()->getDefaultProperties();
875        $properties = array();
876
877        foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
878            $name = $property->getName();
879
880            if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) {
881                $properties[$name] = $defaultProperties[$name];
882            }
883        }
884
885        return $properties;
886    }
887
888    /**
889     * @param ClassMetadata          $class
890     * @param \ReflectionMethod      $method
891     * @param \ReflectionParameter[] $parameters
892     *
893     * @return string
894     */
895    private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters)
896    {
897        $parameterDefinitions = array();
898
899        /* @var $param \ReflectionParameter */
900        foreach ($parameters as $param) {
901            $parameterDefinition = '';
902
903            if ($parameterType = $this->getParameterType($class, $method, $param)) {
904                $parameterDefinition .= $parameterType . ' ';
905            }
906
907            if ($param->isPassedByReference()) {
908                $parameterDefinition .= '&';
909            }
910
911            if (method_exists($param, 'isVariadic')) {
912                if ($param->isVariadic()) {
913                    $parameterDefinition .= '...';
914                }
915            }
916
917            $parameters[]     = '$' . $param->getName();
918            $parameterDefinition .= '$' . $param->getName();
919
920            if ($param->isDefaultValueAvailable()) {
921                $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
922            }
923
924            $parameterDefinitions[] = $parameterDefinition;
925        }
926
927        return implode(', ', $parameterDefinitions);
928    }
929
930    /**
931     * @param ClassMetadata $class
932     * @param \ReflectionMethod $method
933     * @param \ReflectionParameter $parameter
934     *
935     * @return string|null
936     */
937    private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter)
938    {
939
940        // We need to pick the type hint class too
941        if ($parameter->isArray()) {
942            return 'array';
943        }
944
945        if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) {
946            return 'callable';
947        }
948
949        try {
950            $parameterClass = $parameter->getClass();
951
952            if ($parameterClass) {
953                return '\\' . $parameterClass->getName();
954            }
955        } catch (\ReflectionException $previous) {
956            throw UnexpectedValueException::invalidParameterTypeHint(
957                $class->getName(),
958                $method->getName(),
959                $parameter->getName(),
960                $previous
961            );
962        }
963
964        return null;
965    }
966
967    /**
968     * @param \ReflectionParameter[] $parameters
969     *
970     * @return string[]
971     */
972    private function getParameterNamesForInvoke(array $parameters)
973    {
974        return array_map(
975            function (\ReflectionParameter $parameter) {
976                return '$' . $parameter->getName();
977            },
978            $parameters
979        );
980    }
981
982    /**
983     * @param \ReflectionParameter[] $parameters
984     *
985     * @return string[]
986     */
987    private function getParameterNamesForParentCall(array $parameters)
988    {
989        return array_map(
990            function (\ReflectionParameter $parameter) {
991                $name = '';
992
993                if (method_exists($parameter, 'isVariadic')) {
994                    if ($parameter->isVariadic()) {
995                        $name .= '...';
996                    }
997                }
998
999                $name .= '$' . $parameter->getName();
1000
1001                return $name;
1002            },
1003            $parameters
1004        );
1005    }
1006}
1007