Getting Started

Author Latest Version

Software License Build Status Coverage Status Quality Score Total Downloads

Key Features

  1. Simple API
  2. Interoperabiity. Container is an implementation of PSR-11.
  3. Speed. Because Container is simple, it is also very fast.
  4. Service Providers allow you to package code or configuration for packages that you reuse regularly.
  5. Inflectors allow you to manipulate objects resolved through the container based on the type.

Introduction

Container is dependency injection container. It allows you to implement the dependency injection design pattern meaning that you can decouple your class dependencies and have the container inject them where they are needed.

<?php declare(strict_types=1);

namespace Acme;

class Foo
{
    /**
     * @var \Acme\Bar
     */
    public $bar;

    /**
     * Construct.
     */
    public function __construct()
    {
        $this->bar = new Bar;
    }
}

class Bar {}

The class above Acme\Foo has a dependency on Acme\Bar, as it is written, this is a tightly coupled dependency meaning that every time Acme\Foo is instantiated, it takes it upon itself to also instantiate Acme\Bar.

By refactoring the code above to have the class accept it’s dependency as a constructor argument, we can loosen that dependency.

<?php declare(strict_types=1);

namespace Acme;

class Foo
{
    /**
     * @var \Acme\Bar
     */
    public $bar;

    /**
     * Construct.
     *
     * @param \Acme\Bar $bar
     */
    public function __construct(Bar $bar)
    {
        $this->bar = $bar;
    }
}

class Bar {}

Dependency injection at it’s core is as simple as that, and Container allows you to implement this in your applications.

Further Reading

I recommend reading the links below for further information about what problems dependency injection solve.

Installation

System Requirements

You need PHP >= 7.2.0 to use League\Container but the latest stable version of PHP is recommended.

Composer

Container is available on Packagist and can be installed using Composer:

composer require league/container

Most modern frameworks will include Composer out of the box, but ensure the following file is included:

<?php

// include the Composer autoloader
require 'vendor/autoload.php';

Going Solo

You can also use Container without using Composer by registering an autoloader function:

spl_autoload_register(function ($class) {
    $prefix = 'League\\Container\\';
    $base_dir = __DIR__ . '/src/';
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return;
    }
    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

Or, use any other PSR-4 compatible autoloader.

Basic Usage

Container allows you to register services, with or without their dependencies for later retrieval. It is a registry of sorts that when used correctly can allow you to implement the dependency injection design pattern.

Using the example in our introduction, we can start to take a look at how Container works. Now that Acme\Foo accepts Acme\Bar as a constructor argument, we can use Container to configure that.

<?php 

declare(strict_types=1);

$container = new League\Container\Container();

$container->add(Acme\Foo::class)->addArgument(Acme\Bar::class);
$container->add(Acme\Bar::class);

$foo = $container->get(Acme\Foo::class);

var_dump($foo instanceof Acme\Foo);      // true
var_dump($foo->bar instanceof Acme\Bar); // true

In the example above, we have registered both Acme\Foo and Acme\Bar with Container, we have also told Container that when we retrieve Acme\Foo we want to retrieve it with an argument of Acme\Bar.

When we ask Container to get(Acme\Foo::class), it knows to first get Acme\Bar and inject it as a constructor argument when instantiating Acme\Foo.

Aliases

We can make a slight adjustment to the code above so that we can use aliases to point to an actual class.

<?php 

declare(strict_types=1);

$container = new League\Container\Container();

$container->add('foo', Acme\Foo::class)->addArgument(Acme\Bar::class);
$container->add('bar', Acme\Bar::class);

$foo = $container->get('foo');

var_dump($foo instanceof Acme\Foo);      // true
var_dump($foo->bar instanceof Acme\Bar); // true

This is useful especially when depending on interfaces rather than concretions. We can refactor our original example to have it depend on an interface instead, and configure Container to inject the concrete implementation.

<?php declare(strict_types=1);

namespace Acme;

class Foo
{
    /**
     * @var \Acme\BarInterface
     */
    public $bar;

    /**
     * Construct.
     *
     * @param \Acme\BarInterface $bar
     */
    public function __construct(BarInterface $bar)
    {
        $this->bar = $bar;
    }
}

interface BarInterface {}
class BarA implements BarInterface {}
class BarB implements BarInterface {}

We now have Acme\Foo depending on an implementation of Acme\BarInterface (Acme\BarA or Acme\BarB).

<?php 

declare(strict_types=1);

$container = new League\Container\Container();

// Acme\Foo is added as normal but with an argument of Acme\BarInterface
$container->add(Acme\Foo::class)->addArgument(Acme\BarInterface::class);
// Acme\BarInterface is added as an alias with Acme\BarA as the concrete implementation,
// this could be swapped to Acme\BarB and that would be injected instead
$container->add(Acme\BarInterface::class, Acme\BarA::class);

$foo = $container->get(Acme\Foo::class);

var_dump($foo instanceof Acme\Foo);               // true
var_dump($foo->bar instanceof Acme\BarInterface); // true
var_dump($foo->bar instanceof Acme\BarA);         // true
var_dump($foo->bar instanceof Acme\BarB);         // false

Container has many more features and ways it can be configured to implement dependency injection, continue reading the documentation to find out more.

Questions?

Container was created by Phil Bennett. Find him on Twitter at @philipobenito.