/** * Resolve parameters referencing other services * * @param string $class * @param string $method * @param array $callTimeUserParams * @param bool $isInstantiator * @param string $alias * @return array */ protected function resolveMethodParameters($class, $method, array $callTimeUserParams, $alias, $methodIsRequired, $isInstantiator = false) { // parameters for this method, in proper order, to be returned $resolvedParams = array(); // parameter requirements from the definition $injectionMethodParameters = $this->definitions->getMethodParameters($class, $method); // computed parameters array $computedParams = array( 'value' => array(), 'required' => array(), 'optional' => array() ); // retrieve instance configurations for all contexts $iConfig = array(); $aliases = $this->instanceManager->getAliases(); // for the alias in the dependency tree if ($alias && $this->instanceManager->hasConfiguration($alias)) { $iConfig['thisAlias'] = $this->instanceManager->getConfiguration($alias); } // for the current class in the dependency tree if ($this->instanceManager->hasConfiguration($class)) { $iConfig['thisClass'] = $this->instanceManager->getConfiguration($class); } // for the parent class, provided we are deeper than one node if (isset($this->instanceContext[0])) { list($requestedClass, $requestedAlias) = ($this->instanceContext[0][0] == 'NEW') ? array($this->instanceContext[0][1], $this->instanceContext[0][2]) : array($this->instanceContext[1][1], $this->instanceContext[1][2]); } else { $requestedClass = $requestedAlias = null; } if ($requestedClass != $class && $this->instanceManager->hasConfiguration($requestedClass)) { $iConfig['requestedClass'] = $this->instanceManager->getConfiguration($requestedClass); if ($requestedAlias) { $iConfig['requestedAlias'] = $this->instanceManager->getConfiguration($requestedAlias); } } // This is a 2 pass system for resolving parameters // first pass will find the sources, the second pass will order them and resolve lookups if they exist // MOST methods will only have a single parameters to resolve, so this should be fast foreach ($injectionMethodParameters as $fqParamPos => $info) { list($name, $type, $isRequired) = $info; $fqParamName = substr_replace($fqParamPos, ':' . $info[0], strrpos($fqParamPos, ':')); // PRIORITY 1 - consult user provided parameters if (isset($callTimeUserParams[$fqParamPos]) || isset($callTimeUserParams[$name])) { if (isset($callTimeUserParams[$fqParamPos])) { $callTimeCurValue =& $callTimeUserParams[$fqParamPos]; } elseif (isset($callTimeUserParams[$fqParamName])) { $callTimeCurValue =& $callTimeUserParams[$fqParamName]; } else { $callTimeCurValue =& $callTimeUserParams[$name]; } if (is_string($callTimeCurValue)) { if ($this->instanceManager->hasAlias($callTimeCurValue)) { // was an alias provided? $computedParams['required'][$fqParamPos] = array( $callTimeUserParams[$name], $this->instanceManager->getClassFromAlias($callTimeCurValue) ); } elseif ($this->definitions->hasClass($callTimeUserParams[$name])) { // was a known class provided? $computedParams['required'][$fqParamPos] = array( $callTimeCurValue, $callTimeCurValue ); } else { // must be a value $computedParams['value'][$fqParamPos] = $callTimeCurValue; } } else { // int, float, null, object, etc $computedParams['value'][$fqParamPos] = $callTimeCurValue; } unset($callTimeCurValue); continue; } // PRIORITY 2 -specific instance configuration (thisAlias) - this alias // PRIORITY 3 -THEN specific instance configuration (thisClass) - this class // PRIORITY 4 -THEN specific instance configuration (requestedAlias) - requested alias // PRIORITY 5 -THEN specific instance configuration (requestedClass) - requested class foreach (array('thisAlias', 'thisClass', 'requestedAlias', 'requestedClass') as $thisIndex) { // check the provided parameters config if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos]) || isset($iConfig[$thisIndex]['parameters'][$fqParamName]) || isset($iConfig[$thisIndex]['parameters'][$name])) { if (isset($iConfig[$thisIndex]['parameters'][$fqParamPos])) { $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamPos]; } elseif (isset($iConfig[$thisIndex]['parameters'][$fqParamName])) { $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$fqParamName]; } else { $iConfigCurValue =& $iConfig[$thisIndex]['parameters'][$name]; } if (is_string($iConfigCurValue) && $type === false) { $computedParams['value'][$fqParamPos] = $iConfigCurValue; } elseif (is_string($iConfigCurValue) && isset($aliases[$iConfigCurValue])) { $computedParams['required'][$fqParamPos] = array( $iConfig[$thisIndex]['parameters'][$name], $this->instanceManager->getClassFromAlias($iConfigCurValue) ); } elseif (is_string($iConfigCurValue) && $this->definitions->hasClass($iConfigCurValue)) { $computedParams['required'][$fqParamPos] = array( $iConfigCurValue, $iConfigCurValue ); } elseif (is_object($iConfigCurValue) && $iConfigCurValue instanceof \Closure && $type !== 'Closure') { $computedParams['value'][$fqParamPos] = $iConfigCurValue(); } else { $computedParams['value'][$fqParamPos] = $iConfigCurValue; } unset($iConfigCurValue); continue 2; } } // PRIORITY 6 - globally preferred implementations // next consult alias level preferred instances if ($alias && $this->instanceManager->hasTypePreferences($alias)) { $pInstances = $this->instanceManager->getTypePreferences($alias); foreach ($pInstances as $pInstance) { if (is_object($pInstance)) { $computedParams['value'][$fqParamPos] = $pInstance; continue 2; } $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; if ($pInstanceClass === $type || $this->isSubclassOf($pInstanceClass, $type)) { $computedParams['required'][$fqParamPos] = array($pInstance, $pInstanceClass); continue 2; } } } // next consult class level preferred instances if ($type && $this->instanceManager->hasTypePreferences($type)) { $pInstances = $this->instanceManager->getTypePreferences($type); foreach ($pInstances as $pInstance) { if (is_object($pInstance)) { $computedParams['value'][$fqParamPos] = $pInstance; continue 2; } $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; if ($pInstanceClass === $type || $this->isSubclassOf($pInstanceClass, $type)) { $computedParams['required'][$fqParamPos] = array($pInstance, $pInstanceClass); continue 2; } } } if (!$isRequired) { $computedParams['optional'][$fqParamPos] = true; } if ($type && $isRequired && $methodIsRequired) { $computedParams['required'][$fqParamPos] = array($type, $type); } } $index = 0; foreach ($injectionMethodParameters as $fqParamPos => $value) { $name = $value[0]; if (isset($computedParams['value'][$fqParamPos])) { // if there is a value supplied, use it $resolvedParams[$index] = $computedParams['value'][$fqParamPos]; } elseif (isset($computedParams['required'][$fqParamPos])) { // detect circular dependencies! (they can only happen in instantiators) if ($isInstantiator && in_array($computedParams['required'][$fqParamPos][1], $this->currentDependencies)) { throw new Exception\CircularDependencyException( "Circular dependency detected: $class depends on {$value[1]} and viceversa" ); } array_push($this->currentDependencies, $class); $dConfig = $this->instanceManager->getConfiguration($computedParams['required'][$fqParamPos][0]); if ($dConfig['shared'] === false) { $resolvedParams[$index] = $this->newInstance($computedParams['required'][$fqParamPos][0], $callTimeUserParams, false); } else { $resolvedParams[$index] = $this->get($computedParams['required'][$fqParamPos][0], $callTimeUserParams); } array_pop($this->currentDependencies); } elseif (!array_key_exists($fqParamPos, $computedParams['optional'])) { if ($methodIsRequired) { // if this item was not marked as optional, // plus it cannot be resolve, and no value exist, bail out throw new Exception\MissingPropertyException(sprintf( 'Missing %s for parameter ' . $name . ' for ' . $class . '::' . $method, (($value[0] === null) ? 'value' : 'instance/object' ) )); } else { return false; } } else { $resolvedParams[$index] = null; } $index++; } return $resolvedParams; // return ordered list of parameters }
/** * Resolve parameters referencing other services * * @param string $class * @param string $method * @param array $callTimeUserParams * @param bool $isInstantiator * @param string $alias * @return array */ protected function resolveMethodParameters($class, $method, array $callTimeUserParams, $isInstantiator, $alias) { /* @var $isSubclassFunc Closure */ static $isSubclassFunc = null; static $isSubclassFuncCache = null; // null as unset, array when set $isSubclassFunc = function($class, $type) use (&$isSubclassFuncCache) { /* @see https://bugs.php.net/bug.php?id=53727 */ if ($isSubclassFuncCache === null) { $isSubclassFuncCache = array(); } if (!array_key_exists($class, $isSubclassFuncCache)) { $isSubclassFuncCache[$class] = class_parents($class, true) + class_implements($class, true); } return (isset($isSubclassFuncCache[$class][$type])); }; // parameters for this method, in proper order, to be returned $resolvedParams = array(); // parameter requirements from the definition $injectionMethodParameters = $this->definition->getInjectionMethodParameters($class, $method); // computed parameters array $computedParams = array( 'value' => array(), 'lookup' => array(), 'optional' => array() ); // retrieve instance configurations for all contexts $iConfig = array(); $aliases = $this->instanceManager->getAliases(); // for the alias in the dependency tree if ($alias && $this->instanceManager->hasConfiguration($alias)) { $iConfig['thisAlias'] = $this->instanceManager->getConfiguration($alias); } // for the current class in the dependency tree if ($this->instanceManager->hasConfiguration($class)) { $iConfig['thisClass'] = $this->instanceManager->getConfiguration($class); } // for the parent class, provided we are deeper than one node list($requestedClass, $requestedAlias) = ($this->instanceContext[0][0] == 'NEW') ? array($this->instanceContext[0][1], $this->instanceContext[0][2]) : array($this->instanceContext[1][1], $this->instanceContext[1][2]); if ($requestedClass != $class && $this->instanceManager->hasConfiguration($requestedClass)) { $iConfig['requestedClass'] = $this->instanceManager->getConfiguration($requestedClass); if ($requestedAlias) { $iConfig['requestedAlias'] = $this->instanceManager->getConfiguration($requestedAlias); } } // This is a 2 pass system for resolving parameters // first pass will find the sources, the second pass will order them and resolve lookups if they exist // MOST methods will only have a single parameters to resolve, so this should be fast foreach ($injectionMethodParameters as $name => $info) { list($type, $isOptional, $isTypeInstantiable) = $info; // PRIORITY 1 - consult user provided parameters if (isset($callTimeUserParams[$name])) { if (is_string($callTimeUserParams[$name])) { if ($this->instanceManager->hasAlias($callTimeUserParams[$name])) { // was an alias provided? $computedParams['lookup'][$name] = array( $callTimeUserParams[$name], $this->instanceManager->getClassFromAlias($callTimeUserParams[$name]) ); } elseif ($this->definition->hasClass($callTimeUserParams[$name])) { // was a known class provided? $computedParams['lookup'][$name] = array( $callTimeUserParams[$name], $callTimeUserParams[$name] ); } else { // must be a value $computedParams['value'][$name] = $callTimeUserParams[$name]; } } else { // int, float, null, object, etc $computedParams['value'][$name] = $callTimeUserParams[$name]; } continue; } // PRIORITY 2 -specific instance configuration (thisAlias) - this alias // PRIORITY 3 -THEN specific instance configuration (thisClass) - this class // PRIORITY 4 -THEN specific instance configuration (requestedAlias) - requested alias // PRIORITY 5 -THEN specific instance configuration (requestedClass) - requested class foreach (array('thisAlias', 'thisClass', 'requestedAlias', 'requestedClass') as $thisIndex) { // check the provided parameters config if (isset($iConfig[$thisIndex]['parameters'][$name])) { if (is_string($iConfig[$thisIndex]['parameters'][$name]) && isset($aliases[$iConfig[$thisIndex]['parameters'][$name]])) { $computedParams['lookup'][$name] = array( $iConfig[$thisIndex]['parameters'][$name], $this->instanceManager->getClassFromAlias($iConfig[$thisIndex]['parameters'][$name]) ); } elseif (is_string($iConfig[$thisIndex]['parameters'][$name]) && $this->definition->hasClass($iConfig[$thisIndex]['parameters'][$name])) { $computedParams['lookup'][$name] = array( $iConfig[$thisIndex]['parameters'][$name], $iConfig[$thisIndex]['parameters'][$name] ); } else { $computedParams['value'][$name] = $iConfig[$thisIndex]['parameters'][$name]; } continue 2; } // check the provided method config if (isset($iConfig[$thisIndex]['methods'][$method][$name])) { if (is_string(is_string($iConfig[$thisIndex]['methods'][$method][$name])) && isset($aliases[$iConfig[$thisIndex]['methods'][$method][$name]])) { $computedParams['lookup'][$name] = array( $iConfig[$thisIndex]['methods'][$method][$name], $this->instanceManager->getClassFromAlias($iConfig[$thisIndex]['methods'][$method][$name]) ); } elseif (is_string(is_string($iConfig[$thisIndex]['methods'][$method][$name])) && $this->definition->hasClass($iConfig[$thisIndex]['methods'][$method][$name])) { $computedParams['lookup'][$name] = array( $iConfig[$thisIndex]['methods'][$method][$name], $iConfig[$thisIndex]['methods'][$method][$name] ); } else { $computedParams['value'][$name] = $iConfig[$thisIndex]['methods'][$method][$name]; } continue 2; } } // PRIORITY 6 - globally preferred implementations // next consult alias level preferred instances if ($alias && $this->instanceManager->hasTypePreferences($alias)) { $pInstances = $this->instanceManager->getTypePreferences($alias); foreach ($pInstances as $pInstance) { $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; if ($pInstanceClass === $type || $isSubclassFunc($pInstanceClass, $type)) { $computedParams['lookup'][$name] = array($pInstance, $pInstanceClass); continue 2; } } } // next consult class level preferred instances if ($type && $this->instanceManager->hasTypePreferences($type)) { $pInstances = $this->instanceManager->getTypePreferences($type); foreach ($pInstances as $pInstance) { $pInstanceClass = ($this->instanceManager->hasAlias($pInstance)) ? $this->instanceManager->getClassFromAlias($pInstance) : $pInstance; if ($pInstanceClass === $type || $isSubclassFunc($pInstanceClass, $type)) { $computedParams['lookup'][$name] = array($pInstance, $pInstanceClass); continue 2; } } } if ($isOptional) { $computedParams['optional'][$name] = true; } if ($type && $isTypeInstantiable === true && !$isOptional) { $computedParams['lookup'][$name] = array($type, $type); } } $index = 0; foreach ($injectionMethodParameters as $name => $value) { if (isset($computedParams['value'][$name])) { $resolvedParams[$index] = $computedParams['value'][$name]; } elseif (isset($computedParams['lookup'][$name])) { if ($isInstantiator && in_array($computedParams['lookup'][$name][1], $this->currentDependencies)) { throw new Exception\CircularDependencyException( "Circular dependency detected: $class depends on {$value[0]} and viceversa" ); } array_push($this->currentDependencies, $class); $resolvedParams[$index] = $this->get($computedParams['lookup'][$name][0], $callTimeUserParams); array_pop($this->currentDependencies); } elseif (!array_key_exists($name, $computedParams['optional'])) { throw new Exception\MissingPropertyException( 'Missing parameter named ' . $name . ' for ' . $class . '::' . $method ); } else { $resolvedParams[$index] = null; } $index++; } return $resolvedParams; }