repository-pattern
Skill4 starsChanged 4 months agoMITView on GitHub
Put persistence behind a collection-like repository interface that speaks in domain objects, so the domain saves and finds aggregates without knowing about SQL, the ORM, or query details. Use when the user wants domain code to save and load aggregates through an interface instead of writing SQL or ORM calls inline; the user needs to change the data store or query technology without touching the code that uses the data; the user wants to test business logic against an in-memory repository instead of a real database. Keywords: repository, persistence, abstraction, aggregate, domain, data-access.
In this repository119 other files it gives its agents.
- strategy-bitrix-d7-incrementalSkill
- strategy-explicit-over-magicSkill
- strategy-framework-conventionsSkill
- strategy-legacy-safe-editsSkill
- strategy-minimal-diffSkill
- strategy-no-untested-refactorSkill
- strategy-preserve-public-apiSkill
- strategy-readonly-immutabilitySkill
- strategy-security-firstSkill
- strategy-test-drivenSkill
- openapiSkill
- paginationSkill
- rest-designSkill
- versioningSkill
- cqrsSkill
- dependency-inversionSkill
- hexagonalSkill
- service-layerSkill
- bitrixSkill
- findings-reportSkill
- laravelSkill
- performanceSkill
- phpSkill
- project-analysisSkill
- refactoringSkill
- roi-prioritizationSkill
- securitySkill
- stack-detectionSkill
- cache-layersSkill
- component-engineSkill
- d7-eventsSkill
- d7-loaderSkill
- d7-orm-queriesSkill
- iblock-elementsSkill
- kernel-main-moduleSkill
- localizationSkill
- controllersSkill
- ormSkill
- validationSkill
- controllersSkill
- query-builderSkill
- routingSkill
- autoload-psr4Skill
- dependency-managementSkill
- scriptsSkill
- connection-poolingSkill
- mysql-indexingSkill
- redisSkill
- transactionsSkill
- ci-github-actionsSkill
- deployment-strategiesSkill
- docker-phpSkill
- env-configSkill
- associationsSkill
- entities-mappingSkill
- orm-basicsSkill
- entity-apiSkill
- module-developmentSkill
- routing-controllersSkill
- formsSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 66
Do not wrap ad-hoc reporting or analytics queries that return raw rows in a repository when there is no aggregate to reconstitute.
- Distinctive: line 67
Do not add repositories to a small CRUD app that deliberately uses active-record models and has no separate domain layer.
- Very specific: line 80
Write a concrete implementation in infrastructure that maps rows to aggregates, e.g.
- Distinctive: line 125
Not applicable — no I/O, auth, or untrusted input.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- generated: do not edit — source: knowledge/atoms/architecture/repository-pattern/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->Repository Pattern
When to use
- The user wants domain code to save and load aggregates through an interface instead of writing SQL or ORM calls inline.
- The user needs to change the data store or query technology without touching the code that uses the data.
- The user wants to test business logic against an in-memory repository instead of a real database.
When NOT to use
- Do not wrap ad-hoc reporting or analytics queries that return raw rows in a repository when there is no aggregate to reconstitute.
- Do not add repositories to a small CRUD app that deliberately uses active-record models and has no separate domain layer.
Core guidance
- Give the repository collection-like methods — Shape the interface like an in-memory collection of aggregates —
add,remove,findById,matching(criteria)— rather than exposing raw query builders. - Define the repository interface in the domain — Declare the repository as an interface that lives in the domain layer and is expressed in domain terms; keep the concrete implementation in infrastructure.
Quick recipe
- Declare an interface per aggregate in the domain with collection-like methods, e.g.
interface CustomerRepositorywithadd(Customer $c): voidandofId(CustomerId $id): ?Customer. (verify: The interface names domain operations and mentions no ORM or SQL type) - Write a concrete implementation in infrastructure that maps rows to aggregates, e.g.
EloquentCustomerRepository implements CustomerRepository, keeping all query building inside it. (verify: The implementation returns reconstituted aggregates, never raw rows or a query builder) - Bind the interface to the concrete repository at the composition root, and add an in-memory implementation for tests that stores aggregates in an array. (verify: A use-case test runs against the in-memory repository with no database)
Best practices
- Encapsulate each meaningful query behind a named method (
findActiveByCustomer) or a specification, instead of letting callers assemble WHERE clauses - Provide one repository per aggregate root and load or store the whole aggregate through it, rather than a repository per table
- Have the repository return fully reconstituted domain objects (or null / a not-found result), never raw rows, arrays, or ORM result sets
Anti-patterns
| Anti-pattern | Why it fails | Do instead |
| Business rules buried in the repository | A repository is for storing and retrieving aggregates; hiding business rules in it scatters the domain and ties those rules to the data store. | Keep the repository focused on persistence and put business rules in the aggregate or an<br>application service that uses the repository. |
| A generic CRUD repository over every entity | A generic CRUD base treats all entities alike, ignoring aggregate boundaries and forcing meaningless methods onto types that do not need them. | Define a specific interface per aggregate with only the operations that aggregate genuinely<br>supports, expressed in its own vocabulary. |
| Callers touching the ORM alongside the repository | Mixing direct ORM access with the repository defeats the abstraction, so the data store can no longer be swapped and query logic lives in two places. | Route every access to the aggregate through its repository, adding a named method when a new<br>query is needed rather than reaching for the ORM. |
See references/anti-patterns.md.
Common mistakes
- A god repository with dozens of finders — Letting one repository accumulate every query the app ever needs produces a bloated interface that mixes unrelated concerns. Fix: Keep the repository scoped to its aggregate's core lifecycle, and move complex read-side queries into dedicated read models or query services.
- Returning a query builder from the repository — Exposing a method that returns the ORM query builder lets callers append arbitrary clauses, dragging query construction back out of the repository. Fix: Return finished domain objects or collections from named methods, and keep all query building inside the repository implementation.
- N+1 queries hidden behind the interface — A repository that lazily loads each related aggregate on access can fire one query per item, turning a list load into an N+1 storm the caller cannot see. Fix: Add a method that eager-loads the needed relations in one query, and measure query counts in a test so regressions surface.
Version notes
- Readonly identifiers strengthen lookups in PHP 8.1 (PHP >=8.1) — PHP 8.1 readonly properties let identifier value objects (a
CustomerId) be immutable, so repository lookup methods can take a typed id instead of a bare int or string. - Native lazy objects aid hydration in PHP 8.4 (PHP >=8.4) — From PHP 8.4, native lazy objects let a repository return a placeholder aggregate that hydrates on first use, deferring the database read without a hand-rolled proxy.
Security
- Not applicable — no I/O, auth, or untrusted input.
Testing
- Activation and decision behavior are verified in
eval/scenarios/architecture/repository-pattern.yaml.
Additional resources
- references/anti-patterns.md
- references/core.md
- references/examples.md
- references/pitfalls.md
- references/recipes.md
- references/version-differences.md
Knowledge graph
Prerequisites: architecture/hexagonal
Related: architecture/service-layer, php/value-objects
<!-- graph-fragment: graph/fragments/architecture/repository-pattern.yaml -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

