Skip to content
This repository has been archived by the owner on Jan 16, 2019. It is now read-only.

Docs IOC

Frank Kleine edited this page Apr 7, 2012 · 1 revision

Table of Contents

Inversion of Control

Stubbles provides a very simple-to-use but still powerful inversion of control container, which supports constructor and setter based dependency injection. The IoC container of Stubbles is modeled after Google Guice and makes use of type hinting and Stubbles' annotation features. If you've never heard of type hinting or annotations, you should at first read the sections on these two topics:

The example code

Imagine, you are building a car configurator. To follow the rules of good design, you define interfaces for all components of a car and provide several classes that implement these components.

The interfaces in you application include:

#php
<?php
interface Car {
    public function moveForward($miles);
}
interface Person {
    public function sayHello();
}
interface Tire {
    public function rotate();
}
interface Engine {
    public function start();
}
?>

The implementations are:

#php
<?php
class BMW implements Car {
    protected $driver;
    protected $engine;
    protected $tire;

    public function __construct(Engine $engine, Tire $tire) {
        $this->engine = $engine;
        $this->tire = $tire;
    }
    public function setDriver(Person $driver) {
        $this->driver = $driver;
    }
    public function moveForward($miles) {
        $this->driver->sayHello();
        $this->engine->start();
        $this->tire->rotate();
    }
}

class Schst implements Person {
    public function sayHello() {
        echo "My name is Stephan\n";
    }
}

class Goodyear implements Tire {
    public function rotate() {
        echo "Rotating Goodyear tire\n";
    }
}

class TwoLitresEngine implements Engine {
    public function start() {
        echo "Starting 2l engine\n";
    }
}
?>

Without the dependency injection framework

To create a new instance of an implementation of Car the following code is required:

#php
<?php
$tire   = new Goodyear();
$engine = new TwoLitresEngine();
$schst  = new Schst();

$bmw    = new BMW($engine, $tire);
$bmw->setDriver($schst);

$bmw->moveForward(50);
?>

Creating objects manually like this has several drawbacks:

  • Your application is bound to the concrete implementations instead of the interfaces
  • Changing the implementation means changing existing code, which might break it
  • The creation of objects is scattered throughout your application

Enter 'Inversion of Control'

Stubbles tries to solve these problems, by providing functionality to handle all dependency injections for you. This keeps your application clean of boilerplate code, which is only needed to construct complex objects.

Furthermore, it allows you to centralize and/or modularize the definition of the concrete implementations for your interfaces or abstract types.

A simple example

To define the concrete implementations is done using an instance of stubBinder:

#php
<?php
// make sure, the IoC functionality is loaded
stubClassLoader::load('net::stubbles::ioc::stubBinder');

$binder = new stubBinder();
$binder->bind('Car')->to('BMW');
$binder->bind('Tire')->to('Goodyear');
$binder->bind('Person')->to('Schst');
$binder->bind('Engine')->to('TwoLitresEngine');
?>

In this short code snippet, you bound the interfaces from the example above to their concrete implementations.

If you now need an instance of the engine, you use the binder to create a stubInjector, which can be used to create the desired Engine:

#php
<?php
$injector = $binder->getInjector();
$engine = $injector->getInstance('Engine');
var_dump($engine);
?>

This code snippet will now display:

object(TwoLitresEngine)#48 (0) {
}

As desired, it created an instance of the concrete implementation, that you bound to the interface.

Adding dependency injection

Next, you probably want to get an instance of Car using the same approach:

#php
<?php
$injector = $binder->getInjector();
$car = $injector->getInstance('Car');
var_dump($car);
?>

This will result in the following error:

Catchable fatal error: Argument 1 passed to BMW::__construct() must implement interface Engine, none given in path/to/code.php on line 13

This had to happen, because the constructor of the BMW class requires you to pass an instance of Tire and Engine and Stubbles does not know, that it should do this. But it is very easy to tell Stubbles, that it should inject dependencies into the constructor, by just adding an @Inject annotation. You only need to modify the sourcecode for the class BMW a little bit:

#php
<?php
class BMW implements Car {
    protected $driver;
    protected $engine;
    protected $tire;

   /**
***@Inject
***/
    public function __construct(Engine $engine, Tire $tire) {
        $this->engine = $engine;
        $this->tire = $tire;
    }
    public function setDriver(Person $driver) {
        $this->driver = $driver;
    }
    public function moveForward($miles) {
        $this->driver->sayHello();
        $this->engine->start();
        $this->tire->rotate();
    }
}
?>

After you modified the class, just run the example again and you get the following output:

object(BMW)#33 (3) {
  ["driver:protected"]=>
  NULL
  ["engine:protected"]=>
  object(TwoLitresEngine)#37 (0) {
  }
  ["tire:protected"]=>
  object(Goodyear)#40 (0) {
  }
}

Stubbles created a new instance of BMW, as you bound it to Car, and as the constructor of BMW requires a Tire and an Engine instance, it created these instances as well. To determine the concrete classes to use, Stubbles used the bindings you defined in the stubBinder instance.

What you also can see is, that Stubbles did not inject an object into the $driver property, although you specified a binding for Person. Stubbles will never inject any dependencies, unless you annotate the constructor or setter method with @Inject. All that you need to do to inject a driver is to annotate the setDriver() method:

#php
<?php
class BMW implements Car {
    protected $driver;
    protected $engine;
    protected $tire;

   /**
***@Inject
***/
    public function __construct(Engine $engine, Tire $tire) {
        $this->engine = $engine;
        $this->tire = $tire;
    }
   /**
***@Inject
***/
    public function setDriver(Person $driver) {
        $this->driver = $driver;
    }
    public function moveForward($miles) {
        $this->driver->sayHello();
        $this->engine->start();
        $this->tire->rotate();
    }
}
?>

If you re-run this example, all properties will be set according to your bindings.

Further readings

Drawbacks

The Stubbles IoC container is very useful until you need to create various instances of the same class that have different dependencies. In this case you have to create the bindings from scratch for each of the objects which is not how the container is intended to be used.

For these cases, we recommend the use of XJConf for PHP which is tightly integrated into Stubbles.

Clone this wiki locally