Skip to content

PHP PSR-11 Container Interface — Complete Guide to Dependency Injection

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about PHP PSR. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP PSR-11 ContainerInterface provides a standard get() and has() interface for dependency injection containers, enabling framework-agnostic service location and dependency resolution.

What You'll Learn

By the end of this tutorial, you'll implement PSR-11 containers, use service providers, configure auto-wiring, understand service definition patterns, and integrate containers with frameworks.

Why PSR-11 Matters

Before PSR-11, every framework had its own container interface. PSR-11 enables portable libraries that request dependencies from any compatible container.

Real-World Use

A PHP package for file uploads requires a PSR-11 container to resolve its dependencies (logger, storage adapter). It works with Symfony DI, PHP-DI, Laravel container, or any PSR-11 container.

PSR-11 Path

flowchart LR
  A[PSR-7 HTTP] --> B[PSR-11 Container]
  B --> C[PSR-14 Events]
  B --> D[PSR-15 Middleware]
  C --> E[PSR-17 HTTP Factory]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

ContainerInterface Basics

PSR-11 defines two methods: get($id) and has($id).

<?php
use Psr\Container\ContainerInterface;
class Application {
    public function __construct(
        private ContainerInterface $container
    ) {}
    public function handleRequest(): void {
        if ($this->container->has(UserService::class)) {
            $service = $this->container->get(UserService::class);
            $service->listUsers();
        }
    }
}

Simple Container Implementation

A basic PSR-11 container using service definitions.

<?php
use Psr\Container\ContainerInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class SimpleContainer implements ContainerInterface {
    private array $services = [];
    private array $instances = [];
    public function set(string $id, callable $factory): void {
        $this->services[$id] = $factory;
    }
    public function get(string $id): mixed {
        if (!$this->has($id)) {
            throw new class("Service {$id} not found") extends \RuntimeException implements NotFoundExceptionInterface {};
        }
        if (!isset($this->instances[$id])) {
            $this->instances[$id] = ($this->services[$id])($this);
        }
        return $this->instances[$id];
    }
    public function has(string $id): bool {
        return isset($this->services[$id]);
    }
}

Auto-Wiring Container

An auto-wiring container resolves dependencies through reflection.

<?php
class AutoWiringContainer implements ContainerInterface {
    private array $instances = [];
    private array $definitions = [];
    public function set(string $id, mixed $definition): void {
        $this->definitions[$id] = $definition;
    }
    public function get(string $id): mixed {
        if (!isset($this->instances[$id])) {
            $this->instances[$id] = $this->resolve($id);
        }
        return $this->instances[$id];
    }
    public function has(string $id): bool {
        return class_exists($id) || isset($this->definitions[$id]);
    }
    private function resolve(string $id): mixed {
        if (isset($this->definitions[$id])) {
            $definition = $this->definitions[$id];
            return is_callable($definition) ? $definition($this) : $definition;
        }
        $ref = new ReflectionClass($id);
        $constructor = $ref->getConstructor();
        if (!$constructor) return $ref->newInstance();
        $params = array_map(fn($p) => $this->get($p->getType()->getName()), $constructor->getParameters());
        return $ref->newInstanceArgs($params);
    }
}

Service Providers

Organize container configuration into service providers.

<?php
interface ServiceProviderInterface {
    public function register(ContainerInterface $container): void;
}
class LoggerServiceProvider implements ServiceProviderInterface {
    public function register(ContainerInterface $container): void {
        $container->set(LoggerInterface::class, function ($c) {
            return new Monolog\Logger("app", [new Monolog\Handler\StreamHandler("/var/log/app.log")]);
        });
    }
}
class DatabaseServiceProvider implements ServiceProviderInterface {
    public function register(ContainerInterface $container): void {
        $container->set(PDO::class, function ($c) {
            return new PDO(getenv("DB_DSN"), getenv("DB_USER"), getenv("DB_PASS"));
        });
    }
}

Container Interop with Frameworks

PSR-11 containers work with Symfony, Laravel, and Slim.

<?php
// Symfony DI already implements ContainerInterface
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->autowire(UserService::class);
$service = $container->get(UserService::class);

Common Mistakes

1. Using get() Without Checking has()

Calling get() on a non-existent service throws NotFoundException. Always check has() first.

2. Storing Container as a Service Locator

Inject ContainerInterface only in service providers. Inject specific dependencies in application code.

3. Forgetting Shared vs Non-Shared Services

get() typically returns the same instance each time. For new instances, add a Factory method.

4. Circular Dependencies

A depends on B, B depends on A causes infinite Recursion. Detect and report circular references.

5. Not Catching ContainerExceptionInterface

Container operations can throw exceptions. Catch container-specific exceptions for error handling.

Practice Questions

1. What two methods does PSR-11 define?

get($id) returns a service, has($id) checks if a service is registered.

2. What exceptions can get() throw?

NotFoundExceptionInterface if the service is not found. ContainerExceptionInterface for other errors.

3. What is auto-wiring?

Automatic dependency resolution through reflection on constructor parameter types.

4. Should you inject the container or specific dependencies?

Inject specific dependencies. The container should only be used in configuration and Bootstrap code.

5. Challenge: Implement a PSR-11 container with factory support.

<?php
use Psr\Container\ContainerInterface;
class FactoryContainer implements ContainerInterface {
    private array $factories = [];
    private array $instances = [];
    public function factory(string $id, callable $factory): void {
        $this->factories[$id] = $factory;
    }
    public function get(string $id): mixed {
        if (!isset($this->instances[$id])) {
            if (!$this->has($id)) throw new \RuntimeException("Not found: {$id}");
            $this->instances[$id] = ($this->factories[$id])($this);
        }
        return $this->instances[$id];
    }
    public function has(string $id): bool {
        return isset($this->factories[$id]);
    }
}

FAQ

What is the difference between PSR-11 and PHP-DI?

PSR-11 is an interface standard. PHP-DI is an implementation that implements PSR-11.

Can I use PSR-11 without a framework?

Yes. Implement the interface yourself or use a standalone container like PHP-DI.

How do I handle shared services?

Cache the resolved instance in the container. get() should return the same instance by default.

What is a service provider?

A class that registers services into a container, grouping related configuration.

Is it safe to type-hint ContainerInterface?

Yes. Type-hinting against the interface ensures you can swap container implementations.

Mini Project: Configuration-Aware Container

Build a container that reads service definitions from a PHP config file.

<?php
use Psr\Container\ContainerInterface;
class ConfigContainer implements ContainerInterface {
    private array $services = [];
    private array $instances = [];
    public function __construct(string $configFile) {
        $definitions = require $configFile;
        foreach ($definitions as $id => $definition) {
            $this->services[$id] = $definition;
        }
    }
    public function get(string $id): mixed {
        if (!isset($this->instances[$id])) {
            $definition = $this->services[$id] ?? throw new \RuntimeException("Service {$id} not found");
            $this->instances[$id] = is_callable($definition) ? $definition($this) : $definition;
        }
        return $this->instances[$id];
    }
    public function has(string $id): bool {
        return isset($this->services[$id]);
    }
}

What's Next

PHP PSR-14 Event Dispatcher PHP PSR-15 Middleware PHP PSR-17 HTTP Factory

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro