DEV Community

Russell Jones
Russell Jones

Posted on • Originally published at jonesrussell.github.io on

PSR-11: Container Interface in PHP

PSR-11 defines a common interface for dependency injection containers in PHP. This standardization allows libraries to retrieve services from any container implementation, promoting better interoperability between different frameworks and libraries.

Understanding Dependency Injection Containers

A dependency injection container (DIC) is responsible for:

  1. Managing service definitions
  2. Creating service instances
  3. Resolving dependencies
  4. Managing object lifecycle

The Container Interface

<?php

namespace JonesRussell\PhpFigGuide\PSR11;

interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id
     * @return mixed
     * @throws NotFoundExceptionInterface
     * @throws ContainerExceptionInterface
     */
    public function get($id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     *
     * @param string $id
     * @return bool
     */
    public function has($id);
}

Enter fullscreen mode Exit fullscreen mode

Basic Implementation

Here’s a simple implementation of a dependency injection container that adheres to PSR-11:

<?php

namespace JonesRussell\PhpFigGuide\PSR11;

use JonesRussell\PhpFigGuide\PSR11\ContainerInterface;
use JonesRussell\PhpFigGuide\PSR11\NotFoundExceptionInterface;
use JonesRussell\PhpFigGuide\PSR11\ContainerExceptionInterface;

class SimpleContainer implements ContainerInterface
{
    private array $services = [];

    public function set(string $id, $service): void
    {
        $this->services[$id] = $service;
    }

    public function get($id)
    {
        if (!$this->has($id)) {
            throw new class extends \Exception implements NotFoundExceptionInterface {};
        }

        return $this->services[$id];
    }

    public function has($id): bool
    {
        return isset($this->services[$id]);
    }
}

// Example usage
$container = new SimpleContainer();
$container->set('database', new DatabaseConnection());
$database = $container->get('database');

Enter fullscreen mode Exit fullscreen mode

Advanced Usage of ExampleContainer

1. Registering Services with Dependencies

You can register services that depend on other services. For example, if you have a UserService that requires a DatabaseConnection, you can set it up like this:

<?php

class DatabaseConnection {
    public function connect() {
        return "Database connected!";
    }
}

class UserService {
    private DatabaseConnection $db;

    public function __construct(DatabaseConnection $db) {
        $this->db = $db;
    }

    public function getUser() {
        return "User data from " . $this->db->connect();
    }
}

// Example usage
$container = new SimpleContainer();
$container->set('database', new DatabaseConnection());
$container->set('userService', new UserService($container->get('database')));

$userService = $container->get('userService');
echo $userService->getUser(); // Output: User data from Database connected!

Enter fullscreen mode Exit fullscreen mode

2. Using Factory Functions

You can also register services using factory functions, which allows for more complex instantiation logic:

<?php

$container->set('userService', function (ContainerInterface $c) {
    return new UserService($c->get('database'));
});

// Example usage
$userService = $container->get('userService');
echo $userService->getUser(); // Output: User data from Database connected!

Enter fullscreen mode Exit fullscreen mode

Real-World Usage

1. Service Registration

<?php

// Define services
$container = new Container();

// Simple value
$container->set('api.key', 'secret-key-123');

// Factory function
$container->set('database', function (ContainerInterface $container) {
    return new PDO(
        $container->get('db.dsn'),
        $container->get('db.user'),
        $container->get('db.pass')
    );
});

// Service with dependencies
$container->set('userRepository', function (ContainerInterface $container) {
    return new UserRepository($container->get('database'));
});

Enter fullscreen mode Exit fullscreen mode

2. Service Retrieval

<?php

class UserController
{
    private UserRepository $users;

    public function __construct(ContainerInterface $container)
    {
        $this->users = $container->get('userRepository');
    }
}

Enter fullscreen mode Exit fullscreen mode

Framework Integration

1. Laravel Example

<?php

use Illuminate\Container\Container;
use JonesRussell\PhpFigGuide\PSR11\ContainerInterface;

class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
    public function register()
    {
        $this->app->bind(ContainerInterface::class, function ($app) {
            return new class($app) implements ContainerInterface {
                private $app;

                public function __construct($app)
                {
                    $this->app = $app;
                }

                public function get($id)
                {
                    return $this->app->make($id);
                }

                public function has($id): bool
                {
                    return $this->app->bound($id);
                }
            };
        });
    }
}

Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Service Resolution
// Bad - Service locator pattern
class UserService
{
    public function __construct(private ContainerInterface $container) {}

    public function doSomething()
    {
        $dep = $this->container->get('some.service');
    }
}

// Good - Explicit dependency injection
class UserService
{
    public function __construct(
        private SomeServiceInterface $someService
    ) {}
}

Enter fullscreen mode Exit fullscreen mode
  1. Container Configuration
// Bad - Runtime service definition
if ($condition) {
    $container->set('service', new ServiceA());
} else {
    $container->set('service', new ServiceB());
}

// Good - Configuration-driven definition
$container->set('service', function (ContainerInterface $c) {
    return $c->get('config')->get('use_service_a')
        ? new ServiceA()
        : new ServiceB();
});

Enter fullscreen mode Exit fullscreen mode

Next Steps

In our next post, we’ll explore PSR-14, which defines a standard event dispatcher interface. Check out our example repository for the implementation of these standards.

Resources

Baamaapii 👋

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay