12

Is it possible to inject a dependency into a Magento 2 CRUD model?

That is -- Magento 2 has a base abstract model class: Magento\Framework\Model\AbstractModel. If you want to create a simple Create, Read, Update, Delete model object, you extend this class with your own class.

class Foo extends Magento\Framework\Model\AbstractModel
{
}

Is it possible to have injected dependencies in your model's __construct method? When I try, I end up getting the following error.

Fatal error: Cannot instantiate abstract class Magento\Framework\Model\ResourceModel\AbstractResource

The culprit seems to be the AbstractModel's __construct method.

public function __construct(
    \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
    \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
    array $data = []
) {

There are two type hints in this constructor (Magento\Framework\Model\ResourceModel\AbstractResource, Magento\Framework\Data\Collection\AbstractDb) that are not Magento object manager interfaces. They're abstract classes. When I extend this class and try to add my injected dependency

class Foo extends Magento\Framework\Model\AbstractModel
{
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        array $data = [],
        \Package\Module\Model\Mine $mine,

    ) {
        //...
        parent::__construct($context, $registry, $resource, $resourceCollection, $data);

    }
}

Magento bails when the object manager tries to instantiate the abstract classes.

I can "fix" this by moving my object dependency in front of the abstract classes

    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,

        \Package\Module\Model\Mine $mine,

        \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        array $data = [],
    ) {  

However, this changed the argument order. In a class that was fully object managed, this wouldn't be a problem. However, the fact that these abstract class type hints exists imply there are parts of the Magento system that will manually (i.e. not via the object manager or DI) instantiate CRUD objects and pass in objects that conform to the type hints in that specific order.

Is this safe? i.e. Are these abstract classes in an abstract model's constructor just legacy code, and not used? Or will parts of the system still use these, meaning it's not possible to inject dependencies into a CRUD model?

3 Answers 3

9

First of all the constructor is private api of class. Constructor function have special meaning and do not require have to have same list/order of arguments as in parent class.

Is it possible to inject a dependency into a Magento 2 CRUD model?

Yes of course.

Is this safe?

Yes, but Magento Object Manager assume that all optional parameters is placed at end of list and required parameters after optional will not resolved.

$resource, $resourceCollection arguments is legacy but still widely used in Model classes. Most of model use code like this to initialize resource and collection class.

protected function _construct() { 
    $this->_init('Magento\AdminNotification\Model\Resource Model\Inbox'); 
}

This is why this parameters is optional. But, for example, in unit test we pass resource or collection mock in constructor to allow replace realization.

5
  • @Kanday Has Magento's engineering/architecture department ever made a public statement that constructor order for core classes is irrelevant? Or is that just the hope of most the folks working on it? Commented Jan 12, 2016 at 0:04
  • I will do not call it "irrelevant". Just OM will pass required arguments to your constructor and it don't depends on the order in parent class. Moreover, IN use names of parameters, so now it's better to do not change their (it's different from php language, where you can change names of parameters as you want )
    – KAndy
    Commented Jan 12, 2016 at 7:22
  • I'm not sure I understand what you're saying. Are you saying that, at some point in the future, the core Magento system code might start treating argument/parameter order as significant again? Commented Jan 12, 2016 at 17:03
  • I believe that no
    – KAndy
    Commented Jan 15, 2016 at 19:17
  • thanks again! FWIW, and for the Googlers, this seems like it should be a safe thing to do. From what I can tell there's no Magento system code that automatically blindly instantiates a model assuming constructor parameter order. Commented Jan 15, 2016 at 19:36
6

This appears to be safe. At least, magento is doing this in a number of places. See the __construct methods in the following (non exclusive) list of classes for examples

  • \Magento\Theme\Model\Theme\File
  • \Magento\Theme\Model\Design
  • \Magento\Sales\Model\Order\Creditmemo

Unfortunately, I can't answer the other part of your question.

4
  1. How do you use your model?
  2. In your case $mine is a required parameter, while $resource, $resourceCollection and $data are optional. Optional parameters should always go last, otherwise it's just impossible to work with them as with optional. So it looks OK to me that you should specify $mine before any optional parameters.
4
  • Except those Abstract parameters aren't dependency injected parameters, and if Magento core system code expects them to be there moving $mine to the front of the queue will create errors. If Magento core system code doesn't use them then why are they there? That's the question I'm trying to get to the bottom of. Just because I'm able to use my model with the parameter moved doesn't make it safe. Commented Jan 11, 2016 at 20:39
  • Some models may still use these optional parameters to pass a custom resource model. For example, github.com/magento/magento2/blob/develop/app/code/Magento/…
    – BuskaMuza
    Commented Jan 11, 2016 at 20:54
  • Magento uses reflection to determine whether the parameter is optional or not. And PHP considers all parameters standing before required parameter as required. So, if you move $mine before optional parameters, they become really optional and Magento just passes default values (null, array()). If you put a required parameter after optional ones, PHP considers optional parameters as required ones and Magento tried to instantiate them (but there are no preferences for them).
    – BuskaMuza
    Commented Jan 11, 2016 at 21:00
  • Though I agree that it looks confusing and maybe we could just setup a preference for the abstract classes instead of handling it inside the model class. So a real object is always injected.
    – BuskaMuza
    Commented Jan 11, 2016 at 21:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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