/** * @group AliasAlias */ public function testInstanceManagerResolvesRecursiveAliasesForConfiguration() { $config = array('parameters' => array('username' => 'my-username')); $im = new InstanceManager(); $im->addAlias('bar-alias', 'Some\\Class'); $im->addAlias('foo-alias', 'bar-alias'); $im->setConfiguration('bar-alias', $config); $config['injections'] = array(); $this->assertEquals($config, $im->getConfiguration('foo-alias')); }
/** * Resolve parameters referencing other services * * @param string $class * @param string $method * @param array $callTimeUserParams * @param bool $isInstantiator * @param string $alias * @return array * @throws Exception\CircularDependencyException */ 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 ($type !== false && 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 ($type === false && is_string($iConfigCurValue)) { $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 || self::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 || self::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 }