0

I'm writing my own implementation of the Laravel Service Container to practice some design patterns and later make a private microframework.

The class looks like this right now:

class Container implements ContainerInterface
{
    /**
     * Concrete bindings of contracts.
     *
     * @var array
     */
    protected $bindings = [];

    /**
     * Lists of arguments used for a class instantiation.
     *
     * @var array
     */
    protected $arguments = [];

    /**
     * Container's storage used to store already built or customly setted objects.
     *
     * @var array
     */
    protected $storage = [];


    /**
     * Returns an instance of a service
     *
     * @param $name
     * @return object
     * @throws \ReflectionException
     */
    public function get($name) {
        $className = (isset($this->bindings[$name])) ? $this->bindings[$name] : $name;

        if (isset($this->storage[$className])) {
            return $this->storage[$className];
        }

        return $this->make($className);
    }

    /**
     * Creates an instance of a class
     *
     * @param $className
     * @return object
     * @throws \ReflectionException
     */
    public function make($className) {
        $refObject = new \ReflectionClass($className);
        if (!$refObject->isInstantiable()) {
            throw new \ReflectionException("$className is not instantiable");
        }

        $refConstructor = $refObject->getConstructor();
        $refParameters = ($refConstructor) ? $refConstructor->getParameters() : [];

        $args = [];
        // Iterates over constructor arguments, checks for custom defined parameters 
        // and builds $args array
        foreach ($refParameters as $refParameter) {
            $refClass = $refParameter->getClass();
            $parameterName = $refParameter->name;
            $parameterValue =
                isset($this->arguments[$className][$parameterName]) ? $this->arguments[$className][$parameterName]
                    : (null !== $refClass ? $refClass->name
                        : ($refParameter->isOptional() ? $refParameter->getDefaultValue()
                            : null));

            // Recursively gets needed objects for a class instantiation
            $args[] = ($refClass) ? $this->get($parameterValue)
                                  : $parameterValue;
        }

        $instance = $refObject->newInstanceArgs($args);

        $this->storage[$className] = $instance;

        return $instance;
    }

    /**
     * Sets a concrete implementation of a contract
     *
     * @param $abstract
     * @param $concrete
     */
    public function bind($abstract, $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    /**
     * Sets arguments used for a class instantiation
     *
     * @param $className
     * @param array $arguments
     */
    public function setArguments($className, array $arguments) {
        $this->arguments[$className] = $arguments;
    }
}

It works fine but I clearly see a violation of SRP in the make() method. So I decided to delegate an object creational logic to a separate class.

A problem that I encountered is that this class will be tightly coupled with a Container class. Because it needs an access to $bindings and $arguments arrays, and the get() method. And even if we pass these parameters to the class, the storage still stays in a container. So basically all architecture is wrong and we need, like, 2 more classes: StorageManager and ClassFactory. Or maybe ClassBuilder? And should ClassFactory be able to build constructor arguments or it needs another class — ArgumentFactory?

What do you think guys?

alain_morel
  • 43
  • 1
  • 7
  • Why do you say your `make` method violates the SRP? – Henrique Barcelos Jan 06 '16 at 00:32
  • In my opinion a container is just a bag of objects and it shouldn't be able to create them. But maybe I'm wrong. – alain_morel Jan 06 '16 at 00:36
  • Yes, you are correct! But I don't understand the purpose of this container. Is it a general container? – Henrique Barcelos Jan 06 '16 at 00:38
  • Basically it's implementation of the service locator pattern. It supposed to substitute a constructor dependency injection. For example, in a controller action we can get a service like `$this->container->get("Mailer")` instead of injecting it in a constructor. – alain_morel Jan 06 '16 at 00:48
  • I'm not a big fan of Service Locator. See this [link](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). As far as I understood, you want a "one factory suits it all" implementation. There is no point of doing so. You'd prefer using a Dependency Injection Container, like [Pimple](http://pimple.sensiolabs.org/). – Henrique Barcelos Jan 06 '16 at 01:52
  • Pimple doesn't support auto-wiring. Also it has no big differences with Service Locator. Yes, we may specify a scope, but also we may not. – alain_morel Jan 06 '16 at 02:25
  • Pimple is a simple one. If you need something more robust, you should try [Symfony DIC](http://symfony.com/doc/current/components/dependency_injection/introduction.html). And you are wrong, Service Locator and Dependency Injection Containers are [fundamentally different](http://stackoverflow.com/questions/1557781/whats-the-difference-between-the-dependency-injection-and-service-locator-patte). – Henrique Barcelos Jan 06 '16 at 12:29
  • Pay attention to [this answer](http://stackoverflow.com/a/1557804/1798341). When you cut off the instantiation from the Service Locator, it will be basically a DIC. So, if you are worried about violating SRP, you should not use a Service Locator. – Henrique Barcelos Jan 06 '16 at 12:31
  • Ok, I'll take your position into consideration. – alain_morel Jan 06 '16 at 18:56

0 Answers0