Symfony bundle for trait injection.
FermioTraitInjectionBundle is only supported on PHP 5.4 and up.
Add FermioTraitInjectionBundle in your composer.json:
{
"require": {
"fermio/trait-injection-bundle": "1.0.*@dev"
}
}
Now tell composer to download the bundle by running the command:
$ php composer.phar update fermio/trait-injection-bundle
Composer will install the bundle to your project's vendor/fermio
directory.
Enable the bundle in the kernel:
<?php
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Fermio\Bundle\TraitInjectionBundle\FermioTraitInjectionBundle(),
);
}
Add the following configuration to your config.yml
file according to which
services you want to be injected for a specific trait.
# app/config/config.yml
fermio_trait_injection:
traits:
container:
# the FQCN of the trait a class has to use to trigger injection
trait: Fermio\Bundle\TraitInjectionBundle\Traits\ContainerAware
method: setContainer # the method to call for service injection
service: service_container # the id of the service to be injected
By default, all services implementing a specified trait will have a method call added to their definition, except:
- the service is defined to be excluded in the configuration (see
excludes
) - the service is synthetic (runtime injection, container cannot build it)
- the service has already an identical method call configured
The bundle ships with some traits useful for every Symfony application. Take a
look in the Traits/
directory for a comprehensive list of available traits.
Feel free to provide more useful traits via pull requests.
Besides the trait injection there are some options to gain a coarser-grained control over the bundle.
The bundle may use its default trait configuration - coordinated with the Symfony Standard Edition service names. Each trait shipped by the bundle will be configured and ready to use out of the box.
# app/config/config.yml
fermio_trait_injection:
defaults: true
traits:
fermio.doctrine_aware: # add DoctrineAware if you use Doctrine
trait: Fermio\Bundle\TraitInjectionBundle\Traits\DoctrineAware
method: setDoctrine
service: doctrine # add SecurityAclProviderAware if you enabled ACL
fermio.security_acl_provider_aware:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\SecurityAclProviderAware
method: setAclProvider
service: security.acl.provider
BEWARE: The
DoctrineAware
andSecurityAclProviderAware
traits are not configured by default.
You can override each trait configuration at will, just use the same name for the trait configuration - it's enough to only provide the settings you want to override:
# app/config/config.yml
fermio_trait_injection:
defaults: true
traits:
# the invalid default value is 'exception'
fermio.container_aware: { invalid: ignore }
If simple service injection is not enough, you may want to use the advanced traits library. It's completely compatible with this bundle out of the box and replaces the traits of with more advanced ones, providing helper methods considering the Symfony Component interfaces.
If you want to disable the trait injection for specific services (e.g. inject
another implementation) you can do that by simply adding the service id to the
excludes
configuration:
# app/config/config.yml
fermio_trait_injection:
excludes: ['my.service.id', 'my.other.service.id', ]
The dependency injection container itself can handle invalid service reference behaviors. The bundle considers same functionality.
Throws an exception if the referenced service does not exist. This is the default behavior.
# app/config/config.yml
fermio_trait_injection:
traits:
container:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\ContainerAware
method: setContainer
service: service_container
invalid: exception # default
Ignores the trait injection completely if the referenced service does not exist.
# app/config/config.yml
fermio_trait_injection:
traits:
translator:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\TranslatorAware
method: setTranslator
service: translator
invalid: ignore
Injects null
if the referenced service does not exist (for optional
dependencies, e.g. like services that are not available in all environments).
# app/config/config.yml
fermio_trait_injection:
traits:
logger:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\LoggerAware
method: setLogger
service: logger
invalid: null
If you're not familiar with automatic dependency injection please read this article about interface injection first. This feature is gone for several reasons and I do not want to argue about that (I liked it in the first place). Since PHP5.4 we have traits and the goal of this bundle is to solve the same problems like the interface injection once solved: keep service configuration simple and don't repeat yourself.
So here's a very basic example on how to use the bundle in the wild. Let there be a small controller with an action that needs several services. We might inject them via the constructor or via setters, the latter beeing somewhat useless because we do not have any optional dependency. We would usually prefer the constructor injection.
<?php
// App/Controller/SomeController.php
namespace App\Controller;
use App\Events\SomeEvent;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
class SomeController
{
// copy/paste starts
private $doctrine;
private $eventDispatcher;
private $urlGenerator;
private $securityContext;
public function __construct(RegistryInterface $doctrine, EventDispatcherInterface $eventDispatcher, UrlGeneratorInterface $urlGenerator, SecurityContextInterface $securityContext)
{
$this->doctrine = $doctrine;
$this->eventDispatcher = $eventDispatcher;
$this->urlGenerator = $urlGenerator;
$this->securityContext = $securityContext;
}
// copy/paste ends
public function someAction($id)
{
if (!$this->securityContext->isGranted('ROLE_ANY')) {
return new RedirectResponse($this->urlGenerator->generate('homepage'));
}
$entity = $this->doctrine->getEntityManager('Some:Entity')->getRepository('Some:Entity')->find($id);
$this->eventDispatcher->dispatch('some.event', new SomeEvent($entity));
return ['entity' = $entity];
}
}
Constructor injection would be configured as followed:
# App/Resources/config/services.yml
services:
some.controller:
class: App\Controller\SomeController
# you shall not forget the constructor arguments,
# service names, and correct sequence of order :)
arguments:
- '@doctrine'
- '@event_dispatcher'
- '@router'
- '@security.context'
<?php
// App/Controller/SomeController.php
namespace App\Controller;
use App\Events\SomeEvent;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
class SomeController
{
// copy/paste starts
private $doctrine;
private $eventDispatcher;
private $urlGenerator;
private $securityContext;
public function setDoctrine(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
public function setUrlGenereator(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function setSecurityContext(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
// copy/paste ends
public function someAction($id)
{
if (!$this->securityContext->isGranted('ROLE_ANY')) {
return new RedirectResponse($this->urlGenerator->generate('homepage'));
}
$entity = $this->doctrine->getEntityManager('Some:Entity')->getRepository('Some:Entity')->find($id);
$this->eventDispatcher->dispatch('some.event', new SomeEvent($entity));
return ['entity' = $entity];
}
}
Setter injection would be configured as followed:
# App/Resources/config/services.yml
services:
some.controller:
class: App\Controller\SomeController
calls: # you shall not forget a method and service name
- { method: setDoctrine, arguments: ['@doctrine'] }
- { method: setEventDispatcher, arguments: ['@event_dispatcher'] }
- { method: setUrlGenerator, arguments: ['@router'] }
- { method: setSecurityContext, arguments: ['@security.context'] }
<?php
// App/Controller/SomeController.php
namespace App\Controller;
use App\Events\SomeEvent;
use Fermio\Bundle\FermioTraitInjectionBundle\Traits;
use Symfony\Component\HttpFoundation\RedirectResponse;
class SomeController
{
use Traits\DoctrineAware;
use Traits\EventDispatcherAware;
use Traits\RouterAware;
use Traits\SecurityContextAware;
public function someAction($id)
{
if (!$this->getSecurityContext()->isGranted('ROLE_ANY')) {
return new RedirectResponse($this->getRouter()->generate('homepage'));
}
$entity = $this->getDoctrine()->getEntityManager('Some:Entity')->getRepository('Some:Entity')->find($id);
$this->getEventDispatcher()->dispatch('some.event', new SomeEvent($entity));
return ['entity' = $entity];
}
}
Instead of using constructor injection or setter injection we safely can rely on the trait injection (which indeed is a setter injection, but once configured it works for all services the same without configuring it again, again and again). It also saves a lot of time in the long run because we must implement common and shared functionality in traits only once and can re-use them everywhere.
# app/config/config.yml
fermio_trait_injection:
traits:
doctrine:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\DoctrineAware
method: setDoctrine
service: doctrine
event_dispatcher:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\EventDispatcherAware
method: setEventDispatcher
service: event_dispatcher
router:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\RouterAware
method: setRouter
service: router
security_context:
trait: Fermio\Bundle\TraitInjectionBundle\Traits\SecurityContextAware
method: setSecurityContext
service: security.context
# ... even more traits
We can skip defining constructor injection (unless we have constructor arguments) and setter injection (unless we have setter to be called additionally). The bundle will add the correct method calls to the service configuration.
# App/Resources/config/services.yml
services:
some_controller:
class: App\Controller\SomeController
# you shall concentrate on the code!