Service dependencies injection
Symfony2 service container handles services in a kind of lazyness. A service is never registered until it is invoked. But what about service dependencies? Well, when a service is invoked for the first time, Symfony2 registers its service dependencies first (if they aren't yet) to inject them to the service itself (container service) and then register it.
Let's see an example:
Guess we have the following services defined in services.yml
services:
dependency:
class: SaveTheCode\LazyDependenciesBundle\Service\DependencyService
main:
class: SaveTheCode\LazyDependenciesBundle\Service\MainService
arguments:
- "@dependency"
There are 2 different services, having dependency service (dependency) as an argument for main service (container). So the process to register main service when invoked is:
- Create and register main service dependencies
- Service dependency in our case. So DependencyService class instance is created and registered in service container as a service called dependency
- Create main service and inject dependencies
- Dependencies defined as service arguments are injected in service class
constructor
as parameters. In our case MainServiceconstructor
will have a parameter that is the service called dependency (DependencyService class instance)
- Register main service in service container having an instance of MainService class
Whenever a service is invoked for the first time, the service class instance is created and registered as well as its service dependencies
Let's take a look to service classes code
class MainService {
private $dependency;
public function __construct(DependencyService $dependency) {
$this->dependency = $dependency;
}
public function doSomething() { ... }
public function doSomethingWithDependency() {
...
$this->dependency->doWhatever();
...
}
}
class DependencyService {
public function doWhatever() { ... }
}
As you can see, MainService class constructor
has an argument type hinted as DependencyService (line 4), that is actually the service class defined as its argument in services.yml, and it's assigned to a private class property called dependency. MainService class has 2 more methods:
- doSomething
- doSomethingWithDependency
First method doSomething()
doesn't call dependency service, but second method doSomethingWithDependency()
calls dependency service method doWhatever()
(line 12)
Whenever main service is invoked, Symfony2 will provide an instance of MainService class. As we said before, a dependency service must be registered (having an instance of DependencyService class) to be able to register main service in service container.
This doesn't look wrong and actually it isn't. Let's see a simple example in which main service is invoked:
class DefaultController extends Controller {
public function indexAction() {
...
$this->container->get('main')->doSomething();
...
}
}
Hey! wait a sec. indexAction()
calls main service method doSomething()
(line 4). But if you go back to MainService class, this method doesn't access its injected service dependency, so we don't need a DependencyService instance to run this code, however, it will exist because Symfony2 needs it to create MainService class instance and register main service.
Well, this is not wrong, remember that service dependencies must be registered in service container before registering a new service, but what happens if DependencyService instance creation takes a lot of time/resources and it's not invoked? Let's make the following assumption to clear up
class DependencyService {
public function __construct() {
/* guess low performance code here */
}
public function doWhatever() { ... }
}
Calling indexAction()
we are wasting time/resources in DependencyService instance creation and we don't really need to because it's never invoked.
Lazy services
To let our service dependencies classes be created really when they are needed and not when registered in Symfony2 service container when injected to other services, we have lazy services.
To enable this feature, you have to follow 2 steps:
Include proxy-manager dependency
composer require ocramius/proxy-manager:~1.0
Declare service dependency as lazy
services:
dependency:
class: SaveTheCode\LazyDependenciesBundle\Service\DependencyService
lazy: true
main:
class: SaveTheCode\LazyDependenciesBundle\Service\MainService
arguments:
- "@dependency"
Notice that dependency service is defined as lazy bacause this is the service dependency that we need to be registered delaying contructor
call until needed
It's pointless to define a service lazy if it is never injected into other service, since a service class instance will be created in the very moment of invocation.
What happens in previous example when indexAction()
is executed and main service method doSomething()
is called?
Actually dependency service is registered in Symfony2 service container since it 's a dependency for main service, but dependency service has not an instance of DependencyService class but a proxy object pretending to be an instance of real class bypassing its creation (constructor
call) until it is really needed.
Thus, lazy dependency service class constructor
will not be called until any of its methods is called
Sample Application
Guess we have this service definition
services:
lazy_dependency:
class: SaveTheCode\LazyDependenciesBundle\Service\DependencyService
lazy: true
main_with_lazy_dependency:
class: SaveTheCode\LazyDependenciesBundle\Service\MainService
arguments:
- "@lazy_dependency"
dependency:
class: SaveTheCode\LazyDependenciesBundle\Service\DependencyService
main:
class: SaveTheCode\LazyDependenciesBundle\Service\MainService
arguments:
- "@dependency"
Guess we have this controller
class DefaultController extends Controller {
public function doSomethingLazyAction() {
$this->container->get('main_with_lazy_dependency')
->doSomething();
}
public function doSomethingWithDependencyLazyAction() {
$this->container->get('main_with_lazy_dependency')
->doSomethingWithDependency();
}
public function doSomethingAction() {
$this->container->get('main')
->doSomething();
}
public function doSomethingWithDependencyAction() {
$this->container->get('main')
->doSomethingWithDependency();
}
}
Let's say that DependencyService instance creation takes a lot of memory and time. In terms of performace there would be 2 groups.
Best performance
DependencyService class instance is never created
- doSomethingLazyAction
Worst performance
DependencyService class instance is always created. It's needed in doSomethingWithOtherServiceLazyAction and doSomethingWithOtherServiceAction, but not needed in doSomethingAction and still created
- doSomethingAction
- doSomethingWithDependencyLazyAction
- doSomethingWithDependencyAction
Source Code
You can find source code related to this article in GitHub
Source Code
You can find tests here:
Tests Source Code
Run tests:
phpunit -c app
*Symfony2 takes time to generate cache files when running first time, so run tests at least 2 times to see real results