1<?php 2 3declare(strict_types=1); 4 5namespace DI\Definition\Resolver; 6 7use DI\Definition\Definition; 8use DI\Definition\Exception\InvalidDefinition; 9use DI\Definition\ObjectDefinition\MethodInjection; 10use ReflectionMethod; 11use ReflectionParameter; 12 13/** 14 * Resolves parameters for a function call. 15 * 16 * @since 4.2 17 * @author Matthieu Napoli <matthieu@mnapoli.fr> 18 */ 19class ParameterResolver 20{ 21 /** 22 * @var DefinitionResolver 23 */ 24 private $definitionResolver; 25 26 /** 27 * @param DefinitionResolver $definitionResolver Will be used to resolve nested definitions. 28 */ 29 public function __construct(DefinitionResolver $definitionResolver) 30 { 31 $this->definitionResolver = $definitionResolver; 32 } 33 34 /** 35 * @throws InvalidDefinition A parameter has no value defined or guessable. 36 * @return array Parameters to use to call the function. 37 */ 38 public function resolveParameters( 39 MethodInjection $definition = null, 40 ReflectionMethod $method = null, 41 array $parameters = [] 42 ) { 43 $args = []; 44 45 if (! $method) { 46 return $args; 47 } 48 49 $definitionParameters = $definition ? $definition->getParameters() : []; 50 51 foreach ($method->getParameters() as $index => $parameter) { 52 if (array_key_exists($parameter->getName(), $parameters)) { 53 // Look in the $parameters array 54 $value = &$parameters[$parameter->getName()]; 55 } elseif (array_key_exists($index, $definitionParameters)) { 56 // Look in the definition 57 $value = &$definitionParameters[$index]; 58 } else { 59 // If the parameter is optional and wasn't specified, we take its default value 60 if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) { 61 $args[] = $this->getParameterDefaultValue($parameter, $method); 62 continue; 63 } 64 65 throw new InvalidDefinition(sprintf( 66 'Parameter $%s of %s has no value defined or guessable', 67 $parameter->getName(), 68 $this->getFunctionName($method) 69 )); 70 } 71 72 // Nested definitions 73 if ($value instanceof Definition) { 74 // If the container cannot produce the entry, we can use the default parameter value 75 if ($parameter->isOptional() && ! $this->definitionResolver->isResolvable($value)) { 76 $value = $this->getParameterDefaultValue($parameter, $method); 77 } else { 78 $value = $this->definitionResolver->resolve($value); 79 } 80 } 81 82 $args[] = &$value; 83 } 84 85 return $args; 86 } 87 88 /** 89 * Returns the default value of a function parameter. 90 * 91 * @throws InvalidDefinition Can't get default values from PHP internal classes and functions 92 * @return mixed 93 */ 94 private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function) 95 { 96 try { 97 return $parameter->getDefaultValue(); 98 } catch (\ReflectionException $e) { 99 throw new InvalidDefinition(sprintf( 100 'The parameter "%s" of %s has no type defined or guessable. It has a default value, ' 101 . 'but the default value can\'t be read through Reflection because it is a PHP internal class.', 102 $parameter->getName(), 103 $this->getFunctionName($function) 104 )); 105 } 106 } 107 108 private function getFunctionName(ReflectionMethod $method) : string 109 { 110 return $method->getName() . '()'; 111 } 112} 113