Install
$ agentstack add skill-garrettw-php-arch-skills-dependency-injection ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Dependency Injection Best Practices for PHP
When to Use a DI Container (and When NOT to)
A DI container is an infrastructure concern. Use it for applications that need flexible wiring, interface swapping, or framework integration. Libraries should use constructor injection directly and let the consumer resolve dependencies.
| Use a DI Container For | Do Not Use a Container For | |---------------------------------------------------|------------------------------------------| | Applications with many interchangeable services | Libraries and packages | | Need to swap implementations (test vs production) | Simple scripts with one or two classes | | Framework integration (Symfony, Laravel) | Throwaway code or prototypes |
The container should wire the application together at the edge, while the core remains ignorant of the container. This skill provides the rules for registering dependencies, migrating legacy locators, and injecting objects.
A [Plugin](references/plugin.md) is the purpose this wiring serves: define extension points (Ports) so new behavior is added by plugging in implementations (Adapters) at the composition root, without modifying the core. A plugin must extend the existing abstractions, never bypass them. It protects the framework / extension boundary — one of a family of boundary-protection patterns (with Gateway, Mapper, Remote Facade, DTO, and Special Case) that form the foundation of Hexagonal architecture; see [boundary-protection-patterns.md](../distribution-patterns/references/boundary-protection-patterns.md).
Numbered Workflows
1. Registering Dependencies
If deciding whether to create an interface and register it in the DI container:
- Check for Multiple Implementations. Are there multiple implementations (e.g.,
StripeAdapterandPayPalAdapter)? - Check for Boundary Inversion. Is the application defining a port that the infrastructure implements?
- Check for Decorators. Do you need to wrap the class with caching or logging?
- If Yes to any: Create an interface and register the binding in the container configuration.
- If No to all: Depend on the concrete class directly and let the container autowire it. Do not blindly create interfaces for every class.
2. Testing with Constructor Injection
If writing tests for classes that use constructor injection:
- For Domain/Handler Tests: Construct the object using
new. Pass in test doubles (mocks or fakes) for any infrastructure dependencies. Do not boot the DI container. - For Integration/Feature Tests: Ask the test framework to resolve the controller or handler from the DI container.
3. Migrating Legacy Code
If modifying a legacy class that uses a service locator (Container::getInstance()->get()):
- Follow the Migration Sequence. Read the [Migration Sequence for Legacy Containers](references/migration-sequence.md).
- Extract to Constructor. Find every internal
getInstance()call and add those dependencies as typed constructor properties. - Fix Callers. Update all instantiations of the legacy class to pass the dependencies.
- Use Shims if Necessary. If the class is instantiated in 50 places you cannot change, use the temporary shim pattern to bridge the gap.
Boundaries
Always Do
- Always use constructor injection for services, handlers, repositories, adapters, and listeners.
- Always keep the container wiring logic at the framework edge (composition roots, service providers).
- Always keep the domain core free of framework dependencies. It should resolve from tests or a composition root with no framework boot.
- Always treat a plugin as a first-class architectural citizen: it implements a defined Port and obeys the same dependency rule and invariants as core code (see [plugin.md](references/plugin.md)).
Ask First
- Ask before introducing a legacy shim if it is feasible to simply update all callers instead.
Never Do
- Never use service locators inside domain objects, application handlers, or policies. They hide dependencies and couple core code to the framework.
- Never let a plugin become a backdoor: it must not reach into core internals, write to the database directly, or skip domain invariants. Plug in via the defined Port only.
Related Patterns
- The Factory pattern (variant selection, composition-root wiring, Singleton as anti-pattern) is covered in [creation-patterns.md](references/creation-patterns.md).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: garrettw
- Source: garrettw/php-arch-skills
- License: MPL-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.