2017 © Pedro Peláez
 

library container

PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros

image

bhittani/container

PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros

  • Saturday, March 31, 2018
  • by bhittani
  • Repository
  • 1 Watchers
  • 0 Stars
  • 0 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 1 Versions
  • 0 % Grown

The README.md

PSR-11 Container

Build Status Packagist Downloads License, (*1)

PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros. This package does not require any external dependencies., (*2)

Install

You may install this package using composer., (*3)

$ composer require bhittani/container --prefer-dist

Usage

PSR-11 Implementation

This package implements the PSR-11 container interface, hence, you can easily swap any existing implementation with the container provided in this package., (*4)

Container

In its simplest form, the container stores key value pairs so that it can be accessed later during your application life cycle., (*5)

<?php

require_once __DIR__ . '/vendor/autload.php';

$container = new \Bhittani\Container\Container;

$container->add('foo', 'bar');

echo $container->get('foo'); // 'bar'

Binding resolution

Practically, a dependency injection container is more useful by storing class factories/instances so that they are automatically resolved., (*6)

<?php

require_once __DIR__ . '/vendor/autload.php';

class FooDatabase
{
    // ...
}

$container = new Bhittani\Container\Container;

$container->add(FooDatabase::class, new FooDatabase);

$db = $container->get(FooDatabase::class);

The key being used FooDatabase is significant. This key will act as a look-up against any class typehints during resolution attempts of binding parameters (in class constructors, methods, closures, callables, ..., etc)., (*7)

Still confused? Lets take a closer look at a practical example., (*8)

<?php

require_once __DIR__ . '/vendor/autload.php';

class FooDatabase
{
    // ...
}

class Query
{
    protected $db;

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

$container = new Bhittani\Container\Container;

$container->add(FooDatabase::class, new FooDatabase);

$query = $container->get(Query::class); // Query

Here, $db is automatically resolved by the container as it is type hinted with the 'FooDatabase' class which the container is aware of., (*9)

Automatic dependency resolution

Binding resolution is all handy and dandy but we can do much better and improve on our first iteration., (*10)

If we take a closer look at the previous code example, we see that we bind the FooDatabase class explicitly into the container but the Query class is implicitly resolved without any explicit binding., (*11)

This means, we should also be able to resolve the FooDatabase class implicitly., (*12)

Let's apply our first refactor., (*13)

<?php

require_once __DIR__ . '/vendor/autload.php';

class FooDatabase
{
    // ...
}

class Query
{
    protected $db;

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

$container = new Bhittani\Container\Container;

$query = $container->get(Query::class); // Query

In case you didn't notice, the code line $container->add(FooDatabase::class, new FooDatabase); is completely removed as no binding is required., (*14)

How does this work? Lets go behind the scenes for a moment to see what actually happens., (*15)

When you call the get method on the container,, (*16)

  1. The container identifies the key as a class that exists.
  2. It takes a peek into the constructor parameters and notices a parameter type-hinted as the class FooDatabase.
  3. In order to resolve this parameter, it repeats step 1 and 2 using the type-hint as the key.
  4. It doesn't see any constructor for the FooDatabase class so it instantiates it and uses that instance to instantiate the Query class.

A binding will take precedence over a new instantiation., (*17)

Interface resolution

Wouldn't it be nice if we could implement to an interface so that we could easily swap the underlying implementation?, (*18)

<?php

require_once __DIR__ . '/vendor/autload.php';

interface DatabaseInterface
{
    // ...
}

class FooDatabase implements DatabaseInterface
{
    // ...
}

class BarDatabase implements DatabaseInterface
{
    // ...
}

class Query
{
    public $db;

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

$container = new Bhittani\Container\Container;

$container->add(DatabaseInterface::class, new FooDatabase);

$query = $container->get(Query::class);

echo $query->db instanceof FooDatabase; // true

$container->add(DatabaseInterface::class, new BarDatabase);

$query = $container->get(Query::class);

echo $query->db instanceof BarDatabase; // true

We have easily swapped the underlying database implementation from FooDatabase to BarDatabase., (*19)

Callable resolution

To resolve a callable/closure, we can invoke it directly., (*20)

$container = new Bhittani\Container\Container;

class Acme
{
    // ...
}

$container->call(function (Acme $acme) {
    echo $acme instanceof Acme; // true
});

Custom parameter resolution

We can resolve entities that require custom arguments in two ways., (*21)

