Feature Wiki

Information about planned and released features

Tabs

Dependency Injection

1 Requirements

I would like to suggest a concept of dependency injection (using the PHP Reflection API and Pimple).

The most important code-snippet looks like this:

class SomeController {
/**
* Note: This method has a dependency on FooServiceInterface
* @param FooServiceInterface $service
*/

public static function doFooAction(FooServiceInterface $service) {
echo $service->foo();
}
}

The router (in our case ilCtrl) should scan the function/method signature by reflection API to be able to inject all dependencies. Resolving dependencies is done by explicit type hinting in the signature and pimple.

The idea is best described by this short Demo Code .

2 Additional Information

  • Idea / concept: Colin Kiegel
  • Interest in funding: Funding Needed
  • Maintainer: (will be set by Jour Fixe / maintainer)
  • Implementation of the feature is done by (will be set by Jour Fixe / maintainer)
  • Testcases by: (please add your name if you want to create the testcases for this feature)

3 Discussion

CK 20150401: You can also join the discussion in the SIG Refacturing.

Alex Killing, 1 Apr 2015: One stupid question: What would be the disadvantage, if the services needed by the controller would not be passed to it by using arguments function(service1, service2, ...), but if the controller would request the DiC instance and the services itself? This way the router would not need the reflection and we would not need to pass around all these service objects in constructors or other (like here static) functions to a controller.

CK 20150401: I think the differences are very subtle - but that's definitely the big question about how to use the DiC. These are the Pro/Cons that I can see (from previous discussions and readings):

  • Advantage of Approach A: passing the DiC 'around' e.g. to Controllers, etc.
    • Convenience would be similar to 'globals', wherever the DiC is available. If the DiC would be globally available that would be called the "service locator pattern" (one hotline for every service): convenience would be enpar with globals. I.e. we only gain the ability to handle complex service dependencies automatically.
  • Advantage of Approach B: keeping the DiC within restricted bounds
    • No architectural dependency on DiC implementation - replacing/removing pimple would not be a big deal - because only few central spots would depend on the DiC.
    • Type hinting + all the IDE-benefits like autocompletion <-- that is my favorite! :-)
    • Explicit service dependencies: Injection makes all dependencies explicit - there is no way around it without globals or a global service locator pattern. I think that's a big plus from an architectural point of view.
    • No risk of inflationary usage of the DiC as a service locator. If every second function places subcalls to a service locator that would add unneccessary overhead - compared to dependency injection.
Ok there are actually two decisions to be made - but usually they go along:
  1. Should the DiC be kept in restricted bounds?
  2. Shall we always use service injection (with type hinting)?
Intentionally I skipped one possible advantage of approach A / 'passing the DiC around'. One could argue that this enables delaying service initialisation right to the point where it is decided whether the service is needed at all. Say, if a complex controller only 'might' need a service. You would have to 'always inject the service in advance' (approach B) vs. 'locate the service when really needed' (approach A). This is a serious disadvantage of the strict approach B.

Here comes Approach B+ :-)
  • All Services definitely needed: Inject in advance (=approach B)
  • All Services maybe needed: Do something in the spirit of approach B ... but with delay 
     [see accordeon below for an example how this might look like]
I guess this will be a question of personal flavour. :-)
The question is the ratio of 'always' to 'maybe' needed.

class SomeController {
/**
* Note: This method uses conditional re-routing to inject additional services as needed
* @param ServicesAwareRouter $router
* @param FortuneServiceInterface $fortune
*/

public static function doFooAction(ServicesAwareRouter $router, FortuneServiceInterface $fortune) {
if($fortune->isLucky()) {
echo 'Jackpot - redirecting' . PHP_EOL;
$router->setRoute('SomeController','doExtraFooAction');
$router->dispatch();
} else {
echo 'sorry, no luck today' . PHP_EOL;
}
echo 'see you around' . PHP_EOL;
}
/**
* Note: This method has a dependency on FooServiceInterface
* @param FooServiceInterface $service
*/

public static function doExtraFooAction(FooServiceInterface $service) {
echo $service->foo() . PHP_EOL;
}
}

Working proof of concept -> extended demo.

almost as above:

  • replace 'doExtraFooAction()' with 'setFooService()'
  • move all logic to 'doFooAciton()'

4 Implementation

...

Last edited: 1. Apr 2015, 21:31, Kiegel, Colin [kiegel]