Magento 2 – Injecting Dependencies into a CRUD/Abstract Model

dependency-injectionmagento2PHP

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?

Best Answer

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.