<?php 
namespace Injector; 
 
use Pimple\Container; 
use Pimple\ServiceProviderInterface; 
 
/** 
 * Dependency injection class 
 * @author emaphp 
 * @package Injector 
 */ 
class Injector { 
    /** 
     * Providers array 
     * @var array 
     */ 
    protected static $providers = array(); 
     
    /** 
     * Obtains a provider class instance 
     * @param string $classname 
     * @throws \InvalidArgumentException 
     * @return ServiceProviderInterface 
     */ 
    public static function getProvider($classname) { 
        if (array_key_exists($classname, self::$providers)) { 
            return self::$providers[$classname]; 
        } 
         
        $provider = new $classname; 
         
        if (!($provider instanceof ServiceProviderInterface)) { 
            throw new \InvalidArgumentException("$classname is not a valid ServiceProviderInterface instance"); 
        } 
 
        self::$providers[$classname] = $provider; 
        return self::$providers[$classname]; 
    } 
     
    /** 
     * Creates a new instance of $classname with the specified arguments 
     * @param string $classname 
     * @param Container $container 
     * @param string $args 
     * @param array $filter 
     * @param array $override 
     * @throws \InvalidArgumentException 
     * @throws \RuntimeException 
     * @return object 
     */ 
    public static function createWith($classname, Container $container, $args = null, $filter = null, $override = null) { 
        if (!is_string($classname) || empty($classname)) { 
            throw new \InvalidArgumentException("Argument is not a valid class name"); 
        } 
         
        //obtain provider list for this class 
        $profile = Profiler::getClassProfile($classname); 
                 
        if (isset($profile->constructor)) { 
            //build constructor parameter list 
            $parameters = $profile->constructor->getParameters(); 
             
            if (is_null($args)) { 
                $args = []; 
            } 
            else { 
                $args = is_array($args) ? $args : [$args]; 
            } 
                 
            foreach ($parameters as $param) { 
                if (!empty($args)) { 
                    $params[] = array_shift($args); 
                } 
                elseif (is_array($override) && array_key_exists($param->getName(), $profile->constructorParams) && array_key_exists($profile->constructorParams[$param->getName()], $override)) { 
                    $params[] = $override[$profile->constructorParams[$param->getName()]]; 
                } 
                elseif (array_key_exists($param->getName(), $profile->constructorParams)) { 
                    //get parameter id 
                    $parameterId = $profile->constructorParams[$param->getName()]; 
                     
                    if (is_array($filter) && !in_array($parameterId, $filter)) { 
                        if ($param->isOptional()) { 
                            $params[] = $param->getDefaultValue(); 
                        } 
                        elseif (!$profile->isStrict) { 
                            $params[] = null; 
                        } 
                        else { 
                            throw new \RuntimeException(sprintf("Argument %s in class '%s' constructor is associated to a filtered service '%s'", $param->getName(), $classname, $parameterId)); 
                        } 
                    } 
                    elseif ($container->offsetExists($parameterId)) { 
                        //add service to constructor arguments 
                        $params[] = $container->offsetGet($parameterId); 
                    } 
                    elseif ($param->isOptional()) { 
                        $params[] = $param->getDefaultValue(); 
                    } 
                    elseif (!$profile->isStrict) { 
                        $params[] = null; 
                    } 
                    else { 
                        throw new \RuntimeException(sprintf("Argument %s in class '%s' constructor is associated to a unknown service '%s'", $param->getName(), $classname, $parameterId)); 
                    } 
                } 
                elseif ($param->isOptional()) { 
                    $params[] = $param->getDefaultValue(); 
                } 
                else { 
                    throw new \RuntimeException("Not enough arguments provided for '$classname' constructor"); 
                } 
            } 
             
            $instance = $profile->class->newInstanceArgs($params); 
        } 
        else { 
            $instance = new $classname; 
        } 
         
        self::inject($instance, $container, $filter, $override); 
        return $instance; 
    } 
     
