/** * Resolve parameters referencing other services * * @param string $class * @param string $method * @param array $callTimeUserParams * @param string $alias * @param int|bool $methodRequirementType * @param bool $isInstantiator * @throws Exception\MissingPropertyException * @throws Exception\CircularDependencyException * @return array */ protected function resolveMethodParameters($class, $method, array $callTimeUserParams, $alias, $methodRequirementType, $isInstantiator = false) { //for BC if (is_bool($methodRequirementType)) { if ($isInstantiator) { $methodRequirementType = Di::METHOD_IS_INSTANTIATOR; } elseif ($methodRequirementType) { $methodRequirementType = Di::METHOD_IS_REQUIRED; } else { $methodRequirementType = Di::METHOD_IS_OPTIONAL; } } // 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(), 'retrieval' => 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->hasConfig($alias)) { $iConfig['thisAlias'] = $this->instanceManager->getConfig($alias); } // for the current class in the dependency tree if ($this->instanceManager->hasConfig($class)) { $iConfig['thisClass'] = $this->instanceManager->getConfig($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->hasConfig($requestedClass)) { $iConfig['requestedClass'] = $this->instanceManager->getConfig($requestedClass); if (array_key_exists('parameters', $iConfig['requestedClass'])) { $newParameters = array(); foreach ($iConfig['requestedClass']['parameters'] as $name => $parameter) { $newParameters[$requestedClass . '::' . $method . '::' . $name] = $parameter; } $iConfig['requestedClass']['parameters'] = $newParameters; } if ($requestedAlias) { $iConfig['requestedAlias'] = $this->instanceManager->getConfig($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['retrieval'][$fqParamPos] = array($callTimeUserParams[$name], $this->instanceManager->getClassFromAlias($callTimeCurValue)); } elseif ($this->definitions->hasClass($callTimeUserParams[$name])) { // was a known class provided? $computedParams['retrieval'][$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['retrieval'][$fqParamPos] = array($iConfig[$thisIndex]['parameters'][$name], $this->instanceManager->getClassFromAlias($iConfigCurValue)); } elseif (is_string($iConfigCurValue) && $this->definitions->hasClass($iConfigCurValue)) { $computedParams['retrieval'][$fqParamPos] = array($iConfigCurValue, $iConfigCurValue); } elseif (is_object($iConfigCurValue) && $iConfigCurValue instanceof Closure && $type !== 'Closure') { /* @var $iConfigCurValue 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 // RESOLVE_EAGER wants to inject the cross-cutting concerns. // If you want to retrieve an instance from TypePreferences, // use AwareInterface or specify the method requirement option METHOD_IS_EAGER at ClassDefinition if ($methodRequirementType & self::RESOLVE_EAGER) { 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['retrieval'][$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['retrieval'][$fqParamPos] = array($pInstance, $pInstanceClass); continue 2; } } } } if (!$isRequired) { $computedParams['optional'][$fqParamPos] = true; } if ($type && $isRequired && $methodRequirementType & self::RESOLVE_EAGER) { $computedParams['retrieval'][$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['retrieval'][$fqParamPos])) { // detect circular dependencies! (they can only happen in instantiators) if ($isInstantiator && in_array($computedParams['retrieval'][$fqParamPos][1], $this->currentDependencies) && (!isset($alias) || in_array($computedParams['retrieval'][$fqParamPos][0], $this->currentAliasDependenencies))) { $msg = "Circular dependency detected: {$class} depends on {$value[1]} and viceversa"; if (isset($alias)) { $msg .= " (Aliased as {$alias})"; } throw new Exception\CircularDependencyException($msg); } array_push($this->currentDependencies, $class); if (isset($alias)) { array_push($this->currentAliasDependenencies, $alias); } $dConfig = $this->instanceManager->getConfig($computedParams['retrieval'][$fqParamPos][0]); try { if ($dConfig['shared'] === false) { $resolvedParams[$index] = $this->newInstance($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams, false); } else { $resolvedParams[$index] = $this->get($computedParams['retrieval'][$fqParamPos][0], $callTimeUserParams); } } catch (DiRuntimeException $e) { if ($methodRequirementType & self::RESOLVE_STRICT) { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } // if this item was marked strict, // 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'), $e->getCode(), $e); } else { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } return false; } } catch (ServiceManagerException $e) { // Zend\ServiceManager\Exception\ServiceNotCreatedException if ($methodRequirementType & self::RESOLVE_STRICT) { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } // if this item was marked strict, // 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'), $e->getCode(), $e); } else { //finally ( be aware to do at the end of flow) array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } return false; } } array_pop($this->currentDependencies); if (isset($alias)) { array_pop($this->currentAliasDependenencies); } } elseif (!array_key_exists($fqParamPos, $computedParams['optional'])) { if ($methodRequirementType & self::RESOLVE_STRICT) { // 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] = $value[3]; } $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 * @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 }