The theme has been already discussed in the article “Database in Magento 2: Models, Resource Models, and Collections”. Now we’re going to extend the topic and add more details in order to provide you with an advanced information.
Basic principles
Magento ORM consists of three main entities: Models, Resource models, and Collections. Models contain entity-specific data operations and logic. Resource models contain mappings and database fetching/saving code while collections are responsible for group operations.
In order to create a model, you have to extend \Magento\Framework\Model\AbstractModel class and override _construct method where you’ll have to pass Resource Model to _init method call.
1 2 3 4 |
protected function _construct() { $this->_init(\Magento\Sales\Model\ResourceModel\Order::class); } |
Note that this is a single-underscore construct method. This method is a legacy code from Magento 1 and it is called in real _construct method of \Magento\Framework\Model\AbstractModel.
In order to create a Resource Model, you need to extend \Magento\Framework\Model\ResourceModel\Db\AbstractDb class and override again single-underscore _construct method, but now you need to pass table name and id field to _init call.
1 2 3 4 |
protected function _construct() { $this->_init('sales_order', 'entity_id'); } |
Collections can be created by extending \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection class. Here again overridden _construct method should pass Model and Resource model class names to _init method.
1 2 3 4 |
<strong>protected function </strong>_construct() { this->_init(\Magento\Sales\Model\Order::class, \Magento\Sales\Model\ResourceModel\Order::class); } |
Configuring database connection
Magento 2 database connection settings can be found in app/etc/env.php file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php return array ( 'db' => array ( 'table_prefix' => '', 'connection' => array ( 'default' => array ( 'host' => 'localhost', 'dbname' => 'dbname', 'username' => 'username', 'password' => 'password', 'active' => '1', ), ), ), ); |
Saving/loading process
For basic entities saving and loading processes are implemented in Resource model’s \Magento\Framework\Model\ResourceModel\Db\AbstractDb class.
Loading Entities
\Magento\Framework\Model\ResourceModel\Db\AbstractDb::load is responsible for fetching model data from storage and returning model with hydrated data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null) { $object->beforeLoad($value, $field); if ($field === null) { $field = $this->getIdFieldName(); } $connection = $this->getConnection(); if ($connection && $value !== null) { $select = $this->_getLoadSelect($field, $value, $object); $data = $connection->fetchRow($select); if ($data) { $object->setData($data); } } $this->unserializeFields($object); $this->_afterLoad($object); $object->afterLoad(); $object->setOrigData(); $object->setHasDataChanges(false); return $this; } |
This method receives model object, value and field variables, where field and value are database column names and it’s value by which model should be fetched. If field name is not specified, then id field from this Resource model will be used.
First load method performs any “before load” operations which may or may not be implemented for this specific model by calling beforLoad method.
Then it gets current storage connection and when connection and search values are set, then it gets instance of \Magento\Framework\DB\Select class based on desired database column, search value and model object by calling \Magento\Framework\Model\ResourceModel\Db\AbstractDb::_getLoadSelect method and it fetches first found row from this selection.
If record is found, then data is set to our model object.
1 2 3 |
if ($data) { $object->setData($data); } |
In the end load method unserializes any serialized fields, calls after load methods from both model and resource model, set original data to model and sets hasDataChanges flag to false.
Saving Entities
\Magento\Framework\Model\ResourceModel\Db\AbstractDb::save method is responsible for persisting model’s data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
public function save(\Magento\Framework\Model\AbstractModel $object) { if ($object->isDeleted()) { return $this->delete($object); } $this->beginTransaction(); try { if (!$this->isModified($object)) { $this->processNotModifiedSave($object); $this->commit(); $object->setHasDataChanges(false); return $this; } $object->validateBeforeSave(); $object->beforeSave(); if ($object->isSaveAllowed()) { $this->_serializeFields($object); $this->_beforeSave($object); $this->_checkUnique($object); $this->objectRelationProcessor->validateDataIntegrity($this->getMainTable(), $object->getData()); if ($this->isObjectNotNew($object)) { $this->updateObject($object); } else { $this->saveNewObject($object); } $this->unserializeFields($object); $this->processAfterSaves($object); } $this->addCommitCallback([$object, 'afterCommitCallback'])->commit(); $object->setHasDataChanges(false); } catch (DuplicateException $e) { $this->rollBack(); $object->setHasDataChanges(true); throw new AlreadyExistsException(new Phrase('Unique constraint violation found'), $e); } catch (\Exception $e) { $this->rollBack(); $object->setHasDataChanges(true); throw $e; } return $this; } |
First thing here is to check if our model should be deleted instead of updated. In this case, \Magento\Framework\Model\ResourceModel\Db\AbstractDb::delete method is called. After starting a transaction by calling \Magento\Framework\Model\ResourceModel\AbstractResource::beginTransaction method, our method checks if there was any modification to the model’s data.
1 2 3 4 5 6 |
if (!$this->isModified($object)) { $this->processNotModifiedSave($object); $this->commit(); $object->setHasDataChanges(false); return $this; } |
And if there were no modifications, then \Magento\Framework\Model\ResourceModel\Db\AbstractDb::processNotModifiedSave is called.
In \Magento\Framework\Model\ResourceModel\Db\AbstractDb class’s implementation this method just returns $this and does not perform any work, so if our business logic requires any events to fire in this case then we should override this method in our resource model.
But in most cases where model’s data was actually changed, our method runs model validation, any “before save” logic and checks if our model should be updated or this is a new object and new database record should be created. This can be done by calling \Magento\Framework\Model\ResourceModel\Db\AbstractDb::isObjectNotNew method which checks if our object has id field set (new objects don’t usually have id field set). Depending on the outcome \Magento\Framework\Model\ResourceModel\Db\AbstractDb::updateObject or \Magento\Framework\Model\ResourceModel\Db\AbstractDb::saveNewObject methods are called.
After saving or updating database save method calls any “after save” login and closes the transaction.