8

Do traits actually work with dependency injection in Magento? Consider the following code:

Trait Class

namespace Frame\Slick\Block;
use Frame\Slider\Slick\Block\Data as Helper

trait Slick
{
   protected $_slickHelper;
   public function __construct(Helper $slickHelper) 
   {
     $this->_slickHelper = $slickHelper;
   }
}

Class using the trait

namespace Frame\Slick\Block;

class Product ListProduct implements BlockInterface 
{
   use Slick;
   public function testTrait()
   {
      return $this->_slickHelper->getHelloWorld();
   }
}

This seems to always return null, am very sure everything is being included properly. Can trait really support dependency injection?

EDIT: For example if you do a di in the trait constructor and assign it to a trait variable and then called it on the class which uses the trait, it will always return null. Anything else works fine.

  • Just one question... does "testTrait()" return null or "$this->_slickHelper" is null? – Phoenix128_RiccardoT Aug 12 '16 at 9:56
  • $this->_slickHelper returns null, other methods in the trait work only di's assigned to trait variables don't work. – André Ferraz Aug 12 '16 at 10:00
  • 1
    Good question. I assume, Magento uses Reflection to inspect the constructor arguments and this works well with traits: 3v4l.org/jbVTU - but I would have to take a closer look at the code generation to verify it. – Fabian Schmengler Aug 12 '16 at 10:01
  • but why do you want to use traits? Can you give a real life example? Maybe there is a simpler way around it – Marius Aug 17 '16 at 6:33
  • @Marius I created this module which acts as a slider for CMS Blocks, Cross sells, Products (of a specific category) and up sells. Each one of these block class extends another class for example products extends Magento\Catalog\Block\Product\ListProduct. Really the reason why I am using traits is because it solves the PHP single inheritance architecture "problem". This way there is less code repetition. – André Ferraz Aug 17 '16 at 8:06
2

I have tested using trait and it works fine.

Here is what my trait looks like:

<?php

namespace ProjectName\ModuleName\Controller\Adminhtml;

use Magento\Backend\App\Action\Context;
use ProjectName\ModuleName\Model\ResourceModel\Distributor\CollectionFactory as DistributorCollectionFactory;

trait DistributorTrait
{
    protected $distributorCollectionFactory;

    public function __construct(
        Context $context,
        DistributorCollectionFactory $distributorCollectionFactory
    )
    {
        parent::__construct($context);

        $this->distributorCollectionFactory = $distributorCollectionFactory;
    }
}

I use it in controller like this:

<?php

namespace ProjectName\ModuleName\Controller\Adminhtml\Distributor;

use Magento\Backend\App\Action;
use ProjectName\ModuleName\Controller\Adminhtml\DistributorTrait;

class Index extends Action
{
    use DistributorTrait;

    public function execute()
    {
        dump($this->distributorCollectionFactory->create()->getItems());exit;
    }
}

And here is the result:

Trait Test Result

0

I was just facing this myself. The original post is pretty old so things may be different now than when it was posted, however what I found is that the constructor DI does work but it has a pretty big caveat.

If I use the following Trait in my code:

<?php

namespace My\Module\Util;

use Psr\Log\LoggerInterface;

trait LoggerTrait
{
    protected $logger;

    public function __construct(
        LoggerInterface $logger
    ) {
        $this->logger = $logger;
    }

    /**
     * @return Logger
     */
    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * @param Logger $logger
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;
    }
}

and then proceed to use that trait in a class:

<?php

namespace My\Module;

use \My\Module\Util\LoggerTrait;

class Service
{
    use LoggerTrait;

    public function doSomething() {
        $this->getLogger()->log('Something was done!');
    }
}

The logger interface is injected perfectly and everything works fine. HOWEVER, if I want to inject my own classes into my Service class using the constructor method. E.g:

<?php

namespace My\Module;

use \My\Module\Util\LoggerTrait;


class Service
{
    use LoggerTrait;

    public function __construct(
         \Some\Other\Class $class
    ) {
        $this->other = $class;
    }


    public function doSomething() {
        $this->getLogger()->log('Something was done!');
    }
}

In this case the constructor method of my trait is never called, meaning the $logger property of my class is never set. Admittedly I haven't used traits much so my knowledge is kind of limited, but my assumption is that this is because my class has overridden the constructor method of my trait. This is pretty much a show stopper as most of the Magento code base uses constructors to inject dependancies, affectively ruling out their use in traits.

The only real solution I can see to this is to use the ObjectManager directly to inject your trait dependancies:

<?php

namespace My\Module\Util;

use Psr\Log\LoggerInterface;

trait LoggerTrait
{
    protected $logger;


    /**
     * @return Logger
     */
    public function getLogger()
    {
        if (is_null($this->logger)) {
            $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
            $this->logger = $objectManager->create('Psr\Log\LoggerInterface');
        }
        return $this->logger;
    }

    /**
     * @param Logger $logger
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;
    }
}

Disclaimer: The use of ObjectManager in Magento is generally discouraged but from what I can see in this case its the only real option. In my example if you wanted to set a different logger interface in your class you can still do that by injecting it in your constructor and overriding the classes $logger property.

  • In your class, you have declared 2 __construct, which is one imported from trait, and the other in the class itself. However, you cannot have 2 methods with same name in single class. So basically in your case, __construct in the trait is overridden by __construct in the class itself. – Rendy Eko Prastiyo Nov 22 '18 at 4:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.