Understanding Dependency Injection & Container#

(Conceptual chapter – the “why”. Hands-on code lives in the other pages.)

What is Dependency Injection (DI), really?#

DI means a class receives (is injected) the things it needs to do its job instead of creating them internally. Less _new_ keywords → less coupling → easier change.

Without DI – brittle coupling#

class StoreService
{
    public function getStoreCoordinates(Store $store): array
    {
        $geo = new GoogleMaps();          // 🔴 hard-wired
        return $geo->getCoordinatesFromAddress($store->address());
    }
}

A future switch to OpenStreetMap touches every line that new’s GoogleMaps.

With DI – pluggable design#

interface GeolocationService { public function locate(string $addr): array; }

class GoogleMaps implements GeolocationService { /* … */ }
class OpenStreetMap implements GeolocationService { /* … */ }

class StoreService
{
    public function __construct(private GeolocationService $geo) {}

    public function getStoreCoordinates(Store $store): array
    {
        return $this->geo->locate($store->address());
    }
}

Business code knows only the contract (GeolocationService). Swap concrete classes at runtime, in tests, or by environment – no edits in StoreService.

Why use a container instead of manual wiring?#

  • Replaceability – bind interface → class once, change it centrally.

  • Testability – hand the container a fake/mock service in tests.

  • Single Source of Truth – definitions live in one place; fewer bootstraps scattered around.

  • Optional power-ups – autowiring, attributes, lazy loading, caching, scopes… when you need them.

InterMix in the picture#

  1. Register recipes (definitions, class bindings, factory closures).

  2. Resolve: $c->get(StoreService::class).

  3. InterMix analyses the constructor, asks itself “What fulfills GeolocationService right now?”, constructs the dependency (recursively if needed) once and hands back a ready-to-use StoreService.

Environment-based overrides#

One-liner switches for prod vs. local (or staging, CI, …):

$c->options()
   ->bindInterfaceForEnv('prod',  GeolocationService::class, GoogleMaps::class)
   ->bindInterfaceForEnv('local', GeolocationService::class, OpenStreetMap::class)
   ->setEnvironment($_ENV['APP_ENV'] ?? 'local');

Running in production will now build GoogleMaps; developers get OpenStreetMap automatically.

Take-away#

DI gives you **loosely coupled* code;* the container automates the wiring so you focus on behaviour, not plumbing. Ready to see it in action? Jump to Quick-Start for practical recipes.