简体   繁体   中英

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?

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.

Answer to your question; Look at PHP autoloading . Registering classes via autoloading makes it so you don't have to put require/includes everywhere, which really has a positive impact on RAD (rapid application development).

My thoughts:

Kudos for attempting such a daunting task, your approach appears to based on good practices such as singletons and factories.

I don't care for dependency injection. OOP is based on encapsulation, injecting one object into another, imo, breaks that encapsulation. When you inject an object into another object, the target object has to 'trust' that nothing regarding the injected object has changed, otherwise you may get unusual behavior.

Consider name-spacing your classes (not PHP name-spacing, but prefix your framework like Zend does, Zend_), this will help so you can register a namespace, then when a class is called the autoloader will ensure that the proper class is loaded. This is how Zend_Framework works. For specifics check out Zend_Loader_Autoloader . The Symfony framework actually takes this one step further; during the first request it will go through all known locations looking for class files, it will then build out an array of the classes and paths to the files then save the array to a file (file caching), so subsequent requests won't have the same overhead. Something to consider for your framework.

As far as config files go, Symfony uses YAML files, which I have found to be extremely flexible. You can even include PHP code for increased flexibility. Symfony has provided a stand-alone YAML parser that is easy to use. You can increase performance by adding a caching layer and caching parsed YAML files so you don't have to parse the files for every request.

I am assuming you are building your framework on top of an ORM. My recommendation would be not to any functionality specific to a version of the ORM, otherwise your framework becomes coupled with that version and you will have to upgrade both the ORM and the framework at the same time.

I would suggest looking under the hood at other frameworks and see if you can pick the best of each; resulting in a solid, easy to use framework.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM