annotate vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @ 19:fa3358dc1485 tip

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