Mercurial > hg > cmmr2012-drupal-site
comparison vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | 5311817fb629 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
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*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 .= ' return ' . $cast . ' parent::' . $method->getName() . "();\n"; | |
791 $methods .= ' }' . "\n\n"; | |
792 } | |
793 | |
794 $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); | |
795 $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); | |
796 | |
797 $methods .= "\n \$this->__initializer__ " | |
798 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) | |
799 . ", [" . $invokeParamsString . "]);" | |
800 . "\n\n return parent::" . $name . '(' . $callParamsString . ');' | |
801 . "\n" . ' }' . "\n"; | |
802 } | |
803 | |
804 return $methods; | |
805 } | |
806 | |
807 /** | |
808 * Generates the Proxy file name. | |
809 * | |
810 * @param string $className | |
811 * @param string $baseDirectory Optional base directory for proxy file name generation. | |
812 * If not specified, the directory configured on the Configuration of the | |
813 * EntityManager will be used by this factory. | |
814 * | |
815 * @return string | |
816 */ | |
817 public function getProxyFileName($className, $baseDirectory = null) | |
818 { | |
819 $baseDirectory = $baseDirectory ?: $this->proxyDirectory; | |
820 | |
821 return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER | |
822 . str_replace('\\', '', $className) . '.php'; | |
823 } | |
824 | |
825 /** | |
826 * Checks if the method is a short identifier getter. | |
827 * | |
828 * What does this mean? For proxy objects the identifier is already known, | |
829 * however accessing the getter for this identifier usually triggers the | |
830 * lazy loading, leading to a query that may not be necessary if only the | |
831 * ID is interesting for the userland code (for example in views that | |
832 * generate links to the entity, but do not display anything else). | |
833 * | |
834 * @param \ReflectionMethod $method | |
835 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
836 * | |
837 * @return boolean | |
838 */ | |
839 private function isShortIdentifierGetter($method, ClassMetadata $class) | |
840 { | |
841 $identifier = lcfirst(substr($method->getName(), 3)); | |
842 $startLine = $method->getStartLine(); | |
843 $endLine = $method->getEndLine(); | |
844 $cheapCheck = ( | |
845 $method->getNumberOfParameters() == 0 | |
846 && substr($method->getName(), 0, 3) == 'get' | |
847 && in_array($identifier, $class->getIdentifier(), true) | |
848 && $class->hasField($identifier) | |
849 && (($endLine - $startLine) <= 4) | |
850 ); | |
851 | |
852 if ($cheapCheck) { | |
853 $code = file($method->getDeclaringClass()->getFileName()); | |
854 $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); | |
855 | |
856 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); | |
857 | |
858 if (preg_match($pattern, $code)) { | |
859 return true; | |
860 } | |
861 } | |
862 | |
863 return false; | |
864 } | |
865 | |
866 /** | |
867 * Generates the list of public properties to be lazy loaded, with their default values. | |
868 * | |
869 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
870 * | |
871 * @return mixed[] | |
872 */ | |
873 private function getLazyLoadedPublicProperties(ClassMetadata $class) | |
874 { | |
875 $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); | |
876 $properties = []; | |
877 | |
878 foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { | |
879 $name = $property->getName(); | |
880 | |
881 if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { | |
882 $properties[$name] = $defaultProperties[$name]; | |
883 } | |
884 } | |
885 | |
886 return $properties; | |
887 } | |
888 | |
889 /** | |
890 * @param ClassMetadata $class | |
891 * @param \ReflectionMethod $method | |
892 * @param \ReflectionParameter[] $parameters | |
893 * | |
894 * @return string | |
895 */ | |
896 private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters) | |
897 { | |
898 $parameterDefinitions = []; | |
899 | |
900 /* @var $param \ReflectionParameter */ | |
901 foreach ($parameters as $param) { | |
902 $parameterDefinition = ''; | |
903 | |
904 if ($parameterType = $this->getParameterType($class, $method, $param)) { | |
905 $parameterDefinition .= $parameterType . ' '; | |
906 } | |
907 | |
908 if ($param->isPassedByReference()) { | |
909 $parameterDefinition .= '&'; | |
910 } | |
911 | |
912 if (method_exists($param, 'isVariadic') && $param->isVariadic()) { | |
913 $parameterDefinition .= '...'; | |
914 } | |
915 | |
916 $parameters[] = '$' . $param->getName(); | |
917 $parameterDefinition .= '$' . $param->getName(); | |
918 | |
919 if ($param->isDefaultValueAvailable()) { | |
920 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); | |
921 } | |
922 | |
923 $parameterDefinitions[] = $parameterDefinition; | |
924 } | |
925 | |
926 return implode(', ', $parameterDefinitions); | |
927 } | |
928 | |
929 /** | |
930 * @param ClassMetadata $class | |
931 * @param \ReflectionMethod $method | |
932 * @param \ReflectionParameter $parameter | |
933 * | |
934 * @return string|null | |
935 */ | |
936 private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter) | |
937 { | |
938 | |
939 // We need to pick the type hint class too | |
940 if ($parameter->isArray()) { | |
941 return 'array'; | |
942 } | |
943 | |
944 if ($parameter->isCallable()) { | |
945 return 'callable'; | |
946 } | |
947 | |
948 if (method_exists($parameter, 'hasType') && $parameter->hasType() && $parameter->getType()->isBuiltin()) { | |
949 return (string) $parameter->getType(); | |
950 } | |
951 | |
952 try { | |
953 $parameterClass = $parameter->getClass(); | |
954 | |
955 if ($parameterClass) { | |
956 return '\\' . $parameterClass->getName(); | |
957 } | |
958 } catch (\ReflectionException $previous) { | |
959 throw UnexpectedValueException::invalidParameterTypeHint( | |
960 $class->getName(), | |
961 $method->getName(), | |
962 $parameter->getName(), | |
963 $previous | |
964 ); | |
965 } | |
966 | |
967 return null; | |
968 } | |
969 | |
970 /** | |
971 * @param \ReflectionParameter[] $parameters | |
972 * | |
973 * @return string[] | |
974 */ | |
975 private function getParameterNamesForInvoke(array $parameters) | |
976 { | |
977 return array_map( | |
978 function (\ReflectionParameter $parameter) { | |
979 return '$' . $parameter->getName(); | |
980 }, | |
981 $parameters | |
982 ); | |
983 } | |
984 | |
985 /** | |
986 * @param \ReflectionParameter[] $parameters | |
987 * | |
988 * @return string[] | |
989 */ | |
990 private function getParameterNamesForParentCall(array $parameters) | |
991 { | |
992 return array_map( | |
993 function (\ReflectionParameter $parameter) { | |
994 $name = ''; | |
995 | |
996 if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) { | |
997 $name .= '...'; | |
998 } | |
999 | |
1000 $name .= '$' . $parameter->getName(); | |
1001 | |
1002 return $name; | |
1003 }, | |
1004 $parameters | |
1005 ); | |
1006 } | |
1007 | |
1008 /** | |
1009 * @Param \ReflectionMethod $method | |
1010 * | |
1011 * @return string | |
1012 */ | |
1013 private function getMethodReturnType(\ReflectionMethod $method) | |
1014 { | |
1015 if (! (method_exists($method, 'hasReturnType') && $method->hasReturnType())) { | |
1016 return ''; | |
1017 } | |
1018 | |
1019 $returnType = $method->getReturnType(); | |
1020 | |
1021 if ($returnType->isBuiltin()) { | |
1022 return ': ' . $returnType; | |
1023 } | |
1024 | |
1025 $nameLower = strtolower((string) $returnType); | |
1026 | |
1027 if ('self' === $nameLower) { | |
1028 return ': \\' . $method->getDeclaringClass()->getName(); | |
1029 } | |
1030 | |
1031 if ('parent' === $nameLower) { | |
1032 return ': \\' . $method->getDeclaringClass()->getParentClass()->getName(); | |
1033 } | |
1034 | |
1035 return ': \\' . (string) $returnType; | |
1036 } | |
1037 } |