I just started using Dependency Injection for obvious reasons and without reading about Inversion of Control (IoC) quickly stumble with the issue of being verbose when instantiate some of my classes. So, reading about IoC I have a question that have not found an concrete answer. When should class registration happen? in a bootstrap? before execution? How can I enforce the type of the dependencies?
I am not using any frameworks. For the sake of learning I wrote my own container.
This is a very lowbrow example of my container and some sample classes.
class DepContainer
{
private static $registry = array();
public static function register($name, Closure $resolve)
{
self::$registry[$name] = $resolve;
}
public static function resolve($name)
{
if (self::registered($name)) {
$name = static::$registry[$name];
return $name();
}
throw new Exception('Nothing bro.');
}
public static function registered($name)
{
return array_key_exists($name, self::$registry);
}
}
class Bar
{
private $hello = 'hello world';
public function __construct()
{
# code...
}
public function out()
{
echo $this->hello . "\n";
}
}
class Foo
{
private $bar;
public function __construct()
{
$this->bar = DepContainer::resolve('Bar');
}
public function say()
{
$this->bar->out();
}
}
With these already in the app structure. The Dependecy Injection way I would do type hint the incoming parameters, but without it I can do:
DepContainer::register('Bar', function(){
return new Bar();
});
$f = new Foo();
$f->say();
To me, makes sense in a bootsrap register all dependencies it would be the more clean way IMO. At run time like a showed you I think is just as ugly as doing new Foo(new Bar(...)...).
I will try to summarize a few things that you should know and (hopefully) will clarify some of your dilemmas .
Let's start from a basic example:
class MySQLAdapter
{
public function __construct()
{
$this->pdo = new PDO();
}
}
class Logger
{
public function __construct()
{
$this->adapter = new MySqlAdapter();
}
}
$log = new Logger();
As you can see, we are instantiating Logger which has two dependencies: MySQLAdapter and PDO.
This process works like this:
We created Logger
Logger creates MySQLAdapter
MySQLAdapter creates PDO
The above code works, but if tomorrow we decided that we need to log our data in a file instead of a database, we will need
to change the Logger class and replace MySQLAdapter with a brand new FileAdapter.
// not good
class Logger
{
public function __construct()
{
$this->adapter = new FileAdapter();
}
}
This is the problem that Dependency Injection tries to solve: do not modify a class because a dependency has changed.
Dependency Injection
Di reefers to the process of instantiating a class by giving it's constructor all the dependencies it needs to function properly. If we apply Dependency
Injection to our previous example, it will look like this:
interface AdapterInterface
{
}
class FileAdapter implements AdapterInterface
{
public function __construct()
{
}
}
class MySQLAdapter implements AdapterInterface
{
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class Logger
{
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
}
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
As you can see, we don't instantiate anything in constructor, but we pass the instantiated class to constructor. This allows us to replace any dependency
without modifying the class:
// log to file
$log = new Logger(
new FileAdapter()
);
This helps us:
To easily maintain the code:
As you already saw, we don't need to modify the class if one of its dependencies changed.
Makes the code more testable:
When you run your test suite against MySQLAdapter you don't want to hit the database on each test, so the PDO object will be mocked in tests:
// test snippet
$log = new Logger(
new MySQLAdapter(
$this->getMockClass('PDO', [...])
)
);
Q: How does Logger knows that you give him a class that it needs and not some garbage ?
A: This is the interface (AdapterInterface) job, which is a contract between Logger and other classes. Logger "knows" that any class that implements
that particular interface will contain the methods it needs to do his job.
Dependency Injection Container:
You can look at this class (ie: container) as a central place where you store all your objects needed to run your application. When you need one of them,
you request the object from the container instead of instantiating yourself.
You can look at DiC as a dog who was trained to get out, get the newspaper and bring it back to you. The catch is that the dog was trained only with the front door opened.
Everything would be fine as long as the dog's dependencies will not change (ie door opened). If one day the front door will be closed, the dog will not know how to get the newspaper.
But if the dog would have an IoC container, he could find a way ...
Inversion of Control
As you saw until now, the initialization process of the "classic" code was:
We created Logger
Logger creates MySQLAdapter
MySQLAdapter creates PDO
IoC simply replicates the above process, but in reverse order:
Create PDO
Create MySQLAdapter and give him PDO
Create Logger and give him MySQLAdapter
If you though that Dependency Injection is some kind of IoC, you are right. When we talked about Dependency Injection, we had this example:
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
At a first look someone could say that the instantiation process is:
Create Logger
Create MySQLAdapter
Create PDO`
The thing is that the code will be interpreted from the middle to the left. So the order will be:
Create PDO
Create MySQLAdapted and give him PDO
Create Logger and give him MySQLAdapter
The IoC container simply automates this process. When you request Logger from the container, it uses PHP Reflection and type hinting to analyze its dependencies (from constructor), instantiate all of them, sends them to the requested class and gives you back a Logger instance.
NOTE: To find out what dependencies a class has, some IoC containers are using annotations instead of type hinting or a combination of both.
So to answer your question:
If the container can resolve the dependencies by itself, you would only need to instantiate the container during the boot
process of your application. (see Inversion of Control container)
If the container can't resolve the dependencies by itself, you would need to manually provision the container with the objects needed to run your application. This provisioning usually happens during the boot process. (see Dependency Injection Container)
If your container can resolve the dependencies by itself, but for various reasons you also need to manually add more dependencies, you would do that in
the boot process, after you initialize the container.
NOTE: In the wild there are all kind of mixes between these two principles, but I tried to explain you what is the main idea behind each of them.
How your container will look depends only by you and don't be afraid to reinvent the wheel as long as you do it for educational purposes.
Related
I have been reading up on OOP for a while now and it's always confused me but I'm trying to improve on my skills (albeit I'm still starting out with this so please be kind).
I am designing a main app, whose "core" class is $App. My thought is that anywhere in the app, you can do everything from within $App.
For example, I have a $Db class to query the database, $Mail class for sending email, etc. These are all autoloaded using spl_autoload_register.
My question is in regards to actually instantiating them properly. Here's how I'm doing it now:
index.php
require_once('config.php'); // Autoloads classes and some other small stuff
$App = new App;
echo $App->Visitor->getIP();
App.class.php
class App {
public function __construct() {
$this->initialize();
}
private function initialize() {
$this->Visitor = new Visitor($this);
$this->Db = new Db($this);
$this->Mail = new Mail($this);
}
// Then some public methods...
// We are able to use $this->Visitor, $this->Db, etc...
}
Visitor.class.php
class Visitor {
private $App;
public function __construct($App) {
$this->App = $App; // I need access to all of App's methods
}
public function getIP() {
return $_SERVER['REMOTE_ADDR'];
}
}
So the short example above shows how I'm doing this in all my classes. The App class always creates an instance of whatever class we will be using in the entire app in it's constructor.
The "child" classes all [dependency injects?] the $App instance so they can use it's methods.
Am I doing this correctly? It works, but I don't know if there is a more efficient/better practice way of doing this.
The problem with injecting the base class on instantiation is that it makes all of your classes have really ambiguous dependencies. Your Visitor class could rely on a class that parses the $_SERVER superglobal, but I can't tell just by looking at it, it's only dependency is the whole app object.
It's much better to only define the specific dependencies a class needs (usually in the constructor) and create something whose job it is to properly provide these dependencies. This is the "dependency injection" that you were hinting at, and they usually involve some Container that stores all the objects that have been instantiated, ready to inject into new classes.
So your app will have the dependency of a container:
$app = new App(new Container);
And then you can ask the container for dependencies
class App {
/**
* #return App\Visitor
*/
public function getVisitor()
{
if (!$this->visitor) {
$this->visitor = $this->container->get('App\Visitor');
}
return $this->visitor;
}
}
If you use a third party dependency injection library (recommended - even when starting out as designing your own is a bit of a tangent) then the library will usually provide some way to automatically resolve dependencies. It can look at the constructor on your class (that's typehinted correctly, e.g. __construct(Visitor $visitor)) and automatically provide the arguments to the constructor - using the Reflection SPL
I would also recommend if you're starting out with OOP that you first familiarise yourself with PSR-4 and Composer autoloading, as including all of your classes manually is going to become quite a hassle, and doing it the "standard" way at the beginning makes life easier in the future when you will want to switch ;) .
I've been doing MVC for several months now, and I store everything in my $registry object. When I create a new class, I only ever pass the registry usually, but I'm having to constantly pass the $this->registry when creating a new class.
e.g.
class something
{
public function __construct($registry)
{
$this->registry = registry;
$this->db = $registry->db;
$this->user = $registry->user; // ......
}
public function something()
{
$class = new something_class($this->registry);
$class->do();
}
}
class something_class
{
public function __construct($registry)
{
$this->registry = $registry;
}
public function do()
{
echo 'Doing something ....';
}
}
My question is, how can I handle the passing of the registry to the new class behind the scenes (in this case when instantiating something_class) inside the registry class somehow? I'm absolutely convinced there is an easy way to do this, but I can't find anything related anywhere to what I'm looking for.
Here is my registry class:
<?php
class registry
{
protected $vars = array();
public function &__set($index, $value)
{
$this->vars[$index] = $value;
return $value;
}
public function &__get($index)
{
return $this->vars[$index];
}
}
This is all wrong. "Registry" is an anti-patter and what you are doing the is not dependency injection. You have found a way to fake global variables .. that's it.
As a start, please watch this lecture.
As for, how to correctly do what you want, there are two ways:
use a factory, that creates a class, using dependencies that you provided
use a dependency injection container, Auryn
To learn how you use a DI container, you will just have to consult the documentation. But I will explain the basics of factory, which is more of a DIY approach
A factory is an object, that is responsible for initializing other class. For example, you have a large set of classes, which require PDO as a dependency.
class Factory
{
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function create($name) {
return new $name($this->pdo);
}
}
If you use an instance of this class, it would let you create objects, which have the PDO already passed in as a dependency in a constructor:
$factory = new Factory(PDO($dsn, $user, $pass));
$user = $factory->create('User');
$document = $factory->create('Doc');
And as an added benefit, this setup would let bot the User class instance and the Doc class instance to share the same PDO object.
It's best practice to have your classes or components not be dependent on your container. Let your container do the injection of dependencies.
Here's an example that uses Container from The League of Extraordinary Packages and shows class B being dependent on A:
<?php
$container = new League\Container\Container();
$container->add('b', function () {
$a = new A('foo');
$b = new B($a);
return $b;
});
var_dump($container->get('b')->getValueFromA()); // Outputs "foo"
class A
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
class B
{
public function __construct(A $a)
{
$this->a = $a;
}
public function getValueFromA()
{
return $this->a->getValue();
}
}
Each class you write should only be coupled to its direct dependencies. If possible, the use of interfaces is highly recommended for further decoupling.
Please see this Stackoverflow question for more information around the differences between a registry and a DI container. Using a registry is considered an anti-pattern and should be avoided.
Your $registry class is what's called a Service Locator pattern. It is useful in some contexts, but it has a high potential for being abused, especially when you inject the service locator into your class.
Dependency Injection is supposed to expose (show) all the objects that your class uses (or depends on). Injecting $registry in your class instead hides the dependencies, and this is why it is an anti-pattern. Also, it creates a burden for you as you have to pass it everywhere, you might as well make it a global to make it easier (this will also answer your question). But better tools are available.
Consider your class like so:
Note the use of dependency injection of $db and $user into constructor of something class. Now it is more clear what your class needs to work.
Also note the use of dependency injection container in this case Auryn to create a class for you.
//in your bootstrap or part of your framework
$injector = new Auryn\Injector();
$class = $injector->make('something_class');
//Your own code:
class something
{
public function __construct(Database $db, User $user)
{
$this->db = $db;
$this->user = $user;
}
public function something(something_class $class)
{
$class->do();
}
}
You can also use Auryn to do what you want to do to call the Registry class. But you will soon find that Auryn is a better more robust version of your Registry class and one that forces you to use better dependency-injection techniques for Auryn to work as intended.
Basic point is that your own application (all of your application's classes) best not be aware of any container. Because what containers do is they hide dependencies from your classes. It remains however as acceptable to use containers as part of a framework. Let your framework handle containers, but you focus on your own classes, without calling containers unless those containers are used as part of your framework to wire up your application.
I am trying to understand the role of a Dependency Injection Container because it strikes me as fundamental in maintainable code.
As I understand it, a DIC is just as the title suggests: a container where all your dependencies are collected together. Instead of seeing new Foo\Bar all over the application, all the new instances are generated inside of the container and then passed through to each other where they are needed (e.g., Model is instantiated with an instance of Database, which is instantiated with an Instance of Config).
I have attempted to make a very simple DIC. This is the result.
In my front controller I am instantiating a new App\Core\Container.
My Container looks like this:
<?php
namespace App\Core;
use App\Config;
class Container
{
public $config;
public $router;
public $database;
public $model;
public $view;
public $controller;
public function __construct()
{
$this->config = new Config;
$this->router = new Router;
$this->database = new Database($this->config);
}
public function add()
{
// add dependencies from the outside?
}
public function getInstance(/* string */)
{
// return an instance for use somewhere?
}
public function newModel($model)
{
$model = $this->getModelNamespace() . $model;
$this->model = new $model($this->database);
return $this->model;
}
private function getModelNamespace()
{
$namespace = 'App\Models\\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\\';
}
return $namespace;
}
public function newView($params)
{
$this->view = new View($this->model, $params);
return $this->view;
}
public function newController($controller)
{
$controller = $this->getControllerNamespace() . $controller;
$this->controller = new $controller;
return $this->controller;
}
private function getControllerNamespace()
{
$namespace = 'App\Controllers\\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\\';
}
return $namespace;
}
}
Questions
Could my implementation above, although very simple, be classed as a basic dependency injector?
Is a Dependency Injection Container generally comprised of one class?
Note: the first three headings answer your questions, while the following ones answer anticipated questions and provide coverage of anything in the first two sections.
Could this be classified as a dependency injection container?
No, this does not look like a dependency injection container. A dependency injection container is meant to reduce the work that instantiation requires by determining, creating, and injecting all dependencies. Rather what you have there appears to be a combination of a factory and a service locator.
Factories abstract the creation of objects. This is essentially what your Container class is doing. By calling designated methods (i.e., newModel), your container takes on the responsibility of locating the exact object to be instantiated and constructing an instance of that object.
The reason I would call this a "poor" factory is that it's beginning to look like it might be used to locate services. Service locators work by hiding an object's dependencies: instead of being dependent on GenericService, an object might depend on a service locator. Given the service locator, it can request an instance of GenericService. I see similar behavior beginning to take hold in your add() and getInstance() methods. Service locators are generally considered anti-patterns because they abstract dependencies therefore making code impossible to test!
Is a dependency injection container comprised of one class?
It depends. You could very easily make a simple dependency injection container with one class. The issue is that the nature of a simple container tends to get more advanced into a not-so-simple container. When you start improving your pattern, you need to consider how the different components play together. Ask yourself: do they follow SOLID principles? If not, refactoring is necessary.
What is a dependency injection container?
I said it above, but again: a dependency injection container is meant to reduce the work that instantiation requires by determining, creating, and injecting all dependencies. A DIC will look at all dependencies of a class, and all dependencies those dependencies may have, and so on... In this sense, the container is responsible for hierarchically instantiating all dependencies.
The Container class you provide relies on very strict definitions of pre-defined classes. For example, classes in your model layer appear to only be dependent on a database connection. (Similar statements can be said about classes in your controller & view layer).
How does a dependency injection container find dependencies?
A dependency injection container will detect dependencies. Typically this happens through 1 of 3 mechanisms: autowiring, annotations, and definitions. PHP-DI docs provide a good idea of what all three of these entail here. In short, though: autowiring detects dependencies by reflecting on a class, annotations are used to write in dependencies using comments above a class, and definitions are used to hard-code dependencies. Personally, I prefer autowiring because it's clean & simple.
Can I create a simple dependency injection container or no?
Yes, you can. Start with the idea that an injector should be able to instantiate any object (service, view, controller, etc...). It needs to look at the relevant object and hierarchically instantiate all dependencies (hint: possibly through some method of recursion).
A quick example of a simple injector using autowiring looks like this:
<?php
class Injector
{
public function make($className)
{
$dependencies = [];
//Create reflection of the class-to-make's constructor to get dependencies
$classReflection = new ReflectionMethod($className, "__construct");
foreach($classReflection->getParameters() as $parameter) {
$dependencyName = $parameter->getClass()->getName();
//Use the injector to make an instance of the dependency
$dependencies[] = $this->make($dependencyName);
}
$class = new ReflectionClass($className);
//Instantiate the class with all dependencies
return $class->newInstanceArgs($dependencies);
}
}
Tested with something like the following, you can see how the injector recursively checks and instantiates all dependencies
class A {
protected $b;
public function __construct(B $b) { $this->b = $b; }
public function output(){ $this->b->foo(); }
}
class B {
protected $c;
public function __construct(C $c) { $this->c = $c; }
public function foo() { $this->c->bar(); }
}
class C {
public function __construct() { }
public function bar() { echo "World!"; }
}
$injector = new Injector;
$a = $injector->make("A");
//No need to manually instantiate A's dependency, B, or B's dependency, C
$a->output();
This basic injector has obvious faults. For example, there is an opportunity to create a recursion disaster if two classes are dependent on each other (there should be a check for that). However, as is, this works as a basic example of what an injector looks like.
Injector vs. Dependency Injection Container
To make this more powerful and fall under the definition of "dependency injection container", you'd want a way to share instantiated instances across multiple make() calls. For example, you may have another method called share(). This method would store the instance passed to it. Whenever a class is built through the make() method and depends on a class previously shared, instead of instantiating a new instance, it would use the already-instantiated one.
For a simple & powerful dependency injection container I suggest Auryn, but by all means, try to understand & create your own before using the ones already available.
I'm trying to get my head around Dependency Injection and I understand it, for the most part.
However, say if, for some reason, one of my classes was dependent on several classes, instead of passing all of these to this one class in the constructor, is there a better, more sensible method?
I've heard about DI Containers, is this how I would go about solving this problem? Where should I start with this solution? Do I pass the dependencies to my DIC, and then pass this to the class that needs these dependencies?
Any help that would point me in the right direction would be fantastic.
Dependency Injection !== DIC
People should really stop confusing them. Dependency Injection is idea that comes from Dependency Inversion principle.
The DIC is "magic cure", which promises to let you use dependency injection, but in PHP is usually implemented by breaking every other principle of object oriented programming. The worst implementations tend to also attach it all to global state, via static Registry or Singleton.
Anyway, if your class depends on too many other classes, then in general , it signifies a design flaw in the class itself. You basically have a class with too many reasons to change, thus, breaking the Single Responsibility principle.
In this case, then dependency injection container will only hide the underlaying design issues.
If you want to learn more about Dependency Injection, i would recommend for you to watch the "Clean Code Talks" on youtube:
The Clean Code Talks - Don't Look For Things!
The Clean Code Talks - "Global State and Singletons"
If you have several dependencies to deal with, then yes a DI container can be the solution.
The DI container can be an object or array constructed of the various dependent object you need, which gets passed to the constructor and unpacked.
Suppose you needed a config object, a database connection, and a client info object passed to each of your classes. You can create an array which holds them:
// Assume each is created or accessed as a singleton, however needed...
// This may be created globally at the top of your script, and passed into each newly
// instantiated class
$di_container = array(
'config' = new Config(),
'db' = new DB($user, $pass, $db, $whatever),
'client' = new ClientInfo($clientid)
);
And your class constructors accept the DI container as a parameter:
class SomeClass {
private $config;
private $db;
private $client;
public function __construct(&$di_container) {
$this->config = $di_container['config'];
$this->db = $di_container['db'];
$this->client = $di_container['client'];
}
}
Instead of an array as I did above (which is simple), you might also create the DI container as an class itself and instantiate it with the component classes injected into it individually. One benefit to using an object instead of an array is that by default it will be passed by reference into the classes using it, while an array is passed by value (though objects inside the array are still references).
Edit
There are some ways in which an object is more flexible than an array, although more complicated to code initially.
The container object may also create/instantiate the contained classes in its constructor as well (rather than creating them outside and passing them in). This can save you some coding on each script that uses it, as you only need to instantiate one object (which itself instantiates several others).
Class DIContainer {
public $config;
public $db;
public $client;
// The DI container can build its own member objects
public function __construct($params....) {
$this->config = new Config();
// These vars might be passed in the constructor, or could be constants, or something else
$this->db = new DB($user, $pass, $db, $whatever);
// Same here - the var may come from the constructor, $_SESSION, or somewhere else
$this->client = new ClientInfo($clientid);
}
}
I've wrote an article about this problem.
The ideea is to use a combination of abstract factory and dependency injection to achieve transparent dependency resolving of (possible nested) dependencies. I will copy/paste here the main code snippets:
namespace Gica\Interfaces\Dependency;
interface AbstractFactory
{
public function createObject($objectClass, $constructorArguments = []);
}
The abstract factory implementation is:
namespace Gica\Dependency;
class AbstractFactory implements \Gica\Interfaces\Dependency\AbstractFactory, \Gica\Interfaces\Dependency\WithDependencyInjector
{
use WithDependencyInjector;
/**
* #param string $objectClass
* #param array $constructorArguments
* #return object instanceof $class
*/
public function createObject($objectClass, $constructorArguments = [])
{
$instance = new $objectClass(...$constructorArguments);
$this->getDependencyInjector()->resolveDependencies($instance);
return $instance;
}
}
The dependency injector is this:
namespace Gica\Dependency;
class DependencyInjector implements \Gica\Interfaces\Dependency\DependencyInjector
{
use \Gica\Traits\WithDependencyContainer;
public function resolveDependencies($instance)
{
$sm = $this->getDependencyInjectionContainer();
if ($instance instanceof \Gica\Interfaces\WithAuthenticator) {
$instance->setAuthenticator($sm->get(\Gica\Interfaces\Authentication\Authenticator::class));
}
if ($instance instanceof \Gica\Interfaces\WithPdo) {
$instance->setPdo($sm->get(\Gica\SqlQuery\Connection::class));
}
if ($instance instanceof \Gica\Interfaces\Dependency\WithAbstractFactory) {
$instance->setAbstractFactory($sm->get(\Gica\Interfaces\Dependency\AbstractFactory::class));
}
//... all the dependency declaring interfaces go below
}
}
The dependency container is the standard one.
The client code could look something like this:
$abstractFactory = $container->get(\Gica\Interfaces\Dependency\AbstractFactory::class);
$someHelper = $abstractFactory->createObject(\Web\Helper\SomeHelper::class);
echo $someHelper->helpAction();
Notice that dependencies are hidden, and we can focus on the main bussiness. My client code doesn't care or know that $someHelper need an Authenticator or that helpAction need an SomeObject to do its work;
In the background a lot of things happen, a lot of dependencies are detected, resolved and injected.
Notice that I don't use the new operator to create $someObject. The responsability of actual creation of the object is passed to the AbstractFactory
P.S. Gica is my nickname :)
I recommend you to use Singltones or Mutlitones. In these cases you will be always able to get objects via static class' methods.
The other way (couldn't find a correct pattern name, but it could be Registry) is to use one global static object to store multiple objects' instances. E.g. (simplified code, without any checks):
class Registry {
private static $instances = array();
public static function add($k, $v) {
$this->instances[$k] = $v;
}
public static function get($k) {
return $this->instances[$k];
}
}
class MyClass {
public function __construct() {
Registry::add('myclass', $this);
}
}
Following on from my question on service locators I have decided to use constructor injection instead. Please consider the following code:
<?php
interface IAppServiceRegistry {
public function getDb();
public function getLogger();
}
interface IFooServiceRegistry extends IAppServiceRegistry {
public function getFooBarBazModel();
}
class AppServiceRegistry
implements IAppServiceRegistry, IFooServiceRegistry
{
private $logger;
private $db;
private $fooBarBazModel;
public function getDb() {
// return db (instantiate if first call)
}
public function getLogger() {
// return logger (instantiate if first call)
}
public function getFooBarBazModel() {
if (!isset($this->fooBarBazModel)) {
$this->fooBarBazModel = new FooBarBazModel( $this->getDb() );
}
return $this->fooBarBazModel;
}
}
// Example client classes:
/**
* Depends on db, logger and foomodel.
*/
class Foo {
private $db;
private $logger;
private $fooModel;
public function __construct(IFooServiceRegistry $services) {
$this->db = $services->getDb();
$this->logger = $services->getLogger();
$this->fooModel = $services->getFooModel();
}
}
/**
* Depends on only db and logger.
*/
class BarBaz {
private $db;
private $logger;
public function __construct(IAppServiceRegistry $services) {
$this->db = $services->getDb();
$this->logger = $services->getLogger();
}
}
Then I would add new service factory methods to the registry as the application evolved, creating segregated interfaces wherever logically appropriate.
Is this approach sane?
Although I don't normally read php, I think I understand most of it. Technically, it looks fine, but then you write
I would add new service factory methods to the registry as the application evolved
That tends to hurt the idea of loose coupling because instead of a general-purpose Service Locator you now have a specialized Service Locator.
Injecting such a serivce registry into each class works against the Single Responsibility Principle (SRP) because once a class has access to the registry, it is too easy to request yet another dependency than originally conceived, and you will end up with a God Object.
It would be better to inject the required dependencies directly into the classes that need them. Thus, your Foo class' constructor should take a db, a logger and a fooModel, while the BarBaz class should take only a db and a logger parameter.
The next question may then be: What if I need a lot of different dependencies to perform the work? That would require a truly ugly constructor with lots of parameters, and this goes against other well-known OO practices.
True, but if you need lots of dependencies, you are probably violating the SRP and should try to split your design into finer-grained objects :)
This implementation is almost the same as the service locator you previously showed us.
A good question to ask is whether upon looking at the class of your objects, you know everything about the objects needs to get their job done. In your case you still don't know.
If Foo needs the db, logger and model, you make this clear by asking for these in the constructor.
Here's a good read on the matter:
http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/
I've been grappling with this kind of problem lately. Service Locator vs Depedency Injection.
I agree with Mark that the way to go is to inject individual fine-grained objects into the constructor, as required. The only drawback, as Mark has highlighted, is that when you build a complex object graph, you inevitably have to start somewhere. This means that your high-level objects will have lots of services (objects) injected into them.
The easy way round this is to use something to do the hard work for you. The prime example of this is Google's Guice which seems to me a very good way of going about things. Sadly it's written for Java! There are PHP versions about; I'm not sure any of them quite measure up to Guice at this point.
I've written a post on this topic which goes into more detail; which you may find interesting. It includes a simple implementation of a dependency injection framework.
The bottom line is that if you've got a class Foo with a number of requirements, you can create your class as such:
/**
* Depends on db, logger and foomodel.
*/
class Foo
{
private $db;
private $logger;
private $fooModel;
/**
* (other documentation here)
* #inject
*/
public function __construct(IDbService $db, ILoggerService $logger, $iModelService $model)
{
// do something
}
}
When you want a new Foo object, you simply ask the dependency injection framework to create you one:
$foo = $serviceInjector->getInstance('Foo');
The dependency injector will do the hard work, making sure it injects the dependencies. This includes any dependencies of the dependencies, if that makes sense. In other words it will sort it all out recursively up the tree.
Later, when you find you need an IBarService object, you can just add it to the Construtor, without altering any other code!