Symfony 2: dependency injection and traits

10,336

Solution 1

I think that traits are not meant to be used to do DI in this way. What I would do in a similar scenario is using constructor injection (or even setter would be fine, even tough constructor is better when possible) in the view class that implements traits to inject directly the needed services.

If you think about that the traits implemented by a class are statically defined before the application executes, thus you don't really need to inspect traits to perform a dynamic injection. You will know what services you need before running, just think to trait as if they were interfaces with some concrete method.

Solution 2

Symfony 3.3 introduced the idea of autowired services.
All you have to do is create a setter function in your trait and add the @required annotation.

private $entityManager;

/**
 * @required
 * @param EntityManagerInterface $entityManager
 */
public function setEntityManager(EntityManagerInterface $entityManager)
{
    $this->entityManager = $entityManager;
}

Reference: https://symfony.com/doc/current/service_container/autowiring.html#autowiring-other-methods-e-g-setters

Share:
10,336
BenMorel
Author by

BenMorel

Author of Brick, a collection of open-source libraries for PHP applications: brick/math : an arbitrary-precision arithmetic library brick/money : a money and currency library brick/date-time : a date and time library brick/phonenumber : a phone number library brick/geo : a GIS geometry library brick/varexporter : a powerful alternative to var_export() ... and a few others.

Updated on June 13, 2022

Comments

  • BenMorel
    BenMorel about 2 years

    I'm trying to find a way to use the Symfony 2 Dependency Injection component with the new PHP 5.4 traits.

    To make a long story short (not so short, actually), my project has decoupled View classes that all have their own, specific constructor. Each View can use zero or more view helpers, that are defined as traits:

    trait TranslatorHelper
    {
        /**
         * @var Translator
         */
        protected $translator;
    
        /**
         * @param Translator $translator
         */
        protected function setTranslator(Translator $translator)
        {
            $this->translator = $translator;
        }
    
        /**
         * @param string $text
         * @return string
         */
        public function translate($text)
        {
            return $this->translator->translate($text);
        }
    }
    

    -

    class UserEditView extends AbstractView
    {
        use TranslatorHelper;
    
        public function __construct(User $user, UserEditForm $form)
        {
            // ...
        }
    }
    

    I'd like to have a method in my controller, renderView(), that performs setter injection based on all the traits used by the View class, before rendering the View:

    class Controller
    {
        public function renderView(View $view)
        {
            // Check what traits are used by $view, and inject their dependencies
            // {...}
    
    
            // Then render the View
            return $view->render();
        }
    }
    

    Any idea on how to do this with the DependencyInjection component?

    The main problem is obviously that the Views won't be created by the DI container, but can be created anywhere in the application flow. It's only before they're rendered that the dependencies need to be injected.

    A last note: I'm not tied to the Symfony component. Any lead on another DI container would be appreciated as well.

  • BenMorel
    BenMorel almost 12 years
    I get your point, however my concern is that I want to keep the View constructor clear of any Helper-specific dependencies (I want the controller to explicitly use the View constructor to pass only the variables needed by the view itself, without having to worry about the helpers). Plus, I will have probably hundreds of View classes, all using the same ~3 view helpers. So I'd prefer to be able to inject based on the Trait name. For example, I'd like to define in my config that all classes using the TranslatorHelper trait would use the Translator pointed to by the translator container key.
  • Aldo Stracquadanio
    Aldo Stracquadanio almost 12 years
    In that case what you could do is using DI tagging: define a tag and a compiler pass that registers does the right setter dependency for each helper trait. Some documentation about advanced DIC usage (such as comiler passes and tags) has been recently released on the Symfony documentation. It is not automatically injection based on trait (I think the only way to do that would be using reflection that is quite slow) but it is quite tidy and clean.