agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/architecture/dependency-inversion/SKILL.md

dependency-inversion

Skill4 starsChanged 4 months agoMITView on GitHub

Depend on abstractions rather than concretions — high-level policy and low-level detail both hinge on interfaces owned by the policy — and wire the concrete implementations in at a single composition root. Use when the user wants a class to depend on an interface rather than a concrete implementation so the two can vary independently; the user wants collaborators injected through the constructor instead of being created with `new` inside a class; the user wants to substitute a fake or mock for a dependency in a test without editing the class under test. Keywords: dependency-inversion, dip, solid, abstractions, dependency-injection, wiring.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 3 are distinctive, and 0 are 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.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not add an interface for a small internal helper that has one caller, no I/O, and no realistic second implementation.

    line 64
  2. Distinctive:

    Do not invert a dependency on a stable standard-library type or a pure value (a string, a DateTimeImmutable, an array helper).

    line 65
  3. Distinctive:

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

    line 122

≈ “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/dependency-inversion/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

Dependency Inversion Principle

When to use

  • The user wants a class to depend on an interface rather than a concrete implementation so the two can vary independently.
  • The user wants collaborators injected through the constructor instead of being created with new inside a class.
  • The user wants to substitute a fake or mock for a dependency in a test without editing the class under test.

When NOT to use

  • Do not add an interface for a small internal helper that has one caller, no I/O, and no realistic second implementation.
  • Do not invert a dependency on a stable standard-library type or a pure value (a string, a DateTimeImmutable, an array helper).

Core guidance

  • Depend on abstractions, not concretions — Type constructor parameters and properties against interfaces, not concrete classes, so a class collaborates with a contract rather than a specific implementation.
  • Let the high-level policy own the interface — Define the interface in the high-level module that needs it and phrase it in that module's terms; the low-level detail then implements the policy's contract.

Quick recipe

  1. Name the role the consumer needs and declare an interface for it in the consumer's module, e.g. interface Mailer with send(Message $message): void. (verify: The interface is phrased in the consumer's terms, not a backend's API)
  2. Replace any new Concrete() with a constructor parameter typed to the interface, e.g. public function __construct(private Mailer $mailer) {}. (verify: The class references only the interface and never constructs the concrete class)
  3. Register the binding once in the composition root, e.g. $container->bind(Mailer::class, SmtpMailer::class), so consumers receive the concrete type. (verify: Rebinding to a fake mailer in a test injects it everywhere with no consumer change)

Best practices

  • Receive every collaborator as a constructor parameter typed to its interface rather than calling new on a concrete class inside a method
  • Shape each abstraction around the role the consumer needs (e.g
  • Map each interface to its concrete implementation in a single composition root (a service provider or container config), never scattered across the codebase

Anti-patterns

Anti-patternWhy it failsDo instead
Injecting the container into domain classesA class holding the container can pull any dependency at runtime, hiding its real needs and coupling the domain to the container.Inject only the specific abstractions the class uses, and keep container access confined to<br>the composition root.
A mirror interface for every classBlanket interfaces double the type count and imply flexibility that does not exist, adding indirection without a real seam.Introduce an abstraction only where an implementation genuinely varies or a test seam is<br>needed; otherwise depend on the class directly.
Type-hinting the concrete class everywhereConsumers bound to a concrete class cannot accept an alternative or a fake, so the class becomes a hard dependency scattered across the codebase.Extract an interface the consumers depend on and type-hint that, binding it to the concrete<br>class once at the composition root.

See references/anti-patterns.md.

Common mistakes

  1. An interface shaped around one implementation — Designing the interface around the quirks of one implementation (Redis-specific methods on a cache abstraction) leaks that detail to every consumer. Fix: Define the interface from the consumer's need in neutral terms, and hide backend-specific behavior inside the implementation.
  2. Newing up a concrete dependency inside the class — Calling new ConcreteMailer() inside a class hardwires that implementation and defeats the inversion the interface was meant to provide. Fix: Accept the dependency as a constructor parameter typed to its interface, and let the composition root pass in the concrete instance.
  3. Pulling dependencies from a global container — Reaching into a static container (app(Mailer::class)) from inside a class hides its real dependencies and re-introduces global coupling. Fix: Inject the dependencies you need as explicit constructor parameters and let the container resolve them at the composition root only.

Version notes

  • Pure interfaces sharpened by PHP 8.1 types (PHP >=8.1) — PHP 8.1 enums, never return types, and readonly promotion let role interfaces and their injected implementations be expressed precisely without framework helpers.
  • New in initializers eases default wiring in PHP 8.1+ (PHP >=8.1) — From PHP 8.1, new in parameter default values lets a class declare a sensible default implementation while still allowing the composition root to override it.

Security

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

Testing

  • Activation and decision behavior are verified in eval/scenarios/architecture/dependency-inversion.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/hexagonal, laravel/service-container

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