Ian P. Christian's Personal Blog Random witterings from pookey

31Mar/093

sfServiceDefinition – a brief introduction

Fabien has already introduced the symfony service container, and will be shortly releasing a post covering more of it. I was keen to get my hands dirty, so I've dived into the code and got a quick example working.

This example is perhaps a bit useless in terms of functionality, but I think it does point out some pretty useful points. First we'll check out the sfServiceContainer:

# svn co http://svn.symfony-project.com/components/dependency_injection/trunk/lib/ sfServiceContainer
A    sfServiceContainer/sfServiceContainer.php
A    sfServiceContainer/sfServiceContainerAutoloader.php
A    sfServiceContainer/sfServiceContainerInterface.php
A    sfServiceContainer/sfServiceDefinition.php
A    sfServiceContainer/sfServiceContainerBuilder.php
A    sfServiceContainer/sfServiceReference.php
Checked out revision 16808.

In my example, I create an Interface which will represent a Request. The IRequest is very basic, and defines just one method - getParameter().

interface IRequest
{
  public function getParameter($name);
}

A really simple implementation of this request is as follows:

class myRequest implements IRequest
{
  private $params = array();
 
  public function __construct()
  {
    echo "BUILDING THE REQUEST!!!";
  } 
 
  public function getParameter($name)
  {
    return $params[$name];
  }
 
}

The only thing worth noting from this class is that we have a constructor that will print out that it's being constructed - this gives us the ability to see at which point the Service Container actually constructs our object.

Now we will create a service container, and register our request service with it, instructing it we want to use myRequest as our implementation of IRequest.

1
2
3
4
5
6
7
8
9
10
<?php
 
require_once './lib/sfServiceContainer/sfServiceContainerAutoloader.php';
sfServiceContainerAutoloader::register();
 
$service = new sfServiceDefinition('myRequest');
$service->setShared(true);
 
$sc = new sfServiceContainerBuilder();
$sc->setServiceDefinition('request', $service);

In the code above, we include the sfServiceContainerAutoloader which will handle autoloading of any other sfServiceContainer classes. We create a new sfServiceDefinition on line 7 - which not surprisingly allows us to define a service. On line 8 we set the service to be shared. A shared service will only be created once, so acts like a singleton in some ways. If we set this to false, everytime getService() was called for that service, we would get a new instance. One very important thing to take note of here is that we haven't instanced myRequest at this point.

We will now show a simple class called myAction, who's constructor expects an object implementing IRequest. Notice that we are coding against an interface here and not an implementation. This allows the possibility of replacing myRequest with any other class that implements the interface. The ability to swap out implementations greatly improves the testability of the code. In this example, when testing the myAction class, we could easily create a mock request which implements IRequest. I will leave an explanation of the improved testability for a later date.

class myAction
{ 
 
  private $request = null;
 
  public function __construct(IRequest $request)
  { 
    $this->request = $request;
  }
 
  public function doSomething()
  { 
    echo "moo!";
  }
}

Now here's the clever part - we will define the 'action' service, specifying that we want the 'request' service as a constructor argument.

$service = new sfServiceDefinition('myAction');
$service->setArguments(array(new sfServiceReference('request')));
$sc->setServiceDefinition('action', $service);

We have used sfServiceReference to tell the service container that when it does create an 'action' for us, we will want the 'request' service to be injected in the constructor. Again, this hasn't created a myRequest object, or a myAction object yet - only defined them as services.

Below is the script in full:

<?php
 
require_once dirname(__FILE__).'/../lib/vendor/sfServiceContainer/sfServiceContainerAutoloader.php';
sfServiceContainerAutoloader::register();
 
interface IRequest
{
  public function getParameter($name);
}
 
class myRequest implements IRequest
{
  private $params = array();
 
 
  public function __construct()
  {
    echo "BUILDING THE REQUEST!!!";
  }
 
  public function getParameter($name)
  {
    return $params[$name];
  }
 
}
 
 
class myAction
{
 
  private $request = null;
 
  public function __construct(IRequest $request)
  {
    $this->request = $request;
  }
 
  public function doSomething()
  {
    echo "moo!";
  }
}
 
$service = new sfServiceDefinition('myRequest');
$service->setShared(true);
 
$sc = new sfServiceContainerBuilder();
$sc->setServiceDefinition('request', $service);
 
$service = new sfServiceDefinition('myAction');
$service->setArguments(array(new sfServiceReference('request')));
$sc->setServiceDefinition('action', $service);
echo "beginning...";
 
$action = $sc->getService('action');
$action->doSomething();

The output of running this script proves that the Service Container doesn't create the objects until we actually need them - effectively lazy loading our services.

beginning...BUILDING THE REQUEST!!!moo!

In a real project, you would define your services, and their dependencies in a configuration file of some sort - either in native PHP, or in XML or YAML.

Filed under: php, symfony Leave a comment
Comments (3) Trackbacks (0)
  1. Very cool – I can see the endless flexibility to extend Symfony, something that has been handled well so far, but has certainly had its bounds. Little typo: “that when is does create an ‘action’ for us”, change is=>it

    Great – keep it up, I’ve been looking forward to these articles.

  2. Hi Ryan,

    Thanks for the feedback, it’s always good to know that someone likes my drivel ;)
    Typo fixed!

  3. Thanks for the tutorial but you have left a mistake. In getParameter() method, it shoulde be $this->params instead of $params ;)


Leave a comment

No trackbacks yet.