How to think about entities in Magento 2

2021-12-04 · Ben Williamson

Why I finally wrote this down

A teammate asked which classes to study when learning Magento data access. I pointed at vendor/magento and explained module naming. That helped, but the real gap was pattern recognition: the same CRUD shapes show up on stores, customers, catalog products, and stock items even when table names change.

I started collecting ENTITY constants and wiring examples in a side module. Every entity felt like a new puzzle until I mapped the recurring layers. This post is the overview I wanted on day one. Follow-up posts drill into store, customer, and catalog examples. The goal here is the decision tree, not a single mega data patch.

The handful of entities pattern

Magento does not give you one generic ORM call for everything. It gives you a small toolkit you reuse:

  • Interface: the service contract surface (Api/Data and Api repository interfaces).

  • Repository: load, save, delete, and list through a stable API.

  • Factory: create a new in-memory entity before persistence.

  • Model: entity state plus business logic methods.

  • Resource model: SQL and table mapping.

  • Collection: filtered, sorted, paginated sets of models.

Most custom module work is some mixture of those six pieces. The confusion is not missing classes. It is picking the wrong layer for the job.

Four ways to load the same row

For any entity you will see overlapping load paths in core and in older modules:

  • EntityRepositoryInterface::get($id) or getById() (service contract)

  • EntityCollection with filters, then getFirstItem() (ORM list query)

  • EntityModel::load($id) (legacy model load, largely deprecated)

  • EntityResourceModel::load($model, $id) (direct persistence layer)

Collections, models, and resource models are the ORM stack. Repositories wrap that stack for modules that should survive internal refactors. When you read docblocks, Magento is usually telling you to prefer repository entry points for single-entity reads and writes.

In practice you still see all four in the wild, especially in code written before repositories were enforced.

Why interfaces matter

If you want to align with Magento service contracts, depend on interfaces in constructors and public APIs, not concrete models.

  • Extensibility: other modules can extend data interfaces (often via ExtensibleDataInterface and extension attributes).

  • REST alignment: repository and data interfaces in Api mirror what web API layers can call.

  • Stability: interface contracts are meant to change slowly even when underlying models move.

Interfaces do not instantiate themselves. Check etc/di.xml preferences in the module that owns the interface. That is where Magento maps StoreRepositoryInterface to StoreRepository, and the same pattern repeats across entities.

Why repositories, and when to skip them

Repositories centralize persistence. Core has been deprecating direct $model->save() calls in favor of repository or resource-model saves. That separation keeps entities from owning SQL details.

Repositories are not complete for every edge case. Store/website/store-view operations are a common example where you still reach for a resource model when the repository surface is too thin. My rule: start with the repository. When it blocks a legitimate operation or adds needless indirection, drop to the resource model with intent, not by default.

Models, resource models, and collections

Short definitions I use when onboarding:

  • Resource model: talks to the database tables.

  • Model: the entity object and its business methods; delegates persistence to the resource model.

  • Collection: a queryable set of models. This is the right tool for filtered lists.

Repositories orchestrate factories and collections behind a contract. Collections are not a hack. They are the intended list mechanism when you need SQL-shaped filtering.

How the layers connect

The customer module is the cleanest core reference for this diagram. Other domains follow the same shape but pick up exceptions faster (catalog EAV, stock reservations, multi-source inventory).

A practical CRUD game plan

This is the flow I sketch before writing integration code:

  1. Read one: repository get or getById.

  2. Read many with filters: collection with addFieldToFilter (or equivalent domain filters).

  3. Create: factory create(), set data, repository save.

  4. Update fields: mutate the entity with setters, then repository save.

  5. Fast attribute-only updates: if the resource model exposes updateAttribute (common in EAV domains), use it instead of saving the whole object.

  6. Delete: repository delete or deleteById when available; otherwise resource model delete with clear audit notes.

SearchCriteria, filters, and follow-up posts

Repository list calls revolve around SearchCriteria and filter groups. Collections use addFieldToFilter with AND/OR semantics that trip people up during migrations. I left those mechanics for entity-specific posts so this overview stays readable.

Planned follow-ups (same series) walk store, customer, and catalog examples with copy-paste snippets instead of one overloaded data patch.

Failure modes I keep debugging

  • Repository list for a simple filtered query: dozens of queries where a single collection would do.

  • Factory create without save: object exists in memory only; downstream code assumes an ID.

  • Loading through collection for a known ID: works, but hides intent and skips repository cache behaviors you might want.

  • Direct model save in new code: fights deprecation paths and bypasses extension points plugins expect.

  • Interface injection without checking preferences: you get the wrong implementation in a customized module stack.

Quick review before merge

  • Am I fetching one entity or a filtered set?

  • Does this domain repository expose the operation I need?

  • If I use a collection, is getFirstItem() really a single-entity read?

  • For attribute-only writes, is there a cheaper resource-model path?

  • Are my public APIs typed to interfaces, not concrete models?