dependency-injection

star 1

Use dependency injection cleanly in PHP applications. Constructor injection, avoiding service locators, what to put in the container.

SanderMuller By SanderMuller schedule Updated 6/4/2026

name: dependency-injection description: Use dependency injection cleanly in PHP applications. Constructor injection, avoiding service locators, what to put in the container.

Dependency injection

When to apply

  • Adding a new class with collaborators
  • Reviewing PR that introduces a new SomeService() deep in a method
  • Asked "should this go in the container?"

Constructor injection by default

final class CreateOrder
{
    public function __construct(
        private readonly OrderRepository $orders,
        private readonly EventDispatcher $events,
        private readonly Clock $clock,
    ) {}
}

Promoted constructor params (PHP 8.0+). Readonly (PHP 8.1+). Typed against interfaces, not concrete classes (testability).

What goes in the container

  • Stateless services with collaborators
  • Repositories, mailers, HTTP clients, event dispatchers
  • Configuration values (via env-bound bindings)

What does NOT go in the container

  • Value objects — construct directly with new. new EmailAddress($input).
  • Entities — produced by factories or repositories, not the container.
  • Per-request state — use a scoped binding or a context object, not singletons.

Service Locator is a code smell

// AVOID
public function handle(): void
{
    $service = app(SomeService::class); // service locator
    // ...
}

// PREFER
public function __construct(private readonly SomeService $service) {}
public function handle(): void
{
    $this->service->doIt();
}

The only legitimate use of a service locator is at the framework boundary (routing, command dispatch). Below that line: constructor injection.

Anti-patterns

  • Six+ constructor params → break the class apart (SRP)
  • new SomeService() inside business logic → not testable in isolation
  • Container binding that calls another binding which calls another → flatten the graph
  • Singletons that hold per-request state → race conditions

Testing

Constructor injection makes test fakes trivial:

$cmd = new CreateOrder(
    orders: new InMemoryOrderRepository(),
    events: new RecordingDispatcher(),
    clock: new FrozenClock('2026-01-15'),
);

No container, no mocks, no magic.

Install via CLI
npx skills add https://github.com/SanderMuller/project-boost --skill dependency-injection
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
SanderMuller
SanderMuller Explore all skills →