moro/container7

Dependency Injection container for PHP-7

1.4.0 2018-05-08 16:26 UTC

README

Latest Version 1.4 Build Status Software License

Container7 is a medium Dependency Injection Container for PHP-7.

This package is compliant with PSR-1, PSR-2, PSR-4 and PSR-11. If you notice compliance oversights, please send a patch via pull request.

Features: providers, singletons, factories, parameters, aliases, tags, configuration, serialization.

Install

Via composer

$ composer require moro/container7

Requirements

  • PHP 7.1

Usage

Creating a container is a matter of creating a Container instance:

<?php
use Moro\Container7\Container;

$container = new Container();

As many other dependency injection containers, Container7 manages two different kind of data: services and parameters. Services receives from container by their class or alias. Parameters stores in their service "parameters".

<?php
if ($container->has(Service::class)) {
    $service = $container->get(Service::class);
}

$value = $container->get('parameters')->get('key');

For service definition used Service Providers.

<?php
$container->addProvider(SomeProvider::class);

Service Providers allow you to package code or configuration for packages that you reuse regularly.

Service provider

Any Service Provider is simple PHP class. Services are defined by methods that return an instance of an object. You must define results of this methods as class or interface. And then you can receive service by that class or interface.

Singleton

<?php
class SomeProvider {
    function someService(): Service {
        return new Service();
    }
}

Factory

If you want define factory - add variadic argument.

<?php
class SomeProvider {
    function someService(...$arguments): Service {
        return new Service($arguments[0]);
    }
}

Dependencies

When your service require other, you can add this dependency to method arguments.

<?php
class SomeProvider {
    function someService(ServiceBeta $beta): ServiceAlpha {
        return new ServiceAlpha($beta);
    }
}

Remember, that parameters is service too:

<?php
use Moro\Container7\Parameters;

class SomeProvider {
    function someService(Parameters $parameters): Service {
        return new Service($parameters->get('key'));
    }
}

Modifying Services after Definition

In some cases you may want to modify a service definition after it has been defined. You can use method that receive service object and can return null.

<?php
class SomeProvider {
    function extendService(Service $service) {
        $service->value = 'value'; // example
    }
}

If you need to replace the service instance use that definition:

<?php
class SomeProvider {
    function extendService(Service $service): ?Service {
        return new Decorator($service);
    }
}

You can add dependencies here as in the definition of factories.

Parameters

Add default parameters.

<?php
use Moro\Container7\Parameters;

class SomeProvider {
    function parameters(Parameters $parameters) {
        $parameters->set('key1', 'value1');
        $parameters->set('key2', '%key1%');
        $parameters->add('key0', ['new item']);
    }
    function someService(Parameters $parameters): Service {
        $service = new Service();
        $service->value = $parameters->get('key2');
        return $service;
    }
}

Set current values of parameters, that replace default values.

<?php
use Moro\Container7\Container;
use Moro\Container7\Parameters;

$parameters = new Parameters(['key1' => 'value2']);
$container = new Container($parameters);
$container->addProvider(SomeProvider::class);

And use it.

<?php
$service = $container->get(Service::class);
assert($service->value === 'value2');

Aliases

When you can not get service by class or interface, then you must define alias for class, interface or method.

<?php
use Moro\Container7\Aliases;

class SomeProvider {
    function someService(): Service {
        return new Service();
    }
    function aliases(Aliases $aliases) {
        // Add alias for unique interface in provider
        $aliases->add('kernel', Service::class);
        // or you can use method name
        $aliases->add('kernel', 'someService');
    }
}

Now you can get service by alias.

<?php
$service = $container->get('kernel');

Tags

You can group services by setting a common tag for them.

<?php
use Moro\Container7\Tags;

class SomeProvider {
    function tags(Tags $tags) {
        // Add tag for unique interface in provider
        $tags->add('someTag', ServiceAlpha::class);
        $tags->add('someTag', ServiceBeta::class);
        // or you can use method name
        $tags->add('someTag', 'getServiceAlpha');
        $tags->add('someTag', 'getServiceBeta');
    }
}

And then get a collection of the services.

<?php
$collection = $container->getCollection('someTag');

Collection implements Iterator interface and you can use it in foreach.

Manipulation with collection

<?php
use Moro\Container7\Container;

$container = new Container();
$collection = $container->getCollection('A');
// Collection contains services with tag "A".
$collection = $collection->merge('B');
// Collection contains services with tags "A" or "B".
$collection = $collection->exclude('A');
// Collection contains services with tag "B" and without tag "A".
$collection = $collection->merge('B')->with('C');
// The collection contains services that are marked
// with "B" and "C" tags simultaneously.

Configuration

Container7 support configuration files in JSON format. You can create provider from that files.

<?php
use Moro\Container7\Container;
use Moro\Container7\Parameters;

$configuration = Parameters::fromFile('conf.json');
$container = new Container($configuration);

Configuration files can be nested.

{
  "@extends": [
    "module1.json",
    "../module2.json"
  ]
}

Singleton

{
  "container": {
    "singletons": [
      {
        "interface": "ServiceInterface",
        "class": "Service"
      }
    ]
  }
}

Factory

{
  "container": {
    "factories": [
      {
        "interface": "ServiceInterface",
        "class": "Service"
      }
    ]
  }
}

Dependencies

{
  "container": {
    "singletons": [
      {
        "interface": "ServiceInterface",
        "class": "ServiceAlpha",
        "args": [
          "@ServiceBeta"
        ],
        "properties": {
          "value": 1
        },
        "calls": [
          {
            "method": "add",
            "args": [
              "key",
              "value"
            ]
          }
        ]
      }
    ]
  }
}

Modifying Services after Definition

{
  "container": {
    "extends": [
      {
        "target": "ServiceInterface",
        "calls": [
          {
            "method": "add",
            "args": [
              "key",
              "value"
            ]
          }
        ]
      }
    ]
  }
}

If you need to replace the service instance use that definition:

{
  "container": {
    "extends": [
      {
        "target": "ServiceInterface",
        "class": "Decorator",
        "args": [
          "$target"
        ]
      }
    ]
  }
}

Parameters, Aliases and Tags

{
  "container": {
    "parameters": {
      "key1": "value1",
      "key2": "%key1%"
    },
    "singletons": [
      {
        "aliases": ["kernel"],
        "tags": ["someTag"],
        "interface": "ServiceInterface",
        "class": "ServiceAlpha",
        "properties": {
          "value": "%key2%"
        }
      },
      {
        "tags": ["someTag"],
        "class": "ServiceBeta"
      },
      {
        "class": "Service",
        "args": [
          "$collections[someTag]"
        ]
      },
      {
        "class": "Service",
        "calls": [
          {
            "foreach": "$collections[someTag]",
            "method": "add",
            "args": ["$item"]
          }
        ]
      }
    ]
  }
}

Dynamic services

<?php
use Moro\Container7\Container;
use Moro\Container7\Provider;

class DynamicProvider {
    function boot(Container $container) {
        $configuration = [];
        // ... dynamic create of configuration array ...
        $container->addProvider(Provider::fromConfiguration(__METHOD__, $configuration));
    }
}

License

The MIT License (MIT). Please see License File for more information.