Magento 2 uses an Object-Relational Mapping (ORM) system to manage how application data interacts with the database.
This article explains how Magento 2 connects to the database and how the core ORM components, such as Models, Resource Models, and Collections, work together to handle CRUD operations, data retrieval, and entity management. You’ll also see how these components fit into modern Magento architecture, where Service Contracts are now the preferred layer for business logic and API-safe development.
How does Connection to Database Work in Magento 2?
Magento 2 Resource Models
Magento 2 Models
Magento 2 Collections
Conclusion
How does Connection to Database Work in Magento 2?
In Magento 2, database connection settings are contained in the app/etc/env.php file:
The path to this file is stored in \Magento\Framework\Config\File\ConfigFilePool class in a private array $applicationConfigFiles.
There is a connection chain from the resource model to the database:
- The method create() of \Magento\Framework\App\Bootstrap class is called in the index.php file. It calls the self public static method createObjectManagerFactory().
12345678public static function createObjectManagerFactory($rootDir, array $initParams){$dirList = self::createFilesystemDirectoryList($rootDir, $initParams);$driverPool = self::createFilesystemDriverPool($initParams);$configFilePool = self::createConfigFilePool();return new ObjectManagerFactory($dirList, $driverPool, $configFilePool);} - The method createObjectManagerFactory() calls the self method createConfigFilePool(), which creates a ConfigFilePool object and returns an ObjectManagerFactory object where the ConfigFilePool was set as a parameter.
(ObjectManagerFactory is an object with complex operations. A global configuration creation is one of such operations.) - All custom resource models should be inherited from \Magento\Framework\Model\ResourceModel\Db\AbstractDb class. It has a method getConnection() where \Magento\Framework\App\ResourceConnection object is called.
12345public function getConnection(){$fullResourceName = ($this->connectionName ? $this->connectionName : ResourceConnection::DEFAULT_CONNECTION);return $this->resource->getContent($fullResourceName);}
The \Magento\Framework\App\ResourceConnection contains fields and methods for operating with data from global configuration. It concerns the data from app/etc/env.php file. For example, ResourceConnection::DEFAULT_CONNECTION is default value and corresponds to [‘db’ => [‘connection’ => [‘default’ => […]]]]:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
'db' => [ 'table_prefix' => '', 'connection' => [ 'default' => [ 'host' => 'localhost', 'dbname' => 'magento', 'username' => 'root', 'password' => '', 'active' => '1' ] ] ], |
(You can create a new custom connection and set it to $connectionName in your custom resource model.)
Magento 2 Resource Models
As mentioned above, the connection to the database is implemented through the resource model. Resource Models are data mappers for the storage structure. They are used for making transactions to the database.
A resource model extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb (With Magento 2, you no longer have to declare the model, resource model, adapters, and more in the configuration. So the process is much simpler than it used to be in Magento 1).
You need to define the table name and the primary key field name for the resource model because it’s necessary to know where to save the model state. They are specified by calling the _init() method in the protected _construct() method. Note that you call _init() from a single-underscore construct method, not the real double-underscore PHP constructor. This single-underscore _construct() method is also the legacy from Magento 1 and is called by the real __construct() method inherited from \Magento\Framework\Model\ResourceModel class.
|
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 |
class Inbox extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { protected function _construct() { $this->_init('adminnotification_inbox', 'notification_id'); } …. } --------------------------------------------------------------------- abstract class AbstractDb extends AbstractResource { …. protected function _init($mainTable, $idFieldName) { $this->_setMainTable($mainTable, $idFieldName); } protected function _setMainTable($mainTable, $idFieldName = null) { $this->_mainTable = $mainTable; if (null === $idFieldName) { $idFieldName = $mainTable . '_id'; } $this->_idFieldName = $idFieldName; return $this; } … } |
All interacting processes with the database are stored in methods of resource models. They access to database tables using \Magento\Framework\DB\Adapter\Pdo\Mysql methods. It extends Zend_Db_Adapter_Pdo_Mysql class and implements Magento\Framework\DB\Adapter\AdapterInterface. Here is a mechanism of connection between these classes to resource models:
- The \Magento\Framework\DB\Adapter\Pdo\Mysql object is created in the Magento\Framework\Model\ResourceModel\Type\Db\Pdo\Mysql class in the protected method getDbConnectionClassName():
1234protected function getDbConnectionClassName(){return DB\Adapter\Pdo\Mysql::class;}
It’s called in the protected method getDbConnectionInstance(), which is called in the public method getConnection(). - The global configuration file app/etc/di.xml contains a preference:
1
So, \Magento\Framework\Model\ResourceModel\Type\Db\Pdo\Mysql is a realization of ConnectionAdapterInterface. - The ConnectionAdapterInterface is called in \Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactory in the create() method:
12345678910public function create(array $connectionConfig){/** @var \Magento\Framework\App\ResourceConnection\ConnectionAdapterInterface $adapterInstance */$adapterInstance = $this->objectManager->create(\Magento\Framework\App\ResourceConnection\ConnectionAdapterInterface::class,['config' => $connectionConfig]);return $adapterInstance->getConnection();} - The \Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactory is a realization of the \Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactoryInterface class. It is called in the \Magento\Framework\App\ResourceConnection (which is called in resource model as it was mentioned above, and used in methods for connecting to the database) in the __construct() method:
1234567891011public function __construct(ResourceConfigInterface $resourceConfig,ConnectionFactoryInterface $connectionFactory,DeploymentConfig $deploymentConfig,) {var_dump($resourceConfig);$this->config = $resourceConfig;$this->connectionFactory = $connectionFactory;$this->deploymentConfig = $deploymentConfig;$this->tablePrefix = $tablePrefix ?: null;}
All transactions, operations with foreign keys, indexes, columns, and tables are implemented in \Magento\Framework\DB\Adapter\Pdo\Mysql.
Magento 2 Models
Models are data and behavior, representing entities. Anything represented by a database table is most likely going to be a model.
To create the model, you just create a class and extend it from \Magento\Framework\Model\AbstractModel. Again, note the single-underscore construct method. Do not confuse it with the real PHP constructor. Call the method _init() where the resource model should be set.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Inbox extends \Magento\Framework\Model\AbstractModel implements NotifierInterface, InboxInterface { protected function _construct() { $this->_init('Magento\AdminNotification\Model\ResourceModel\Inbox'); } …. } ----------------------------------------------------------------------------------- abstract class AbstractModel extends \Magento\Framework\DataObject { ... protected function _init($resourceModel) { $this->_setResourceModel($resourceModel); $this->_idFieldName = $this->_getResource()->getIdFieldName(); } … } |
This is required to support the inherited AbstractModel methods getResource() and getCollection().
Method getCollection() is deprecated. It won’t be used in future Magento 2 versions. It’s required to use the collection factory instead to avoid conflicts with your code.
The model has no direct access to the database, only the resource models.
Models contain methods only for managing objects with data that is supposed to be prepared for saving to a database or loading to a frontend. Even method load was “moved” to a resource model and will be deprecated from a model in future versions of Magento 2. It also contains different useful flags which are used for validating data before setting to database or preventing unnecessary transactions to the database if the data in an object hasn’t changed. It’s used in the method save() in the \Magento\Eav\Model\Entity class, depending on different Magento 2 entities:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public function save(\Magento\Framework\Model\AbstractModel $object) { /** * Direct deleted items to delete method */ if ($object->isDeleted()) { return $this->delete($object); } if (!$object->hasDataChanges()) { return $this; } ... } |
Besides the load() method, Magento 2 model also contains the deprecated CRUD (“CRUD” stands for “Create, Read, Update, Delete.”) methods save() and delete(). The actions have been delegated to the resource model. Developers striving to write upgrade-safe code should not use the CRUD methods on the model, as they will be removed at some point in the future. Instead, the models should be passed directly to the CRUD methods on the resource model.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
abstract class AbstractModel extends \Magento\Framework\DataObject { …. public function save() { $this->_getResource()->save($this); return $this; } …. } abstract class AbstractDb extends AbstractResource { …. public function save(\Magento\Framework\Model\AbstractModel $object) { if ($object->isDeleted()) { return $this->delete($object); } |
Actually, the method does both: If the $isDeleted parameter is specified, it sets the _isDeleted flag value; otherwise, it returns the current flag value. The method validateBeforeSave() uses a validator, which contains all the rules to validate the current model. It is called by the resource model save() method.
In the resource model save() method, the _dataSaveAllowed flag can stop saving after the beforeSave() method was called. This can be helpful in case some data validation failed. The resource models method save() checks the saved object and runs updateObject() and saveNewObject(), which use \Magento\Framework\DB\Adapter\AdapterInterface object for working with databases. Mechanism of the method load() is similar:
|
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 |
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; } …. abstract class AbstractDb extends AbstractResource { …. public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null) { 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); return $this; } …. } |
Since the model load method will be removed at some point in the future, it is better to use plugins into the resource model load method instead of relying on these events.
The _afterLoad() method is used for processing an object directly after loading the data. Every time a Magento model is loaded, you can intercept it by observing the events model_load_before and model_load_after, which carries a simple data transfer object. The model instance itself is the key “object”.