  1. Binding the custom argument into the container.
  2. Passing explicit arguments.
class Acme
{
    public $foo;

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

$container = new Bhittani\Container\Container;

$container->add('foo', 'bar');

$acme = $container->get(Acme::class);

echo $acme->foo; // bar

$acme = $container->get(Acme::class, ['foo' => 'baz']);

echo $acme->foo; // baz

Explicit arguments will take precedence over bindings., (*22)

Factory bindings

Factory bindings allow lazy loading of your instances. Which means that resolution will only occur when it is needed., (*23)

<?php

require_once __DIR__ . '/vendor/autload.php';

interface DatabaseInterface
{
    // ...
}

class FooDatabase implements DatabaseInterface
{
    // ...
}

class Query
{
    public function __construct(DatabaseInterface $db)
    {
        // ...
    }
}

$container = new Bhittani\Container\Container;

$container->add(DatabaseInterface::class, function () {
    return new FooDatabase;
});

$query = $container->get(Query::class); // will trigger the factory closure above.

This way, you can add as many bindings as you want in your container but only trigger/resolve them when needed. Hence, lazy loaded., (*24)

Shared bindings

Shared bindings allow the same instance to be resolved when accessed instead of a new instance every time it is needed., (*25)

$container = new Bhittani\Container\Container;

// This will be shared as we are not using a factory.
$container->add(DatabaseInterface::class, new FooDatabase);

// This will be shared as we are using the method 'share' explicitly.
$container->share(DatabaseInterface::class, function () {
    return new FooDatabase;
});

// This will NOT be shared as we are using a factory.
$container->add(DatabaseInterface::class, function () {
    return new FooDatabase;
});

Delegates

Delegated containers serve as fallback containers that are looked-up for binding resolutions when it can not be found in the container., (*26)

use Bhittani\Container\Container;

$container = new Container;

$container->has('foo'); // false

$delegateContainer = new Container; // Or any PSR-11 container.

$delegateContainer->add('foo', 'bar');

$container->delegate($delegateContainer);

$container->has('foo'); // true

Service providers

This package also ships with a service provider container which allows registering of service providers (Think of laravel service providers) in order to have a smooth and easy application development process., (*27)

In order to make use of service providers, we will work with a ServiceContainer instead of a simple Container., (*28)

use Bhittani\Container\ServiceContainer;
use Bhittani\Container\ServiceProvider;

class DatabaseServiceProvider extends ServiceProvider
{
    public function boot($container)
    {
        // This method will be called when all service providers are registered.
    }

    public function register($container)
    {
        $container->share(DatabaseInterface::class, function () {
            return new SqliteDatabase;
        });
    }
}

$container = new ServiceContainer;

$container->addServiceProvider(DatabaseServiceProvider::class);

$container->bootstrap();

$db = $container->get(DatabaseInterface::class);

echo $db instanceof SqliteDatabase; // true

Facades

Facades extend the container by assigning custom properties onto the service container. When accessed, it will be resolved out of the service container., (*29)

use Bhittani\Container\ServiceContainer;

$container = new ServiceContainer;

$container->share(DatabaseInterface::class, function () {
    return new SqliteDatabase;
});

$container->db = DatabaseInterface::class;

echo $container->db instanceof SqliteDatabase; // true

Macros

Macros extend the container by assigning custom methods onto the service container., (*30)

use Bhittani\Container\ServiceContainer;

$container = new ServiceContainer;

$container->share(DatabaseInterface::class, function () {
    return new SqliteDatabase;
});

$container->macro('query', function ($sql) {
    // $this will be set to the underlying ServiceContainer.
    return $this->get(DatabaseInterface::class)->query($sql);
});

echo $container->query('SELECT * FROM users'); // Invokes the 'query' macro.

Deferred Service Providers

Service providers can be deferred so that services can be lazy loaded., (*31)

use Bhittani\Container\ServiceContainer;
use Bhittani\Container\ServiceProvider;

class DatabaseServiceProvider extends ServiceProvider
{
    // A non empty $provides array will defer this service provider.
    protected $provides = [
        DatabaseInterface::class,
        // If setting a facade, use the facade key as the index.
        // 'db' => DatabaseInterface::class,
    ];

    // A non empty $macros array will defer this service provider as well.
    protected $macros = [
        'query',
    ];

    public function register($container)
    {
        $container->share(DatabaseInterface::class, function () {
            return new SqliteDatabase;
        });

        $container->macro('query', function ($sql) {
            return $this->get(DatabaseInterface::class)->query($sql);
        });
    }
}

$container = new ServiceContainer;

$container->addServiceProvider(DatabaseServiceProvider::class);

$container->bootstrap();

// This will register and boot the service provider.
$container->query('SELECT * FROM users');

Using deferred service providers is an efficient way to build up your application as these services will be lazy loaded and act as plug and play while having a minimimum impact on your application performance., (*32)

Changelog

Please see CHANGELOG for more information on what has changed., (*33)

Testing

git clone https://github.com/kamalkhan/container

cd container

composer install

composer test

Contributing

Please see CONTRIBUTING and CONDUCT for details., (*34)

Security

If you discover any security related issues, please email shout@bhittani.com instead of using the issue tracker., (*35)

Inspiration

Credits

License

The MIT License (MIT). Please see the License File for more information., (*36)

, (*37)

The Versions

31/03 2018

dev-master

9999999-dev https://github.com/kamalkhan/container

PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros

  Sources   Download

MIT

The Requires

 

The Development Requires

container ioc dependency di injection macros facades psr-11 service-provider