2017 © Pedro Peláez
 

library container

An indie Service Container implementation based on Laravel Container

image

rock-symphony/container

An indie Service Container implementation based on Laravel Container

  • Sunday, December 3, 2017
  • by e1himself
  • Repository
  • 1 Watchers
  • 1 Stars
  • 244 Installations
  • PHP
  • 1 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 17 Versions
  • 5 % Grown

The README.md

RockSymphony Service Container

Build Status StyleCI, (*1)

An indie Service Container implementation based on Laravel Container., (*2)

Philosophy

Features

  • PHP 5.4+, PHP 7.0+
  • Automatic dependencies resolution
  • Dependency-injecting constructor calls
  • Dependency-injecting method calls

Usage

Installation

Use composer., (*3)

composer require rock-symphony/container:^2.0

Basics

Of course you can put services to container (->set()) and get them from it (->get()), as well as check if container has specific service (->has())., (*4)

<?php

use RockSymphony\ServiceContainer\ServiceContainer;

$container = new ServiceContainer();

// Definition:
// Set a service instance to container
$container->set('acme', new AcmeService());

// Consumer:
// Check if there is a service binding for given service ID  
echo $container->has('acme') ? 'It has acme service' : 'wtf?';

// Get a service from container
$acme = $container->get('acme');
$acme->doSomeStuff();

Using abstract interfaces

It's handy to bind services by their abstract interfaces to explicitly declare it's interface on both definition and consumer sides., (*5)

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
// Note we bind instance by it's **abstract** interface.
// This way you force consumers to not care about implementation details, but rely on interface. 
$container->set(\Psr\Log\LoggerInterface::class, $my_fancy_psr_logger_implementation);

// Consumer:
// Then you have a consumer that needs a logger implementation,
// but doesn't care on details. It can use any PSR-compatible logger.
$logger = $container->get(\Psr\Log\LoggerInterface::class);
$logger->info('Nice!');

Aliases

Sometimes you may also want to bind the same service by different IDs. You can use aliases for that (->alias()):, (*6)

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->alias('logger', \Psr\Log\LoggerInterface::class);

// Consumer:
$logger = $container->get(\Psr\Log\LoggerInterface::class);
// ... or 
$logger = $container->get('logger'); // 100% equivalent
$logger->info('Nice!');

Binding to a resolver function

You can declare a service by providing a resolver closure function (->bindResolver()). Service container will call that function every time you resolve service., (*7)

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->bindResolver('now', function () {
    return new DateTime();
});

// Consumer:
$now = $container->get('now'); // DateTime object
$another_now = $container->get('now'); // another DateTime object

echo $now === $another_now ? 'true' : 'false'; // == false

Deferred resolution service binding

You can defer service initialization until it is requested for the first time. A resolver function will be called just once and its result will be stored to service container., (*8)

It works similar to ->bindResolver(), but stores result after first invocation., (*9)

<?php
/** @var $container \RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->bindSingletonResolver('cache', function () {
    return new MemcacheCache('127.0.0.1');
});

// Consumer:
$cache = $container->get('cache'); // DateTime object
// do something with $cache

Extending a bound service

You can extend/decorate an existing service binding with ->extend() method., (*10)

<?php
use RockSymphony\ServiceContainer\ServiceContainer;

/** @var $container ServiceContainer */
// Definition:
$container->deferred('cache', function () {
    return new MemcacheCache('127.0.0.1');
}); 

// Wrap cache service with logging decorator
$container->extend('cache', function($cache, ServiceContainer $container) { 
    // Note: it's passing a service container instance as second parameter
    //       so you can get dependencies from it.
    return new LoggingCacheDecorator($cache, $container->get('logger'));
});

// Consumer:
$cache = $container->get('cache'); // DateTime object
// Uses cache seamlessly as before
// (implying that MemcacheCache and LoggingCacheDecorator have the same interface)

Isolated extension to service container

A use-case: you want to create a new container inheriting services from the existing one. But you don't want to re-define the services again, using the originally defined ones. Also you want to provide more services, without modifying the original container., (*11)

Think of it as JavaScript variables scopes: a nested scope inherits all the variables from parent scope. But defining new scope variables won't modify the parent scope. That's it., (*12)

$parent = new ServiceContainer();
$parent->set('configuration', $global_configuration);

$layer = new ServiceContainerLayer($existing_container);
$layer->set('configuration', $layer_configuration); 
$layer->bindResolver('layer_scope_service', ...);
// and so on

var_dump($parent->get('configuration') === $layer->get('configuration')); // "false"

