Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor-Injection when extending a class

This question is not strictly related to Symfony 2, but as I use Symfony 2 components and will later likely use a Symfony\Component\DependencyInjection\Container as DI-Container, it might be relevant.

I am currently building a small library using components from Symfony 2, e.g. HttpFoundation, Validator, Yaml. My Domain Services are all extending a basic AbstractService providing nothing but Doctrine\ORM\EntityManager and Symfony\Component\Validator\Validator via Constructor-Injection like this:

abstract class AbstractService
{
    protected $em;

    protected $validator;

    /**
     * @param Doctrine\ORM\EntityManager $em
     * @param Symfony\Component\Validator\Validator $validator
     */
    public function __construct(EntityManager $em, Validator $validator)
    {
        $this->em = $em;
        $this->validator = $validator;
    }
}

A Service-class extending this AbstractService may now need to inject additonal components, like Symfony\Component\HttpFoundation\Session. As of I do it like this:

class MyService extends AbstractService
{
    /**
     * @var Symfony\Component\HttpFoundation\Session
     */
    protected $session;

    /**
     * @param Symfony\Component\HttpFoundation\Session $session
     * @param Doctrine\ORM\EntityManager $em
     * @param Symfony\Component\Validator\Validator $validator
     */
    public function __construct(Session $session, EntityManager $em, Validator $validator)
    {
        parent::__construct($em, $validator);
        $this->session = $session;
    }
}

Is there a more elegant way to solve this without having to reiterate the parent's constructor arguments, e.g. by using Setter-Injection for Session instead?

As I see it, when I use Setter-Injection for Session, I have to add checks before accessing it in my methods, whether it is already injected, which I want to avoid. On the other hand I don't want to "repeat" injecting the basic components shared by all services.

like image 895
dbrumann Avatar asked Feb 07 '12 11:02

dbrumann


2 Answers

An alternative would be not to extend your Services from the abstract type. Change the abstract type to a real class and then inject it to your Services, e.g.

class MyService
…
    public function __construct(ServiceHelper $serviceHelper)
    {
        $this->serviceHelper = $serviceHelper;
    }
}

Yet another option would be to not pass the dependencies until they are needed, e.g.

class MyService
…
    public function somethingRequiringEntityManager($entityManager)
    {
        // do something with EntityManager and members of MyService
    }
}
like image 53
Gordon Avatar answered Sep 20 '22 23:09

Gordon


Is there a more elegant way to solve this without having to reiterate the parent's constructor arguments, e.g. by using Setter-Injection for Session instead?

Well, by using setter-injection like you've mentioned. Apart from that I see two possible solutions to your immediate problem:

  1. Make the parent constructor accept a Session object as well, but make it null by default. This to me is ugly, since the parent doesn't actually need the session object, and if you have other implementations, this will result in a long parameter list for the parent constructor, so this is actually not really an option.

  2. Pass in a more abstract object. Be it a ParameterObject for that specific type of object, or just a simple Registry, this should work. Again; not preferable and as you're using dependency injection, you probably already know why.

I do have to ask though; where's the harm in using your current way? I don't actually see the downside. I'd just go ahead and carry on. If you discover you're using even more parameters in the constructor, think about what the service's intentions are and why it needs it, chances are that particular pieces of behaviour can be moved to the objects you're requesting instead.

like image 40
Berry Langerak Avatar answered Sep 19 '22 23:09

Berry Langerak