Search code examples
phpoopdependency-injectionserviceloader

Searching for an elegant way in PHP for loading dependencies/services/configuration?


I'm building a MVC PHP framework and I wonder which are the best practices to load what I need in my classes, be it other classes or plain configuration.

Till today I've used singletons, registry and lately a dependency injection container. While many people claim that DI is the way to go, it seems to me like it justs moves the problem of coupling between components into another place.

Singletons introduce global state, registry introduces tight coupling and DI introduces... well, lots of complexity. I am still confused and can't find a proper way to wire my classes each other.

In the meanwhile, I came up with a custom solution. Actually it's not a solution, it just abstracts the implementation of service loading from my code.

I built an abstract class with _load_service and _load_config methods which all the components of my framework extend in order to load other services or configuration.

abstract class Base_Component {
    function _load_service($service) {
        // could be either
        return DI_container::getInstance()->$service;

        // or
        $class = '\services\\'.$service;
        return new $class;

        // or other implementation
    }
}

The implementation of loading them is now implemented in only one place, the base class, so at least I got rid of code lines like the following into my components:

$database = new Database(Registry::getInstance()->load('db_config'));

or

$database = DI_container::getInstance()->database;

Now if want a database instance I do this

$database = $this->_load_service('database');

and the implementation of service loader, container, registry or whatever can be easily changed in a single class method without having to search through all my code to change calls to whatever container implementation I was using before.

But as I said I'm not even close to sure about what method I will use for loading classes and configuration.

What are your opinions?


Solution

  • Why reinvent the wheel? Use Pimple as your DI container, and learn how to use it from its documentation.

    Or, use Silex microframework as a base to create your own framework. It extends Pimple functionality, so you can use dependency injection.

    To answer your question, this is how you use a DI without coupling your classes to it:

    interface ContainerInterface {
        public function getService($service_name);
        public function registerService($service_name,Closure $service_definition);
    }
    
    class Application {
        public function __construct(ContainerInterface $container) {
            $this->container= $container;
        }
    
        public function run() {
            // very simple to use!
            $this->container->getService('db')->someDatabaseQuery();
        }
    }
    
    $c = new My_DI_Container;
    
    // Service definitions could be in a separate file
    $c->registerService('db',function() { return new Database('some config'); });
    
    // Then you inject your DI container into the objects that need it
    $app = new Application($c);
    $app->run(); // or whatever
    

    This way, the DI container is decoupled and in the future you could use a different implementation. The only requirement is that it implements the ContainerInterface.

    Note that the container object is being pushed, and not pulled. Avoid using singleton. To get/set single-instance objects, use the container (that's its responsibility). And to get the container instance, just push it through constructors.