Automatic dependency injection

Dependency-injecting construction

You can construct any class instance automatically injecting class-hinted dependencies from service container. It will try to resolve dependencies from container or construct them recursively resolving their dependencies., (*13)

<?php

// Class we need to inject dependencies into
class LoggingCacheDecorator {
    public function __construct(CacheInterface $cache, LoggerInterface $logger, array $options = []) {
        // initialize
    }
}

/** @var $container RockSymphony\ServiceContainer\ServiceContainer */
// Definition:
$container->set(LoggerInterface::class, $logger);
$container->set(CacheInterface::class, $cache);


// Consumer:
$logging_cache = $container->construct(LoggingCacheDecorator::class);
// you can also provide constructor arguments with second parameter:
$logging_cache = $container->construct(LoggingCacheDecorator::class, ['options' => ['level' => 'debug']]);

Dependency-injecting method call

You can call any callable automatically injecting dependencies from service container. It's primarily intended, but not limited, to call application HTTP controllers., (*14)

<?php
/** @var $container RockSymphony\ServiceContainer\ServiceContainer */

class MyController {
    public function showPost($url, PostsRepository $posts, TemplateEngine $templates)
    {
        // Warning! Pseudo-code :)
        $post = $posts->findPostByUrl($url);
        return $templates->render('post.html', ['post' => $post]); 
    }

    public static function error404(TemplateEngine $templates)
    {
        return $templates->render('404.html');
    }
}
// 1) It can auto-inject dependencies into instance method callables.
//    In this case it will check container for PostsRepository and TemplateEngine bindings.
//    Or try to create those instances automatically.
//    Or throw an exception if both options are not possible.
$container->call([$container, 'showPost'], ['url' => '/hello-world']);

// 2) It can construct class and auto-inject dependencies into method call:
//    Here it will first construct a new instance of MyController (see `->construct()`)
//    And then follows the same logic as the call 1) above.
$container->call('MyController@showPost', ['url' => '/hello-world']); 
// ... or the same: 
$container->call('MyController', ['url' => '/hello-world'], 'showPost');

// 3) It can auto-inject dependencies into static method call: 
$container->call(['MyController', 'error404']);
// ... or the same:
$container->call('MyController::error404');

// 4) It can auto-inject dependencies into closure function calls  
$container->call(function (PostsRepository $repository) {
    $repository->erase();
});

Note: Service container only resolves class-hinted arguments (i.e. arguments explicitly type-hinted to a class). You should provide required scalar arguments with second argument. It will use default value for options arguments (if you don't specify them)., (*15)

FAQ

  1. Why not use Laravel Container?, (*16)

    We were using Laravel Container for our project internally. But it's a bad candidate to link it as library as:, (*17)

    • It doesn't follow SemVer – BC breaks on every minor version bump
    • It has unneeded dependency to flooded illuminate/contracts
    • It's designed to be used as part of Laravel Framework, thus it's almost unusable as-a-library
    • You can use all laravel components only at certain version (i.e. all at 5.3; or all at 5.4; but not mixing)
    • If you want to move forward you are forced to upgrade to latest PHP version (i.e. container 5.4 requires PHP 7.0)
    • Bloated public API: 31 public API methods (vs 10 public methods in this library)
    • Questionable method naming: what's the difference between ->make() and ->build()?

License

This project is licensed under the terms of the MIT license., (*18)

The Versions

03/12 2017

dev-master

9999999-dev

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

03/12 2017

3.1.0

3.1.0.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

03/12 2017

dev-2.x-dev

dev-2.x-dev

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

03/12 2017

2.1.0

2.1.0.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

16/11 2017

3.0.2

3.0.2.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

16/11 2017

2.0.2

2.0.2.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

16/11 2017

dev-1.x-dev

dev-1.x-dev

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

16/11 2017

1.0.4

1.0.4.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

13/11 2017

2.0.1

2.0.1.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

13/11 2017

1.0.3

1.0.3.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

13/11 2017

3.0.1

3.0.1.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

11/11 2017

3.0.0

3.0.0.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

01/11 2017

2.0.0

2.0.0.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

31/10 2017

1.0.2

1.0.2.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

31/10 2017

1.0.1

1.0.1.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

12/03 2017

1.0

1.0.0.0

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk

12/03 2017

dev-analysis-X07GxM

dev-analysis-X07GxM

An indie Service Container implementation based on Laravel Container

  Sources   Download

MIT

The Requires

 

The Development Requires

by Ivan Voskoboinyk