Exemplo n.º 1
0
Arquivo: Di.php Projeto: necrogami/zf2
    /**
     * 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
    }
Exemplo 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
     */
    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;
    }