8

I have quite a lot of experience with Magento but I realized that I don't understand which way of loading a model is the correct one and why. I've read everything that I could, about the topic but people explaining stuff like this never actually go deep enough to explain, why use this specific method instead of another. Let's assume there is no repository for the model I want to load.

Up untill now I was always using model in constructor and then simply load it.

public function __construct(
    \Vendor\Module\Model\Something $somethingModel
) {
    $this->somethingModel = $somethingModel;
}

public function getTestById($id) {
    return $this->somethingModel->load($id);
}

And it always worked as intended, I'm also pretty sure it's or at least was used commonly in core.

But then I saw one of my colleagues using

modelFactory->create()->load($id)

As far as I understand factories are being used to create a new entity, for example, if I wanted to create a new product then I can create the factory, populate it with data and then save it. But then again, I started researching the topic and I saw example from Fabian Schmengler (When Should We Use a Repository and Factory in Magento 2?) who was loading the model this way and also discouraged others from simply loading the models, he didn't explain why though besides saying that it's 'not part of the service contract'. As far as I understand repositories are part of service contracts so I don't see any connection here when it comes to loading models that are not available through a repository.

To add some more confusion I've also found a way of loading the model by getting the resourceModel from created modelFactory, it was presented by Vinai Kopp (How to implement service contract for a custom module in Magento 2?) and now I'm completely lost as I've always read that I shouldn't use resource models directly.

So yeah, could somebody tell me which is the correct way and why I should use it instead of all of the other methods?

  • I'm literally linking this thread as containing confusing example, did you even read my post? – czs Oct 3 '17 at 15:52
  • 1
    Good question, I'll try to find time to answer in detail later. I can already tell you that much: it's a different case if you load your own models (example by Vinai) or models of the core or third party modules (my answer). Also, injecting the model via constructor will give you the same instance every time, which may lead to unwanted side effects. – Fabian Schmengler Oct 10 '17 at 6:33
10

Well, the first step you should check for the model in question is: Is there a Repository Service Contract? If so, use that, because Service Contracts are bound to semantic versioning and will continue to behave like they should up until Magento 3.x comes out. Needless to say, when you create your own modules with models that require persistence, you should also write the repository for that.

public function __construct(
    \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {
    $this->productRepository = $productRepository;
    /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
    $this->productRepository->save($product);
}

If there is no repository present, use the resource model. Note that resource models don't contain a state: they're utilizing persistence for their 'regular' models. Therefore you are not required to include them using a factory:

public function __construct(
    \Magento\Catalog\Model\ResourceModel\Product $productResource,
    \Magento\Catalog\Model\ProductFactory $productFactory
) {
    $this->productResource = $productResource;
    $this->productFactory = $productFactory;
    ...
    /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
    $product = $this->productFactory->create();
    $this->productResource->save($product);
}

"So what benefit brings a Service Contract / Repository over a Resource Model?" you might ask. Well, in theory a Resource Model should only be responsible for the persistence of a Data Model, whereas a Repository also takes into account the additional tasks involved upon saving an entity. Think about updating indexes, creating relations with other entities, etc. This is the theory, although in real life these lines tend to blur quite often. But it's good for yourself to keep this in mind.

You should not use the models' direct save(), load(), etc. -methods. They are deprecated because it's semantic incorrect. Think about it in a SOLID way:

  • (Data) Models should only be responsible for containing data.
  • Resource Models should be responsible for the persistence of such data.
  • Repositories should be responsible for the communication inside and outside the module for persistence actions.

And it's that last point that makes a difference: when communicating with other modules, in an ideal world one should never have to rely on that modules' internal persistent logic (or any of it's public methods for that matter, but that's another discussion), but only use that functionality that is provided by the modules' Service Contracts.

In Conclusion

To answer your question: in order of preference. The correct way to load a model is:

  • If there is a Repository, load it using the Repository.
  • Only if there is no Repository, use the Resource Model (in combination with a factory).
  • 1
    Ok, so if I follow correctly - when I want to modify/ add new data and save it to the database then I should use Resource Model and wen I want to load data to memory then I should use Factory? So is there any situation in which I should use regular Model directly (as in using a Model class in constructor)? – czs Oct 4 '17 at 9:10
  • @czs You are correct I have added a more descriptive example for model loading for the same. – Milind Singh May 1 at 8:07
2
  • Models are Data Interface are used to only hold the data in objects, i.e to set and get data for a row.
  • ResourceModels are a mechanism which is responsible for the persistence of such data, i.e execute the SQL query to actually save or load data into the Model object.

The correct way to load and save should be by creating a repository or loading from a resource as follows:

namespace MyVendor\MyModule\Model;

class QueueRepository impliments \MyVendor\MyModule\Api\QueueRepositoryInterface
{

    /** @var \MyVendor\MyModule\Model\ResourceModel\Queue  */
    public $resource;

    /** @var \MyVendor\MyModule\Model\QueueFactory  */
    public $modelFactory;

    public function __construct(
        \MyVendor\MyModule\Model\ResourceModel\Queue $resource,
        \MyVendor\MyModule\Model\QueueFactory $modelFactory
    ) {
        $this->resource = $resource;
        $this->modelFactory = $modelFactory;
    }

    /**
     * Save
     * @param \MyVendor\MyModule\Api\Data\QueueInterface $queue
     * @return $queue
     * @throws \Exception
     */
    public function save(\MyVendor\Integrator\Api\Data\QueueInterface $queue)
    {
        $this->resource->save($queue);
        return $queue;
    }

    /**
     * Save
     * @param \MyVendor\MyModule\Api\Data\QueueInterface $queue
     * @param int $id
     * @return $queue
     * @throws \Exception
     */
    public function load(\MyVendor\MyModule\Api\Data\QueueInterface $queue, $id)
    {
        $this->resource->load($queue, $id);
        return $queue;
    }

    public function getById($id)
    {
        $queue = $this->modelFactory->create();
        $this->resource->load($queue, $id);
        return $queue;
    }
}

Here, \MyVendor\MyModule\Api\Data\QueueInterface is implimented by Queue Model.

So, behind the scenes, we are actually creating an Model object then loading it by the ResourceModel object. This is the correct way to load or save.

        $queue = $this->modelFactory->create();
        $this->resource->load($queue, $id);
        return $queue;

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.