#27 - Simple Dependency Injection in PHP

Date: 2018-09-22 12:00 - PHP

Simple class to create instances of classes dynamically with dependency injection.

<?php

class Services {
    private static $resolvers = array();
    private static $servicesInstances = array();
    private static $resolvingNow = array();

    public static function createInstance($class) {
        if (isset(self::$resolvers[$class]))
            return (self::$resolvers[$class])();
        return self::createInstanceClass($class);
    }

    public static function register($class, $instance) {
        if (is_callable($instance))
            self::$servicesInstances[$class] = $instance();
        else
            self::$servicesInstances[$class] = $instance;
    }

    private static function createInstanceClass($class) {
        $reflection_class = new ReflectionClass($class);
        $constructor = $reflection_class->getConstructor();
        if ($constructor != null) {
            $values = array_map(function(ReflectionParameter $parameter) use($class) {
                $type = $parameter->getType();
                if ($type == null) {
                    $name = $parameter->getName();
                    trigger_error("Cannot construct service '$class', because '$name' is of unknown type. Please implement custom resolver or specify a type.", E_USER_ERROR);
                }

                if ($type->isBuiltin()) {
                    $name = $parameter->getName();
                    trigger_error("Cannot construct service '$class', because '$name' is a built-in type. Please implement a custom resolver.", E_USER_ERROR);
                }

                if (!is_a($type, 'ReflectionNamedType')) {
                    $name = $parameter->getName();
                    trigger_error("Cannot construct service '$class', because '$name' is not of a named type. Please implement a custom resolver or update PHP version to 7.1 if your type is named.", E_USER_ERROR);
                }

                return self::resolve($type->getName());
            }, $constructor->getParameters());
            return new $class(...$values);
        } else
            return new $class();
    }

    private static function createInstanceService($class) {
        if (isset(self::$resolvers[$class]))
            return (self::$resolvers[$class])();
        $instance = self::createInstanceClass($class);
        return self::$servicesInstances[$class] = $instance;
    }

    public static function resolve($class) {
        if (in_array($class, self::$resolvingNow))
            trigger_error('Cannot handle cyclic dependency injection. Cycle: ' . join(' -> ', array_merge(self::$resolvingNow, [$class])), E_USER_ERROR);

        array_push(self::$resolvingNow, $class);

        if (isset(self::$servicesInstances[$class]))
            return self::$servicesInstances[$class];

        if (isset(self::$resolvers[$class]))
            return (self::$resolvers[$class])();

        if (strpos($class, __SERVICES_NAMESPACE__ . '\\') !== 0)
            trigger_error("Cannot resolve '$class' automatically. Please make sure it is a service in the '" .  __SERVICES_NAMESPACE__ . "' namespace, or implement a custom resolver.", E_USER_ERROR);

        $service_name = str_replace('\\', '/', substr($class, strlen(__SERVICES_NAMESPACE__ . '\\'))) . '.php';
        $service = __SERVICES_DIR__ . "/$service_name";
        if (!file_exists($service))
            trigger_error("File '$service' does not exist. Cannot include service automatically.", E_USER_ERROR);
        include_once $service;
        if (!class_exists($class))
            trigger_error("Service '$class' was not declared in the '$service' file.", E_USER_ERROR);

        $instance = self::createInstanceService($class);

        array_pop(self::$resolvingNow);
        return $instance;
    }
}

Previous snippet | Next snippet