Models, Resource Models & Collections
Master the four types of ORM classes in Magento: Models, Resource Models, Collections, and Repositories, and understand their responsibilities and relationships.
ORM Objects Overview
Four Types of ORM Classes
There are four types of classes related to loading and storing data in Magento:
- Models - Store data (one row)
- Resource Models - Database operations (save/load/delete)
- Collections - Load multiple models
- Repositories - API wrapper for data operations
Models
What are Models?
Models are classes with getters and setters to store data. An instantiated class would represent one row in the database.
Model/ directory
Example:
Model Initialization
This model is initialized with a reference to the Resource Model.
<?php
namespace Vendor\Module\Model;
use Magento\Framework\Model\AbstractModel;
class CustomEntity extends AbstractModel
{
protected function _construct()
{
$this->_init(\Vendor\Module\Model\ResourceModel\CustomEntity::class);
}
}
Data Storage in Models
Models that extend \Magento\Framework\Model\AbstractModel will thus extend \Magento\Framework\DataObject. This means that the data is stored in the class' $_data property.
Magic Getters and Setters
You can convert snake-case notation to camel-case notation and use magic methods:
// Column name: discount_amount
// Magic getter
$discountAmount = $model->getDiscountAmount();
// Magic setter
$model->setDiscountAmount(100);
// OR use getData/setData
$discountAmount = $model->getData('discount_amount');
$model->setData('discount_amount', 100);
Explicit Getters and Setters (Best Practice)
Instead of magic getters/setters, it's recommended to build out explicit methods:
public function getDiscountAmount(): float
{
return (float)$this->getData('discount_amount');
}
public function setDiscountAmount(float $discountAmount): void
{
$this->setData('discount_amount', $discountAmount);
}
Advantages:
- ✅ Type hints - reduces unnecessary conversions (float to int, etc.)
- ✅ Mockable - can mock these methods in unit tests
- ✅ Custom handling - can customize how data is handled (e.g., json_encode in setter)
Resource Models
What are Resource Models?
Resource models are classes responsible for saving and loading data. They handle direct database operations.
Model/ResourceModel/ directory
Example:
Resource Model Inheritance
These classes almost always inherit \Magento\Framework\Model\ResourceModel\Db\AbstractDb.
Primary Methods:
load()- Load data into modelsave()- Save model data to databasedelete()- Delete model data from database
Resource Model Configuration
Resource models are initialized with:
- The table name (as found in db_schema.xml)
- The name of the primary key column
<?php
namespace Vendor\Module\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class CustomEntity extends AbstractDb
{
protected function _construct()
{
$this->_init('vendor_module_custom_entity', 'entity_id');
// table name primary key
}
}
Custom Queries in Resource Models
The resource model is the place to put any custom selects (using the Magento ORM, and NOT writing direct SQL).
Example: Get Count with Custom Query
public function getAvailableToTranslate(string $language): int
{
$select = $this->getConnection()->select();
$select->from($this->getMainTable(), 'COUNT(id)');
$select->where('needs_translation = ?', true);
$select->where('language = ?', $language);
return (int)$this->getConnection()->fetchOne($select);
}
Fetch Methods
Various fetch methods available for different result types:
| Method | Returns | Use Case |
|---|---|---|
fetchOne() |
Single value | Get one result (e.g., COUNT, single column value) |
fetchAll() |
Array of all rows | Get all rows as associative arrays |
fetchCol() |
Array of values from one column | Get all values from a single column |
fetchPairs() |
Key-value pairs | Get first column as key, second as value |
fetchRow() |
Single row as array | Get one row as associative array |
fetchAssoc() |
Associative array | Get rows with first column as array key |
Collections
What are Collections?
Collections load multiple models. They handle loading multiple rows for a database entity or table.
Model/ResourceModel/[ModelName]/Collection.php
Example:
Collection Initialization
This class is initialized with a reference to the Model and Resource Model.
<?php
namespace Vendor\Module\Model\ResourceModel\CustomEntity;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
protected function _construct()
{
$this->_init(
\Vendor\Module\Model\CustomEntity::class,
\Vendor\Module\Model\ResourceModel\CustomEntity::class
);
}
}
Collections Store State
// DON'T inject the collection directly
// DO inject the CollectionFactory
public function __construct(
\Magento\Cms\Model\ResourceModel\Page\CollectionFactory $collectionFactory
) {
$this->collectionFactory = $collectionFactory;
}
public function getPages()
{
$collection = $this->collectionFactory->create(); // Instantiate fresh
return $collection;
}
Collections are Iterable
Because collections implement the \IteratorAggregate interface, you can loop through a collection in a foreach() method.
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('status', 1);
foreach ($collection as $item) {
echo $item->getName();
}
Common Collection Methods
1. addFieldToFilter()
This is the equivalent of adding a WHERE clause to a SQL select.
$collection->addFieldToFilter('product_id', ['neq' => $productId]);
// SQL: SELECT * FROM table_name WHERE product_id <> 4;
Common Conditionals:
['eq' => $value]- Equal to['neq' => $value]- Not equal to['gt' => $value]- Greater than['gteq' => $value]- Greater than or equal['lt' => $value]- Less than['lteq' => $value]- Less than or equal['like' => '%value%']- LIKE pattern['in' => [$val1, $val2]]- IN array['nin' => [$val1, $val2]]- NOT IN array['null' => true]- IS NULL['notnull' => true]- IS NOT NULL
2. addAttributeToFilter()
On EAV entities (Magento\Eav\Model\Entity\Collection\AbstractCollection): this is the same as addFieldToFilter() for EAV-enabled entities. If you call either method, you get the same result.
// For EAV entities (products, categories, customers)
$collection->addAttributeToFilter('status', 1);
3. join()
Joins in another table.
$collection->join(
['table_alias' => 'table_name'],
'main_table.product_id = table_alias.product_id',
['column_a']
);
// Results in:
// SELECT *, table_alias.column_a
// FROM original_table AS main_table
// INNER JOIN table_name AS table_alias
// ON main_entity.product_id = table_alias.product_id;
main_table is the default alias with collections.
4. load()
This fetches the results of the collection. Data is loaded from the database and then models are hydrated with said data.
$collection->load(); // Executes the query and loads data
5. getSize()
This creates a copy of the select, strips out the columns, and uses the COUNT method.
$count = $collection->getSize(); // Returns count of items
Repositories
What are Repositories?
Repositories handle the primary actions that happen to a database table. They are often a wrapper for the Resource Model.
Repository Methods
Repositories usually have:
save()- Save a modelgetById()- Get one row/model from the databasegetList()- Accepts a SearchCriteria class, returns multiple itemsdelete()- Delete a model
Repository Structure
Repositories do not inherit another class. They represent more of an idea (unlike models, resource models, or collections).
Common Injected Classes:
- Model's Factory
- Resource Model
- Collection Factory
- SearchResults Factory
Example:
Repository Example
<?php
namespace Vendor\Module\Model;
use Vendor\Module\Api\CustomEntityRepositoryInterface;
use Vendor\Module\Model\CustomEntityFactory;
use Vendor\Module\Model\ResourceModel\CustomEntity as ResourceModel;
class CustomEntityRepository implements CustomEntityRepositoryInterface
{
private $entityFactory;
private $resourceModel;
public function __construct(
CustomEntityFactory $entityFactory,
ResourceModel $resourceModel
) {
$this->entityFactory = $entityFactory;
$this->resourceModel = $resourceModel;
}
public function getById(int $id)
{
$entity = $this->entityFactory->create();
$this->resourceModel->load($entity, $id);
if (!$entity->getId()) {
throw new NoSuchEntityException(__('Entity not found'));
}
return $entity;
}
public function save($entity)
{
$this->resourceModel->save($entity);
return $entity;
}
public function delete($entity)
{
$this->resourceModel->delete($entity);
return true;
}
}
Why Repository Wraps Resource Model
Repositories are often a wrapper for the Resource Model. The latter has no way to instantiate a model: the repository creates the model (via the model's factory) and then passes it to the Resource Model's load() method.
Repository Interface Requirement
ProductInterface). This is because many areas of Magento rely on the repository returning the correct interface.
Repositories as API Endpoints
Repositories are commonly an API Endpoint (the service class in a webapi.xml file).
For this situation, you would:
- Create an interface that the repository then implements
- Declare the route and resources required in webapi.xml
Repository getList() vs Collection
How do they differ?
For a normal model (like a CMS page or an order), there would be no difference.
- Repository getList(): ALL attributes are loaded
- Collection: Only the attributes you select are loaded
Why This Option Exists
Originally, a repository was designed for use through an API: APIs are less concerned about speed and more about how much information can be returned.
Responsibilities Summary
Models
Responsibility: Store data
- Once loaded from database (hydrated), ready for use
- Data stored in
$_dataproperty - Getters and setters
- Magic methods available
- Represents one row
Resource Models
Responsibility: Handle database operations
- Save, load, delete by default
- Custom queries for performance
- Direct database interaction
- Hydrates models with data
- Uses fetch methods
Collections
Responsibility: Load multiple rows
- Load multiple models
- Iterable (foreach)
- addFieldToFilter, join, load, getSize
- Store state (use Factory)
- For EAV: select specific attributes
Repositories
Responsibility: API wrapper
- save, getById, getList, delete
- Wraps Resource Model
- Do not store state
- API endpoints
- For EAV: loads ALL attributes
How They Relate to One Another
- A collection loads a list of models
- A resource model directly interacts with the database
- A resource model handles loading (hydrating) a model class that is already instantiated
- A model stores data - get() and set() are used frequently
- A repository creates models and uses resource models to load/save data
- Collections implement
\IteratorAggregateso you can use foreach()
Exam Tips
Key Points to Remember
- Models: Model/ directory, store data, one row, getters/setters
- Resource Models: Model/ResourceModel/ directory, database operations, inherit AbstractDb
- Collections: Model/ResourceModel/[ModelName]/Collection.php, load multiple models, iterable
- Repositories: Wrapper, save/getById/getList/delete, no state, API endpoints
- Magic methods: Convert snake_case to camelCase (discount_amount → getDiscountAmount())
- Explicit methods preferred: Type hints, mockable, custom handling
- Collections store state: Use CollectionFactory, not direct injection
- addFieldToFilter: WHERE clause equivalent
- addAttributeToFilter: Same as addFieldToFilter for EAV entities
- main_table: Default alias in collections
- Fetch methods: fetchOne, fetchAll, fetchCol, fetchPairs, fetchRow, fetchAssoc
- Repository vs Collection (EAV): Repository loads ALL attributes, Collection loads selected
- Performance: Collections with selected attributes better for EAV
- IteratorAggregate: Collections can be used in foreach()
- Repository wraps Resource Model: Creates model via factory, passes to resource model