15

Because it was hard for me to find the right way, below you could find the best practice I made mine. Enjoy, correct my English if needed and say me I'm wrong if I am. :)

Edit : ... and I found out I was wrong on some aspect. So I updated the original post after Raphael's answers helped me to understand more. Thanks to him !

Concept used below :

It will be easier for you to understand codes and explanations below if you are comfortable with these concepts :

  • Injection Dependency (as every $this->variable variables in codes are injected)
  • Service Contract and Repository
  • Factory

Context :

Just to have more context, imagine we have a module correctly construct with :

  • a block class CustomBlock containing a method getCustomModel($id),
  • this method returns a CustomModel object based on the id passed in param,
  • CustomModel type correspond to the model in \Vendor\Module\Model\CustomModel
  • This model comes with its resource model (in \Vendor\Module\Model\ResourceModel\CustomModel)
  • and with its repository (in \Vendor\Module\Model\CustomModelRepository).

Question :

  • What's the best practice to let the all thing loading a CustomModel object ?

You can't use the load() from a CustomModel object since this method is deprecated.

The good practice says that you have to use the CustomModel Service Contract. Service contracts are data interfaces (e.g. CustomModelInterface) and service interfaces (e.g. CustomModelRepositoryInterface). So my block looks like this :

/** @var SlideRepositoryInterface  */
protected $slideRepository;

/**
 * CustomBlock constructor
 * ...
 * @param CustomModelRepositoryInterface $customModelRepository
 * ...
 */
public function __construct(
...
CustomModelRepositoryInterface $customModelRepository
...
) {
    $this->customModelRepository = $customModelRepository;
}

public function getCustomModel($id) {
    return $this->customModelRepository->get($id);
}

First of all, we inject the CustomModelRepositoryInterface object in the constructor and we use it in our getCustomModel() method.

In the class Api\CustomModelRepositoryInterface there is not a lot. Generally (but nothing prevent you to do differently) you will declare basic methods : get, getList, save, delete, deleteById. For the purpose of this topic, below is just the get method declaration :

/**
 * Get info by id
 *
 * @param int $id
 * @return Data\CustomModelInterface
 * @throws \Magento\Framework\Exception\NoSuchEntityException
 */
public function get($id);

Ok, but if my CustomModel Interface is called by dependency injection in my block constructor, where is the code ? For answer to this question you have to explain to Magento where find the class implementing this interface. In the etc/di.xml file of the module, you have to add :

<preference for="Vendor\Module\Api\CustomModelRepositoryInterface" type="Vendor\Module\Model\CustomModelRepository" />

So CustomModelRepositoryInterface class is a service interface. In implementing it you will have to implementing also data interfaces (at least Vendor\Module\Api\Data\CustomModelInterface and Vendor\Module\Api\Data\CustomModelSearchResultsInterface). Your model will have to implement Vendor\Module\Api\Data\CustomModelInterface and add <preference ... /> lines for each one of your interfaces. Finally at anytime you use service contract, think in mySomethingInterface not anymore in mySomething : let magento use the di.xml preferences mechanism.

Ok, what comes next ? As we inject CustomModelRepositoryInterface in the block constructor, we get an CustomModelRepository object. CustomModelRepository has to implement the method declare in CustomModelRepositoryInterface. So we have this in Vendor\Module\Model\CustomModelRepository :

public function get($id) {
    $customModel = $this->customModelFactory->create();
    $customModel->load($id);
    if (!$customModel->getId()) {
      throw new NoSuchEntityException(__('CustomModel with id "%1" does not exist.', $id));
    }
    return $customModel;
}

What we are doing ? We create an empty CustomModel object thanks to the factory. Next we load data in the CustomModel using the load model method. Next we return a NoSuchEntityException if we failed to load the CustomModel with the id in params. But if everything's ok, we return the model object and life continue.

But wow...! In this example what is that ?

$customModel->load($id);

Isn't the same deprecated load method than at the beginning ? Yes, it is. I think it's a shame, but you have to use it since in this load() method there are some events dispatched and developer could listening for them (see Raphael's answer below).

In future, we will be save by Entity Manager. It's another story as a new Magento 2 concept, but if you want to drop an eye on, Entity Manager is already implemented in the Resource Model of CMS Page (v2.1) :

public function load(AbstractModel $object, $value, $field = null)
{
    $pageId = $this->getPageId($object, $value, $field);
    if ($pageId) {
        $this->entityManager->load($object, $pageId);
    }
    return $this;
}
15

Best practice: via the service contract

The best practice is always to use the service contract whenever it's possible. You can find the list of reasons here: Magento 2: what are the benefits of using service contracts?

For details on how to implement a service contract I suggest you check this topic: How to implement service contract for a custom module in Magento 2?

If no service contract available

If there is no service contract available, you should use the model repository get method. Using this method, you benefit from the magento caching system for example for the CategoryRepository class:

public function get($categoryId, $storeId = null)
{
    $cacheKey = null !== $storeId ? $storeId : 'all';
    if (!isset($this->instances[$categoryId][$cacheKey])) {
        /** @var Category $category */
        $category = $this->categoryFactory->create();
        if (null !== $storeId) {
            $category->setStoreId($storeId);
        }
        $category->load($categoryId);
        if (!$category->getId()) {
            throw NoSuchEntityException::singleField('id', $categoryId);
        }
        $this->instances[$categoryId][$cacheKey] = $category;
    }
    return $this->instances[$categoryId][$cacheKey];
}

Deprecated load() method

Magento 2 is slowly moving away from the standard CRUD system by dropping the inheritance system and implementing it via composition using the new 2.1 EntityManager you can find details here: Magento 2.1: using the entity manager

Also I suggest you read this interesting topic about the deprecated CRUD methods: Deprecated save and load methods in Abstract Model

Why not using the resource model load

The main reason is that if you use the resource model load method, you will skip some important part of the loading system that are implemented in the model load method, see Magento\Framework\Model\AbstractModel :

public function load($modelId, $field = null)
{
    $this->_beforeLoad($modelId, $field);
    $this->_getResource()->load($this, $modelId, $field);
    $this->_afterLoad();
    $this->setOrigData();
    $this->_hasDataChanges = false;
    $this->updateStoredData();
    return $this;
}

Calling the resource model load method directly will have the following impact:

  • _beforeLoad is not called: thus the model load before events are not dispatched
  • _afterLoad is not called: thus the model load after events are not dispatched
  • the stored data are not updated which can cause various problems (for instance if you call prepareDataForUpdate from Magento\Framework\Model\ResourceModel\Db\AbstractDb )
  • Thanks Raphael, every things you say makes sense and complete my knowledge. But I don't understand why KAndy comments (under his answer) that Marius can use the load() method of his custom module resource model ? It's in [magento.stackexchange.com/questions/114929/… save and load methods in Abstract Model). Any ideas ? – Nicolas PERNOT Oct 27 '16 at 6:42
  • @NicolasPERNOT basically KAndy explains that the goal is to have SL (Service Layer) for every module and that this is what must be used every time you need to load an entity. I suggest you comment by mentioning him maybe he'll be able to enlighten you as he's a Magento Inc employee I think – Raphael at Digital Pianism Oct 27 '16 at 7:01
  • Well, I finally updated my original post. Thank you Raphael for your help. – Nicolas PERNOT Nov 8 '16 at 13:21
  • I see that at least in Magento 2.2 that important is included into ResourceModel's load, so it's not OK to use ResourceModel methods directly, right? – Jānis Elmeris Nov 16 '17 at 8:49
-1

I think following statement is not valid now.

Why not using the resource model load

we can find Magento\Framework\EntityManager\Observer folder all events.

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.