Ejemplo n.º 1
0
 /**
  * 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
 }
Ejemplo n.º 2
0
 /**
  * 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
 }