Example #1
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;
    }