10
votes

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.

4
(sidenote) You should keep the order of the arguments in subclasses identical to the order in the supertype.Gordon
@Gordon You mean Session should be append instead of prepended? Can you explain why? Adding "the new information" in front seems more natural to me.dbrumann
the subtype is a specialization of the supertype. You create it like the supertype but with an additional parameter, so putting it at the end is more natural because it only adds to the supertype's ctor's signature instead of changing it completely. it makes for a more predictable API.Gordon
@Gordon this is something I've always found strange, if you have two parameters that have default values, but you want to add a third parameter that doesn't have a default value... the logical place to put it is before the other twoJamesHalsall
All right! Now I understand why they made Laraveljcarlosweb

4 Answers

3
votes

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
    }
}
1
votes

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.

0
votes

Well you could use singleton for the Session class and access it's instance anywhere, but that might not be an option if you want to restrict it's usage. But then again Session::getInstance()->getStuff seem's pretty much the same as $this->session->getStuff so to answer your question - I don't think there's much of a choice here and you approach seems fine.

Oh and like Gordon said: you shouldn't change the order of the arguments.

0
votes

I went through the same problem while developing my Abstract Controller Bundle — when controllers are defined as services — and ended up using the setter injection in the base class.