agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/architecture/hexagonal/SKILL.md

hexagonal

Skill4 starsChanged 4 months agoMITView on GitHub

Isolate a framework-agnostic domain core behind ports (interfaces) and drive it through adapters at the edges, so the dependency direction always points inward and infrastructure stays swappable. Use when the user wants domain logic that does not import framework, ORM, or HTTP classes so it can be tested and reused in isolation; the user needs to swap a database, message queue, or third-party client without changing business logic; the user wants to unit-test use cases by substituting in-memory fakes for databases and external services. Keywords: hexagonal, ports-and-adapters, architecture, dependency-direction, domain, decoupling.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 4 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    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 force a hexagonal core when the team deliberately builds framework-native (active-record, controllers-with-logic) and accepts the …

    line 65
  2. Distinctive:

    Do not impose ports and adapters on a thin CRUD script that has no business rules and will never swap infrastructure.

    line 66
  3. Very specific:

    Write a concrete adapter in the infrastructure layer that implements the port, e.g.

    line 79
  4. Distinctive:

    Keep framework, ORM, and transport types (requests, Eloquent models, HTTP clients) out of the domain.

    line 89
  5. 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/hexagonal/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

Hexagonal Architecture (Ports & Adapters)

When to use

  • The user wants domain logic that does not import framework, ORM, or HTTP classes so it can be tested and reused in isolation.
  • The user needs to swap a database, message queue, or third-party client without changing business logic.
  • The user wants to unit-test use cases by substituting in-memory fakes for databases and external services.

When NOT to use

  • Do not force a hexagonal core when the team deliberately builds framework-native (active-record, controllers-with-logic) and accepts the coupling.
  • Do not impose ports and adapters on a thin CRUD script that has no business rules and will never swap infrastructure.

Core guidance

  • Express ports as interfaces owned by the core — Declare each port as an interface that lives with the domain, and let adapters in the infrastructure layer implement it.
  • Point every dependency toward the core — Let the domain core depend on nothing outward; adapters and framework code depend on the core's ports, never the reverse.

Quick recipe

  1. Declare a driven port interface in the domain for each outbound need, e.g. interface OrderRepository with save(Order $order): void, typed only in domain terms. (verify: The interface lives with the domain and references no framework or ORM type)
  2. Write a concrete adapter in the infrastructure layer that implements the port, e.g. EloquentOrderRepository implements OrderRepository, mapping domain objects to rows. (verify: The adapter satisfies the interface and no domain class references the adapter)
  3. In the composition root, bind the port to the adapter (e.g. $container->bind(OrderRepository::class, EloquentOrderRepository::class)) and inject the port into the core. (verify: Swapping the binding to an in-memory adapter runs the core unchanged)

Best practices

  • Split ports into driving (inbound, called by controllers and CLI to invoke the core) and driven (outbound, implemented by persistence and clients the core calls)
  • Keep framework, ORM, and transport types (requests, Eloquent models, HTTP clients) out of the domain; translate them to plain domain objects in the adapter
  • Bind each port to a concrete adapter in one composition root (the container config or bootstrap), never by having the core reach for a concrete class

Anti-patterns

Anti-patternWhy it failsDo instead
Adapters calling other adapters directlyAdapter-to-adapter calls route business flow around the domain, so logic and ordering rules escape the core and become untestable.Route every cross-boundary flow through a driving port on the core, so the domain<br>orchestrates the driven adapters.
Core code importing the framework directlyA core that reaches for framework classes reverses the dependency direction and can no longer be tested or reused in isolation.Depend on a port interface owned by the core and inject the framework-backed adapter from<br>the composition root.
A separate port for every single methodOver-fragmenting ports multiplies interfaces and wiring without improving cohesion, drowning the design in boilerplate.Group operations that change together onto one cohesive port named for the capability it<br>provides, splitting only when responsibilities genuinely diverge.

See references/anti-patterns.md.

Common mistakes

  1. Ports with an anemic core behind them — Wrapping getters and setters in ports while the real logic lives in adapters produces the ceremony of hexagonal with none of its benefit. Fix: Put the actual business rules in the domain core so the ports guard meaningful behavior, not a pass-through data holder.
  2. Leaking infrastructure types through a port — Declaring a port method that returns or accepts a framework type (an Eloquent model, a PSR request) drags infrastructure back into the core. Fix: Type ports only in domain terms — entities, value objects, and DTOs the core owns — and map to and from framework types inside the adapter.
  3. Sharing one persistence model as the domain model — Reusing the ORM entity as the domain entity ties the core's shape to the database schema and its lazy-loading behavior. Fix: Keep a plain domain model and translate it to and from the persistence model inside the driven adapter, even when the two look similar today.

Version notes

  • Pure domain modeling with PHP 8.1 features (PHP >=8.1) — PHP 8.1 enums, readonly properties, and first-class enum interfaces let the domain core express value objects and state without any framework dependency.
  • Property hooks refine adapter mapping in PHP 8.4 (PHP >=8.4) — From PHP 8.4, property hooks let an adapter expose a domain-shaped view over stored data without a hand-written getter, keeping the mapping code in the adapter thin.

Security

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

Testing

  • Activation and decision behavior are verified in eval/scenarios/architecture/hexagonal.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

Related: architecture/cqrs, architecture/dependency-inversion, php/value-objects

<!-- graph-fragment: graph/fragments/architecture/hexagonal.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.