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.
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>.
20 namespace Doctrine\Common\Proxy;
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;
28 * This factory is used to generate proxy classes.
29 * It builds proxies from given parameters, a template and class metadata.
31 * @author Marco Pivetta <ocramius@gmail.com>
37 * Used to match very simple id methods that don't need
38 * to be decorated since the identifier is known.
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';
43 * The namespace that contains all proxy classes.
47 private $proxyNamespace;
50 * The directory that contains all proxy classes.
54 private $proxyDirectory;
57 * Map of callables used to fill in placeholders set in the template.
59 * @var string[]|callable[]
61 protected $placeholders = [
62 'baseProxyInterface' => Proxy::class,
63 'additionalProperties' => '',
67 * Template used as a blueprint to generate proxies.
71 protected $proxyClassTemplate = '<?php
73 namespace <namespace>;
76 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR
78 class <proxyShortClassName> extends \<className> implements \<baseProxyInterface>
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.
85 * @see \Doctrine\Common\Persistence\Proxy::__setInitializer
87 public $__initializer__;
90 * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object
92 * @see \Doctrine\Common\Persistence\Proxy::__setCloner
97 * @var boolean flag indicating if this object was already initialized
99 * @see \Doctrine\Common\Persistence\Proxy::__isInitialized
101 public $__isInitialized__ = false;
104 * @var array properties to be lazy loaded, with keys being the property
105 * names and values being their default values
107 * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties
109 public static $lazyPropertiesDefaults = [<lazyPropertiesDefaults>];
111 <additionalProperties>
128 * Forces initialization of the proxy
130 public function __load()
132 $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []);
137 * @internal generated method: use only when explicitly handling proxy specific loading logic
139 public function __isInitialized()
141 return $this->__isInitialized__;
146 * @internal generated method: use only when explicitly handling proxy specific loading logic
148 public function __setInitialized($initialized)
150 $this->__isInitialized__ = $initialized;
155 * @internal generated method: use only when explicitly handling proxy specific loading logic
157 public function __setInitializer(\Closure $initializer = null)
159 $this->__initializer__ = $initializer;
164 * @internal generated method: use only when explicitly handling proxy specific loading logic
166 public function __getInitializer()
168 return $this->__initializer__;
173 * @internal generated method: use only when explicitly handling proxy specific loading logic
175 public function __setCloner(\Closure $cloner = null)
177 $this->__cloner__ = $cloner;
182 * @internal generated method: use only when explicitly handling proxy specific cloning logic
184 public function __getCloner()
186 return $this->__cloner__;
191 * @internal generated method: use only when explicitly handling proxy specific loading logic
194 public function __getLazyProperties()
196 return self::$lazyPropertiesDefaults;
204 * Initializes a new instance of the <tt>ProxyFactory</tt> class that is
205 * connected to the given <tt>EntityManager</tt>.
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.
210 * @throws InvalidArgumentException
212 public function __construct($proxyDirectory, $proxyNamespace)
214 if ( ! $proxyDirectory) {
215 throw InvalidArgumentException::proxyDirectoryRequired();
218 if ( ! $proxyNamespace) {
219 throw InvalidArgumentException::proxyNamespaceRequired();
222 $this->proxyDirectory = $proxyDirectory;
223 $this->proxyNamespace = $proxyNamespace;
227 * Sets a placeholder to be replaced in the template.
229 * @param string $name
230 * @param string|callable $placeholder
232 * @throws InvalidArgumentException
234 public function setPlaceholder($name, $placeholder)
236 if ( ! is_string($placeholder) && ! is_callable($placeholder)) {
237 throw InvalidArgumentException::invalidPlaceholder($name);
240 $this->placeholders[$name] = $placeholder;
244 * Sets the base template used to create proxy classes.
246 * @param string $proxyClassTemplate
248 public function setProxyClassTemplate($proxyClassTemplate)
250 $this->proxyClassTemplate = (string) $proxyClassTemplate;
254 * Generates a proxy class file.
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.
259 * @throws UnexpectedValueException
261 public function generateProxyClass(ClassMetadata $class, $fileName = false)
263 preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches);
265 $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]);
268 foreach ($placeholderMatches as $placeholder => $name) {
269 $placeholders[$placeholder] = isset($this->placeholders[$name])
270 ? $this->placeholders[$name]
271 : [$this, 'generate' . $name];
274 foreach ($placeholders as & $placeholder) {
275 if (is_callable($placeholder)) {
276 $placeholder = call_user_func($placeholder, $class);
280 $proxyCode = strtr($this->proxyClassTemplate, $placeholders);
283 $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class);
285 if ( ! class_exists($proxyClassName)) {
286 eval(substr($proxyCode, 5));
292 $parentDirectory = dirname($fileName);
294 if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) {
295 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
298 if ( ! is_writable($parentDirectory)) {
299 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory);
302 $tmpFileName = $fileName . '.' . uniqid('', true);
304 file_put_contents($tmpFileName, $proxyCode);
305 @chmod($tmpFileName, 0664);
306 rename($tmpFileName, $fileName);
310 * Generates the proxy short class name to be used in the template.
312 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
316 private function generateProxyShortClassName(ClassMetadata $class)
318 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
319 $parts = explode('\\', strrev($proxyClassName), 2);
321 return strrev($parts[0]);
325 * Generates the proxy namespace.
327 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
331 private function generateNamespace(ClassMetadata $class)
333 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace);
334 $parts = explode('\\', strrev($proxyClassName), 2);
336 return strrev($parts[1]);
340 * Generates the original class name.
342 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
346 private function generateClassName(ClassMetadata $class)
348 return ltrim($class->getName(), '\\');
352 * Generates the array representation of lazy loaded public properties and their default values.
354 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
358 private function generateLazyPropertiesDefaults(ClassMetadata $class)
360 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
363 foreach ($lazyPublicProperties as $key => $value) {
364 $values[] = var_export($key, true) . ' => ' . var_export($value, true);
367 return implode(', ', $values);
371 * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values).
373 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
377 private function generateConstructorImpl(ClassMetadata $class)
379 $constructorImpl = <<<'EOT'
381 * @param \Closure $initializer
382 * @param \Closure $cloner
384 public function __construct($initializer = null, $cloner = null)
390 foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) {
391 $toUnset[] = '$this->' . $lazyPublicProperty;
394 $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n")
397 $this->__initializer__ = $initializer;
398 $this->__cloner__ = $cloner;
402 return $constructorImpl;
406 * Generates the magic getter invoked when lazy loaded public properties are requested.
408 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
412 private function generateMagicGet(ClassMetadata $class)
414 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
415 $reflectionClass = $class->getReflectionClass();
416 $hasParentGet = false;
417 $returnReference = '';
420 if ($reflectionClass->hasMethod('__get')) {
421 $hasParentGet = true;
422 $inheritDoc = '{@inheritDoc}';
424 if ($reflectionClass->getMethod('__get')->returnsReference()) {
425 $returnReference = '& ';
429 if (empty($lazyPublicProperties) && ! $hasParentGet) {
436 * @param string \$name
438 public function {$returnReference}__get(\$name)
443 if ( ! empty($lazyPublicProperties)) {
444 $magicGet .= <<<'EOT'
445 if (array_key_exists($name, $this->__getLazyProperties())) {
446 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
456 $magicGet .= <<<'EOT'
457 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]);
459 return parent::__get($name);
463 $magicGet .= <<<'EOT'
464 trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE);
475 * Generates the magic setter (currently unused).
477 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
481 private function generateMagicSet(ClassMetadata $class)
483 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class);
484 $hasParentSet = $class->getReflectionClass()->hasMethod('__set');
486 if (empty($lazyPublicProperties) && ! $hasParentSet) {
490 $inheritDoc = $hasParentSet ? '{@inheritDoc}' : '';
494 * @param string \$name
495 * @param mixed \$value
497 public function __set(\$name, \$value)
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]);
507 $this->$name = $value;
517 $magicSet .= <<<'EOT'
518 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]);
520 return parent::__set($name, $value);
523 $magicSet .= " \$this->\$name = \$value;";
532 * Generates the magic issetter invoked when lazy loaded public properties are checked against isset().
534 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
538 private function generateMagicIsset(ClassMetadata $class)
540 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
541 $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset');
543 if (empty($lazyPublicProperties) && ! $hasParentIsset) {
547 $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : '';
551 * @param string \$name
554 public function __isset(\$name)
559 if ( ! empty($lazyPublicProperties)) {
560 $magicIsset .= <<<'EOT'
561 if (array_key_exists($name, $this->__getLazyProperties())) {
562 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
564 return isset($this->$name);
571 if ($hasParentIsset) {
572 $magicIsset .= <<<'EOT'
573 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]);
575 return parent::__isset($name);
579 $magicIsset .= " return false;";
582 return $magicIsset . "\n }";
586 * Generates implementation for the `__sleep` method of proxies.
588 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
592 private function generateSleepImpl(ClassMetadata $class)
594 $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep');
595 $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : '';
601 public function __sleep()
606 if ($hasParentSleep) {
607 return $sleepImpl . <<<'EOT'
608 $properties = array_merge(['__isInitialized__'], parent::__sleep());
610 if ($this->__isInitialized__) {
611 $properties = array_diff($properties, array_keys($this->__getLazyProperties()));
619 $allProperties = ['__isInitialized__'];
621 /* @var $prop \ReflectionProperty */
622 foreach ($class->getReflectionClass()->getProperties() as $prop) {
623 if ($prop->isStatic()) {
627 $allProperties[] = $prop->isPrivate()
628 ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName()
632 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class));
633 $protectedProperties = array_diff($allProperties, $lazyPublicProperties);
635 foreach ($allProperties as &$property) {
636 $property = var_export($property, true);
639 foreach ($protectedProperties as &$property) {
640 $property = var_export($property, true);
643 $allProperties = implode(', ', $allProperties);
644 $protectedProperties = implode(', ', $protectedProperties);
646 return $sleepImpl . <<<EOT
647 if (\$this->__isInitialized__) {
648 return [$allProperties];
651 return [$protectedProperties];
657 * Generates implementation for the `__wakeup` method of proxies.
659 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
663 private function generateWakeupImpl(ClassMetadata $class)
665 $unsetPublicProperties = [];
666 $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup');
668 foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) {
669 $unsetPublicProperties[] = '$this->' . $lazyPublicProperty;
672 $shortName = $this->generateProxyShortClassName($class);
673 $inheritDoc = $hasWakeup ? '{@inheritDoc}' : '';
678 public function __wakeup()
680 if ( ! \$this->__isInitialized__) {
681 \$this->__initializer__ = function ($shortName \$proxy) {
682 \$proxy->__setInitializer(null);
683 \$proxy->__setCloner(null);
685 \$existingProperties = get_object_vars(\$proxy);
687 foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) {
688 if ( ! array_key_exists(\$property, \$existingProperties)) {
689 \$proxy->\$property = \$defaultValue;
696 if ( ! empty($unsetPublicProperties)) {
697 $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");";
700 $wakeupImpl .= "\n }";
703 $wakeupImpl .= "\n parent::__wakeup();";
706 $wakeupImpl .= "\n }";
712 * Generates implementation for the `__clone` method of proxies.
714 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
718 private function generateCloneImpl(ClassMetadata $class)
720 $hasParentClone = $class->getReflectionClass()->hasMethod('__clone');
721 $inheritDoc = $hasParentClone ? '{@inheritDoc}' : '';
722 $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : '';
728 public function __clone()
730 \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []);
736 * Generates decorated methods by picking those available in the parent class.
738 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
742 private function generateMethods(ClassMetadata $class)
746 $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC);
756 foreach ($reflectionMethods as $method) {
757 $name = $method->getName();
760 $method->isConstructor() ||
761 isset($skippedMethods[strtolower($name)]) ||
762 isset($methodNames[$name]) ||
763 $method->isFinal() ||
764 $method->isStatic() ||
765 ( ! $method->isPublic())
770 $methodNames[$name] = true;
771 $methods .= "\n /**\n"
772 . " * {@inheritDoc}\n"
774 . ' public function ';
776 if ($method->returnsReference()) {
780 $methods .= $name . '(' . $this->buildParametersString($class, $method, $method->getParameters()) . ')';
781 $methods .= $this->getMethodReturnType($method);
782 $methods .= "\n" . ' {' . "\n";
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) ' : '';
789 $methods .= ' if ($this->__isInitialized__ === false) {' . "\n";
791 $methods .= $this->shouldProxiedMethodReturn($method) ? 'return ' : '';
792 $methods .= $cast . ' parent::' . $method->getName() . "();\n";
793 $methods .= ' }' . "\n\n";
796 $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters()));
797 $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters()));
799 $methods .= "\n \$this->__initializer__ "
800 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true)
801 . ", [" . $invokeParamsString . "]);"
803 . ($this->shouldProxiedMethodReturn($method) ? 'return ' : '')
804 . "parent::" . $name . '(' . $callParamsString . ');'
805 . "\n" . ' }' . "\n";
812 * Generates the Proxy file name.
814 * @param string $className
815 * @param string $baseDirectory Optional base directory for proxy file name generation.
816 * If not specified, the directory configured on the Configuration of the
817 * EntityManager will be used by this factory.
821 public function getProxyFileName($className, $baseDirectory = null)
823 $baseDirectory = $baseDirectory ?: $this->proxyDirectory;
825 return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER
826 . str_replace('\\', '', $className) . '.php';
830 * Checks if the method is a short identifier getter.
832 * What does this mean? For proxy objects the identifier is already known,
833 * however accessing the getter for this identifier usually triggers the
834 * lazy loading, leading to a query that may not be necessary if only the
835 * ID is interesting for the userland code (for example in views that
836 * generate links to the entity, but do not display anything else).
838 * @param \ReflectionMethod $method
839 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
843 private function isShortIdentifierGetter($method, ClassMetadata $class)
845 $identifier = lcfirst(substr($method->getName(), 3));
846 $startLine = $method->getStartLine();
847 $endLine = $method->getEndLine();
849 $method->getNumberOfParameters() == 0
850 && substr($method->getName(), 0, 3) == 'get'
851 && in_array($identifier, $class->getIdentifier(), true)
852 && $class->hasField($identifier)
853 && (($endLine - $startLine) <= 4)
857 $code = file($method->getDeclaringClass()->getFileName());
858 $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1)));
860 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier);
862 if (preg_match($pattern, $code)) {
871 * Generates the list of public properties to be lazy loaded, with their default values.
873 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class
877 private function getLazyLoadedPublicProperties(ClassMetadata $class)
879 $defaultProperties = $class->getReflectionClass()->getDefaultProperties();
882 foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
883 $name = $property->getName();
885 if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) {
886 $properties[$name] = $defaultProperties[$name];
894 * @param ClassMetadata $class
895 * @param \ReflectionMethod $method
896 * @param \ReflectionParameter[] $parameters
900 private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters)
902 $parameterDefinitions = [];
904 /* @var $param \ReflectionParameter */
905 foreach ($parameters as $param) {
906 $parameterDefinition = '';
908 if ($parameterType = $this->getParameterType($class, $method, $param)) {
909 $parameterDefinition .= $parameterType . ' ';
912 if ($param->isPassedByReference()) {
913 $parameterDefinition .= '&';
916 if (method_exists($param, 'isVariadic') && $param->isVariadic()) {
917 $parameterDefinition .= '...';
920 $parameters[] = '$' . $param->getName();
921 $parameterDefinition .= '$' . $param->getName();
923 if ($param->isDefaultValueAvailable()) {
924 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true);
927 $parameterDefinitions[] = $parameterDefinition;
930 return implode(', ', $parameterDefinitions);
934 * @param ClassMetadata $class
935 * @param \ReflectionMethod $method
936 * @param \ReflectionParameter $parameter
938 * @return string|null
940 private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter)
942 if (method_exists($parameter, 'hasType')) {
943 if ( ! $parameter->hasType()) {
947 return $this->formatType($parameter->getType(), $parameter->getDeclaringFunction(), $parameter);
950 // For PHP 5.x, we need to pick the type hint in the old way (to be removed for PHP 7.0+)
951 if ($parameter->isArray()) {
955 if ($parameter->isCallable()) {
960 $parameterClass = $parameter->getClass();
962 if ($parameterClass) {
963 return '\\' . $parameterClass->getName();
965 } catch (\ReflectionException $previous) {
966 throw UnexpectedValueException::invalidParameterTypeHint(
969 $parameter->getName(),
978 * @param \ReflectionParameter[] $parameters
982 private function getParameterNamesForInvoke(array $parameters)
985 function (\ReflectionParameter $parameter) {
986 return '$' . $parameter->getName();
993 * @param \ReflectionParameter[] $parameters
997 private function getParameterNamesForParentCall(array $parameters)
1000 function (\ReflectionParameter $parameter) {
1003 if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) {
1007 $name .= '$' . $parameter->getName();
1016 * @Param \ReflectionMethod $method
1020 private function getMethodReturnType(\ReflectionMethod $method)
1022 if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
1026 return ': ' . $this->formatType($method->getReturnType(), $method);
1030 * @param \ReflectionMethod $method
1034 private function shouldProxiedMethodReturn(\ReflectionMethod $method)
1036 if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
1040 return 'void' !== strtolower($this->formatType($method->getReturnType(), $method));
1044 * @param \ReflectionType $type
1045 * @param \ReflectionMethod $method
1046 * @param \ReflectionParameter|null $parameter
1050 private function formatType(
1051 \ReflectionType $type,
1052 \ReflectionMethod $method,
1053 \ReflectionParameter $parameter = null
1055 $name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
1056 $nameLower = strtolower($name);
1058 if ('self' === $nameLower) {
1059 $name = $method->getDeclaringClass()->getName();
1062 if ('parent' === $nameLower) {
1063 $name = $method->getDeclaringClass()->getParentClass()->getName();
1066 if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
1067 if (null !== $parameter) {
1068 throw UnexpectedValueException::invalidParameterTypeHint(
1069 $method->getDeclaringClass()->getName(),
1071 $parameter->getName()
1075 throw UnexpectedValueException::invalidReturnTypeHint(
1076 $method->getDeclaringClass()->getName(),
1081 if ( ! $type->isBuiltin()) {
1082 $name = '\\' . $name;
1085 if ($type->allowsNull()
1086 && (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
1088 $name = '?' . $name;