    /** 
     * Creates a new instance of $classname from the associated providers 
     * @param string $classname 
     * @param array $args 
     * @param array $filter 
     * @param array $override 
     * @throws \RuntimeException 
     * @return object 
     */ 
    public static function create($classname, $args = null, $filter = null, $override = null) { 
        //obtain provider list for this class 
        $profile = Profiler::getClassProfile($classname); 
        $providers = $profile->providers; 
         
        if (empty($providers)) { 
            throw new \RuntimeException("Class $classname does not have any provider associated with it"); 
        } 
 
        //create new container and register all providers 
        $container = new Container(); 
         
        foreach ($providers as $provider) { 
            $providerInstance = self::getProvider($provider); 
            $providerInstance->register($container); 
        } 
 
        return self::createWith($classname, $container, $args, $filter, $override); 
    } 
     
    /** 
     * Injects a set of dependencies into an instance 
     * @param object $instance 
     * @param Pimple\Container $container 
     * @param array $filter Which dependencies must be injected 
     * @param array $override An associative array that overrides a set of injected properties 
     * @throws \InvalidArgumentException 
     * @throws \RuntimeException 
     */ 
    public static function inject(&$instance, $container, $filter = null, $override = null) { 
        if (!is_object($instance)) { 
            throw new \InvalidArgumentException("Argument is not a valid object"); 
        } 
         
        if (!is_object($container)) { 
            throw new \InvalidArgumentException("Container is not a valid object"); 
        } 
        elseif (!($container instanceof Container)) { 
            throw new \InvalidArgumentException("Container is not a valid container"); 
        } 
         
        //check if objects is a stdClass instance 
        if ($instance instanceof \stdClass) { 
            $services = $container->keys(); 
                 
            if (empty($filter)) { 
                //inject all 
                for ($i = 0, $n = count($services); $i < $n; $i++) { 
                    if (is_array($override) && array_key_exists($services[$i], $override)) { 
                        continue; 
                    } 
                         
                    $instance->$services[$i] = $container->offsetGet($services[$i]); 
                } 
            } 
            else { 
                foreach ($filter as $service) { 
                    //check if service exists 
                    if (!in_array($service, $services)) { 
                        throw new \RuntimeException(sprintf("Service '$service' does not exists in container class '%s'", get_class($container))); 
                    } 
                         
                    if (is_array($override) && array_key_exists($service, $override)) { 
                        continue; 
                    } 
                         
                    $instance->$service = $container->offsetGet($service); 
                } 
            } 
                 
            //override values 
            if (is_array($override) && !empty($override)) { 
                foreach ($override as $service => $value) { 
                    if (is_int($service)) { 
                        continue; 
                    } 
                         
                    $instance->$service = $value; 
                } 
            } 
                 
            return; 
        } 
         
        //build class profile 
        $classname = get_class($instance); 
        $profile = Profiler::getClassProfile($classname); 
        $services = $container->keys(); 
         
        foreach ($profile->reflectionProperties as $name => $property) { 
            $serviceId = $profile->properties[$name]; 
         
            if (is_array($override) && array_key_exists($serviceId, $override)) { 
                if ($property->isStatic()) { 
                    $property->setValue(null, $override[$serviceId]); 
                } 
                else { 
                    $property->setValue($instance, $override[$serviceId]); 
                } 
            } 
            else { 
                if (is_array($filter) && !in_array($serviceId, $filter)) { 
                    continue; 
                } 
                 
                if (!$container->offsetExists($serviceId)) { 
                    if ($profile->isStrict) { 
                        throw new \RuntimeException("Property '$name' in class $classname is associated to a unknown service '$serviceId'"); 
                    } 
                     
                    continue; 
                } 
                else { 
                    $value = $container->offsetGet($serviceId); 
                     
                    if ($property->isStatic()) { 
                        $property->setValue(null, $value); 
                    } 
                    else { 
                        $property->setValue($instance, $value); 
                    } 
                }                 
            } 
        } 
    } 
}
 
 |