Symfony2: Base Class for Common Dependencies
I have previously written about the different ways of injecting dependencies in Symfony2. In this post I will look at managing common dependencies in order to reduce code and config repetition. I will look at it using the same controller as before but this can be used for any service and not only controllers.
Simply put, common dependencies can be managed in a base class that is then extended. Setter and property injection lend themselves to this much more readily than constructor injection so I will concentrate on these. First up property injection, a simple base class would look like this:
<?php
namespace LimeThinking\SpringBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class BaseController
{
protected $response;
protected $request;
}
The controller now looks like this:
<?php
namespace LimeThinking\SpringBundle\Controller;
class StaticController extends BaseController
{
public function indexAction()
{
//do stuff to create response
return $this->response;
}
}
And the XML config like this:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="lime_thinking_spring.response"
class="Symfony\Component\HttpFoundation\Response"/>
<service id="lime_thinking_spring.request"
class="Symfony\Component\HttpFoundation\Request"/>
<service id="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\BaseController"
abstract="true">
<property name="request" type="service" id="lime_thinking_spring.request" />
<property name="response" type="service" id="lime_thinking_spring.response" />
</service>
<service id="lime_thinking_spring.static"
parent="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\StaticController" />
</services>
</container>
So the properties are now set for the parent class meaning that this can be reused for multiple controllers without having to rewrite the config for each service. Of note is that the base controller has the abstract attribute set to true preventing it from being requested from the service container. Also that the controller service has a parent attribute referring to the base controller, without this the dependencies would not be injected even though the PHP class extends this base class.
Whilst this reduces the need to repeat configuration for each subclass of the base class there is still the opportunity to change the dependencies for individual subclasses if required. If all the dependencies are different then simply leaving out the parent attribute will stop them being injected via the base class service config. If fewer than all are different then just the changes can be overridden in the service config. For example, to provide a different response object just to the static controller but not all the subclasses of the base controller then the following config could be used:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="lime_thinking_spring.response"
class="Symfony\Component\HttpFoundation\Response"/>
<service id="lime_thinking_spring.request"
class="Symfony\Component\HttpFoundation\Request"/>
<service id="lime_thinking_spring.custom_response"
class="LimeThinking\SpringBundle\HttpFoundation\CustomResponse"/>
<service id="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\BaseController"
abstract="true">
<property name="request" type="service" id="lime_thinking_spring.request" />
<property name="response" type="service" id="lime_thinking_spring.response" />
</service>
<service id="lime_thinking_spring.static"
parent="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\StaticController">
<property name="response" type="service" id="lime_thinking_spring.custom_response" />
</service>
</services>
</container>
It is similar using setter injection. The base class looks like this:
<?php
namespace LimeThinking\SpringBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class BaseController
{
protected $response;
protected $request;
public function setResponse(Response $response)
{
$this->response = $response;
}
public function setRequest(Request $request)
{
$this->request = $request;
}
}
The controller stays the same and the XML config like this:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="lime_thinking_spring.response"
class="Symfony\Component\HttpFoundation\Response"/>
<service id="lime_thinking_spring.request"
class="Symfony\Component\HttpFoundation\Request"/>
<service id="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\BaseController"
abstract="true">
<call method="setResponse">
<argument type="service" id="lime_thinking_spring.response" />
</call>
<call method="setRequest">
<argument type="service" id="lime_thinking_spring.request" />
</call>
</service>
<service id="lime_thinking_spring.static"
parent="lime_thinking_spring.base"
class="LimeThinking\SpringBundle\Controller\StaticController" />
</services>
</container>
Again the dependency can be overridden for the individual subclasses. Worth knowing is that the setter method will be called for the base controller and again replacing the dependency for the subclass rather the DIC deciding not to call it for the base class. Whilst this does not matter in the above example due to the simplistic nature of the setter it would in some cases, for example, if the setter added the passed dependencies to a collection. The following shows such a case, if the the base controller looks like this:
<?php
namespace LimeThinking\SpringBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class BaseController
{
protected $filters = array();
public function setFilter($filter)
{
$this->filters[] = $filter;
}
}
and the XML config like this:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="lime_thinking_spring.filterOne"
class="LimeThinking\SpringBundle\Filters\FilterOne"/>
<service id="lime_thinking_spring.filterTwo"
class="LimeThinking\SpringBundle\Filters\FilterTwo"/>
<service id="lime_thinking_spring.base" class="LimeThinking\SpringBundle\Controller\BaseController" abstract="true">
<call method="setFilter">
<argument type="service" id="lime_thinking_spring.filterOne" />
</call>
</service>
<service id="lime_thinking_spring.static" parent="lime_thinking_spring.base" class="LimeThinking\SpringBundle\Controller\StaticController" />
<call method="setFilter">
<argument type="service" id="lime_thinking_spring.filterTwo" />
</call>
</services>
</container>
This would lead to the $filters array containing both a FilterOne and a FilterTwo object. This is great if you just want to add additional filters to the subclasses. If you want to replace the filters passed to the subclass then removing the parent attribute from the service config will prevent the base class call to setFilter.
So managing common dependencies can now be done with much less repetition using these features of Symfony2 Dependency Injection configuration.