agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/architecture/repository-pattern/SKILL.md

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.

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
  1. Distinctive:

    Do not wrap ad-hoc reporting or analytics queries that return raw rows in a repository when there is no aggregate to reconstitute.

    line 66
  2. Distinctive:

    Do not add repositories to a small CRUD app that deliberately uses active-record models and has no separate domain layer.

    line 67
  3. Very specific:

    Write a concrete implementation in infrastructure that maps rows to aggregates, e.g.

    line 80
  4. Distinctive:

    Not applicable — no I/O, auth, or untrusted input.

    line 125

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- 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

  1. Declare an interface per aggregate in the domain with collection-like methods, e.g. interface CustomerRepository with add(Customer $c): void and ofId(CustomerId $id): ?Customer. (verify: The interface names domain operations and mentions no ORM or SQL type)
  2. 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)
  3. 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-patternWhy it failsDo instead
Business rules buried in the repositoryA 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 entityA 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 repositoryMixing 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

  1. 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.
  2. 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.
  3. 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 -->

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.