As there is no right or wrong in this question, I could give an example about how I like to use Dependency Injection in PHP.
I'm using the Dependency Injection Component from Symfony with the LosoDiAnnotationsBundle and extracted everything from the Bundle to use it as standalone as the Dependency Injection Component. I'm not using the rest of the Symfony Framework.
There are two functions in my system which use the Dependency Injection Container. The main function and the dispatcher. The main function (application entry point) retrieves the configured Dispatcher like this:
$dispatcher = $service_container->get('Dispatcher');
$response = $dispatcher->route( $request );
The service called "Dispatcher" is either defined in the global dependency config or annotated in the class itself like this:
/*
* @Service
*/
class Dispatcher {
/**
* @Inject({"@service_container"})
*/
public function __construct($serviceContainer) {
$this->serviceContainer = $serviceContainer;
}
}
The Dispatcher needs this service locator because thats how my routing works. If you use some extra mapping between routes and called methods, you could avoid this.
To inject something into other services I use the @inject
annotation:
/** @Inject({"@SomeLogic", "@SomeGateway"}) */
public function setDependencies(SomeLogic $someLogic, SomeGateway $someGateway) {
$this->someLogic = $someLogic;
$this->someGateway = $someGateway;
}
I like this approach because the configuration of a Service Class is done in the class itself and not in a separate file. Refactoring doesn't involve messing around with lengthy XML or YAML files.
To not harm the performance I run a script everytime I change some annotations and at build time (or in general on every vagrant up). It uses Symfony's ContainerBuilder and PHP Dumper to generate a PHP Lookup file which extends the Base ServiceContainer Class.
The lookup for the example dispatcher would look like this:
protected function getDispatcherService()
{
return $this->services['dispatcher'] = new \Some\Dispatcher($this);
}
This looks like a lot of work upfront, but it clearly separates concerns later on, especially in bigger projects. Classes do what they are supposed to do and need little to no non-domain-logic code at all.
And I try to keep factories to an absolutely minimum. The DI Container itself is one big factory and repository but it is not manually maintained.
EDIT:
My Routing Function looks like this, request_uri is something like Controller/action/...
$path = explode( self::ACTION_SEPARATOR, $request_uri );
if( empty( $path[1] ))
throw new RoutingException( 'Invalid Action' );
$method = ucfirst( $path[1] );
$className = $path[0].'Controller';
$qualifiedClassName = 'Some\\Namespace\\'.$className;
if( !class_exists( $qualifiedClassName ))
throw new RoutingException(sprintf('Invalid controller "%s" for route "%s"', $qualifiedClassName, $route));
if( !is_callable( array( $qualifiedClassName, $method ) ))
throw new RoutingException(sprintf('Invalid Action "%s"', $method));
$controller = $this->serviceContainer->get( $className );
call_user_func( array( $controller, $method ), $message );
Best Answer
Both
Game
andWorldFactory
class would haveFileSystem
as their dependencies. Below is an example of dependency injected through a constructor (it is also possible to inject them directly into fields):You could then write a provider inside one of your modules that would construct and return a new
FileSystem
instance:Once you treated other services similarly, you will be able to completely remove your reliance on
ServiceLocator
, moving the service-creating code directly into providers.Not only this is not messy, this is the whole point of dependency injection. The thing is, however, that you should never explicitly pass all those instances manually. Instead, you can have Guice reconstruct the dependency graph from them and construct all necessary, intermediate objects (or obtain them in any other way you specify in your providers). On your side, creating the final object is just a matter of calling
Injector.getInstance(Foo.class)
-- it's up to Guice to figure out how to satisify the dependencies.