Magento Development Services
Take your online store to the next level with BelVG Magento development
Click to visit the pageMagento 2 Collections
Collections are encapsulating sets of models and related functionality, such as filtering, sorting, and paging.
A collection in Magento is a class that implements both the IteratorAggregate and the Countable PHP5 SPL interfaces. Collections are widely used in Magento to store a set of objects of a specific type.
The collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection.
When creating a resource collection, you need to specify which model it corresponds to, so that it can instantiate the appropriate classes after loading a list of records. It’s also necessary to know the matching resource model to be able to access the database. That’s why the class names of the model and the resource model are specified by using the _init() method in the protected _construct() method.
There isn’t the best choice to use the method getCollection() of \Magento\Framework\Model\AbstractModel for getting entities collections as it will be remove in Magento 2 future versions. You should use collection factory. Here is an example of getting product collection in \Magento\Setup\Fixtures\Quote\QuoteGenerator:
|
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 |
/** * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */ private $productCollectionFactory; ... public function __construct( ... \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, ... ) { ... $this->productCollectionFactory = $productCollectionFactory; ... } ... private function getProductIds(\Magento\Store\Api\Data\StoreInterface $store, $typeId, $limit = null) { /** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */ $productCollection = $this->productCollectionFactory->create(); $productCollection->addStoreFilter($store->getId()); $productCollection->addWebsiteFilter($store->getWebsiteId()); // "Big%" should be replaced with a configurable value. if ($typeId === QuoteConfiguration::BIG_CONFIGURABLE_TYPE) { $productCollection->getSelect()->where(" type_id = '" . Configurable::TYPE_CODE . "' "); $productCollection->getSelect()->where(" sku LIKE 'Big%' "); } else { $productCollection->getSelect()->where(" type_id = '$typeId' "); $productCollection->getSelect()->where(" sku NOT LIKE 'Big%' "); } return $productCollection->getAllIds($limit); } |
The API you program against for collections is implemented by \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection.
The programmatic API for collections is implemented by \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Collection provides several useful functions for working with a result // set; here is a partial set addFieldToFilter($field, $condition = null); getConnection(); getSelect(); addBindParam($name, $value); getIdFieldName(); distinct($flag); addOrder($field, $direction = self::SORT_ORDER_DESC); setOrder($field, $direction = self::SORT_ORDER_DESC); unshiftOrder($field, $direction = self::SORT_ORDER_DESC); getItems(); getSize(); getSelectCountSql(); load($printQuery = false, $logQuery = false); resetData(); walk($callback, array $args = []); |
Collections provide several useful functions for defining and working with a result set. As you can see, the set of methods exposed by collections has not changed much from Magento 1.
A resource collection is necessary to create a set of model instances and operate on them. Resource collections lazy-load the recordset they represent. The first time the item list is accessed, a collection will load automatically. After that, a collection will not load again, even if the load() method is explicitly called.
Collections are very close to the database layer. Their task is to retrieve a set of rows from the database, then iterate over them, and for each row, instantiate the matching model class.
It solves these main issues:
- Provides a container for storing collections of objects.
- Prevents unnecessary data loading.
- Stores all objects during a session.
- Provides an interface for filtering and sorting entities of a specific kind.
The Magento ORM is used by the Repository implementations that are part of the Magento 2 service contracts. This is an important variation from Magento 1, as a module should no longer rely on other modules using a specific ORM, and instead of it use only the entity repositories. The service contracts will be covered in more detail in the second part of the article.
Conclusion
Magento 2 ORM remains the foundation for database interaction in 2026. The core architecture is built around three main components:
- Models. They manage entity data and business behavior.
- Resource Models. These models handle database operations and CRUD logic.
- Collection. It retrieves and filters groups of entities.
Modern Magento development favors Service Contracts and Repositories for cleaner and upgrade-safe code. Still, understanding the ORM layer remains important for building custom modules, debugging database operations, and efficiently extending Magento functionality.
Please check the second part of the article: Database in Magento 2: Service Contracts
Looking for an expert Magento development team to realize your projects? We can help!
The following people contributed to the article: Sasha Mitskevich.



Hi, Dzmitry!
There seems to be a simple misspelling – it should be “delete data in a database”. Thanks for raising awareness! We will immediately edit the article.
What does it mean “and delete data to a database.” ?
You are welcome, Robert and Ricardo! Subscribe to our blog to stay tuned ;)
Very good article. Well explained. Thanks for sharing.
Great article, really helpful for understanding the Magento 2 ORM!