Jonwd
Jonwd

Reputation: 655

php dependency injection with a container

I'm developing a PHP framework for educational purposes. I have learned a lot since I started it.

I've decided how I'm going to deal with dependencies. I'm create a simple DI Container.


My first question is not about the DI Container itself, but how to inject objects that are created outside (before the DI Container).

Q: In the example: I am calling container->manualAdd('_logger', $logger);. Is there another way to accomplish this? Am I breaking the idea of DI Container?


My second question is about hooking functions. So when in bootstrap all objects are instantiated, objects by it selves can now begin to function.

Q: In the example: I'm creating an EventDispatcher. Whoever needs to do something either on doneBuild or beforeTerminate, is injected with BootstrapEventDispatcher. Is there another way to do this?

I begin to think EventDispatcher is overkill (for bootstrap only), and maybe implement something like: CodeIgniter:Hooks

Any help is appreciated.

Example bootstrap (pseudo-code):

function bootstrap($file = 'file.xml'){
    $logger = new Logger();
    $logger->log('bootstrap: init');

    $dispatcher = new BootstrapEventDispatcher($logger);

    $container = new DIContainer(new ConfigReader($file), $logger);
    $container->manualAdd('_logger', $logger);
    $container->manualAdd('_bootstrap_event_dispatcher', $dispatcher);
    $container->build();
    $dispatcher->doneBuild(null, new EventArgs());

    $dispatcher->beforeTerminate(null, new EventArgs());
    $logger->log('bootstrap: terminate');
}
class DIContainer{
    public function build(){
        //read xmls and create classes, etc.
        $this->logger->log('DIContainer: creating objects: {objects}');
    }
}

Example of an xml:

<!-- example file.xml !-->
<services>
    <service id="simple_class" class="SimpleClass"></service>
    <service id="complex_class" class="ComplexClass">
        <argument type="service" id="simple_class" /> <!-- dependency injection !-->
        <argument type="service" id="_logger" /> <!-- dependency injection !-->
        <argument type="service" id="_bootstrap_event_dispatcher" /> <!-- dependency injection !-->
    </service>
</services>

Example of ComplexClass:

class ComplexClass{
    public function __construct(SimpleClass $simpleClass, BootstrapEventDispatcher $dispatcher, Logger $logger){
        $this->simpleClass = $simpleClass;
        $this->logger = $logger;
        $dispatcher->onDoneBuild(array($this, 'onBootstrapDoneBuild'));
    }
    public function onBootstrapDoneBuild($obj, $args){
        //do something.
        $this->logger->log('complexclass: did something');
    }
}

Upvotes: 2

Views: 439

Answers (1)

Jonwd
Jonwd

Reputation: 655

From my understanding in Silex/Symfony2, is that there is no 'magical way' to do these type of things.

For my first question: It is allowed to add objects that are created before the container.

In Symfony2, in Kernel:initializeContainer function, Kernel adds itself to the container ($this->container->set('kernel', $this);) and later on, in the xml files, services are injected with Kernel (<argument id="kernel" type="service" />).

In Silex, Application:__construct function creates and adds objects to the container. Application injects itself to the ServiceProviders, so these providers can inject dependencies to their objects and add them to the container.

$container->manualAdd('_logger', $logger); is correct.


For my second question: depends on how I want to handle it. I came up with 3 options:

.1 For C#-event-like, Kernel is added into the container:

In ComplexClass: kernel.terminate += kernelTerminate

.2 EventDispatcher (as long as EventDispatcher class doesn't need dependencies that are created in the xml files)

//bootstrap function:
$dispatcher = new KernelEventDispatcher();
$kernel = new Kernel($dispatcher);
$container->manualAdd('_kernel.dispatcher');

.

<!-- in file.xml:ComplexClass !-->
<argument id="_kernel.dispatcher" type="service" />

.3 Create an object that implements an interface: (I couldn't find another name for Runnable)

<!-- in file.xml !-->
<service id="complex_class_runnable" class="ComplexClassRunnable">
    <argument type="service" id="complex_class" />
    <argument type="service" id="_kernel" />
</service>

.

//in ComplexClassRunnable
$kernel->addRunnable($this);

.

//in Kernel
foreach($this->runnables as $runnable){
    $runnable->init(); //same for terminate
}

Bootstrap updated:

function bootstrap($file = 'file.xml'){
    $logger = new Logger();
    $logger->log('bootstrap: init');

    $kernel = new Kernel($logger);
    $container = new DIContainer(new ConfigReader($file), $logger);

    $container->manualAdd('_kernel', $kernel);
    $container->manualAdd('_logger', $logger);
    $container->build();

    $kernel->boot();

    $logger->log('bootstrap: terminate');
}
class DIContainer{
    public function build(){
        //read xmls and create classes, etc.
        $this->logger->log('DIContainer: creating objects: {objects}');
    }
}
class Kernel{
    public function boot(){
        //...
    }
}

Upvotes: 2

Related Questions