Pimple-like DI container library.

0.2.1 2021-06-05 23:28 UTC

This package is auto-updated.

Last update: 2023-09-06 05:03:27 UTC


README

Latest Version on Packagist Software License Build Status Coverage Status Code Climate Total Downloads

Description

knot-lib/Di is a Pimple style dependency injection(DI) container library.

Pimple

Feature

  • Pimple style API
  • Lazy creation for better performance
  • Multi container builders
  • Protecting or restricting component
  • Cacheable
  • Dynamic code generation for bootstrap
  • Dependency injection:
    • autowired
    • constructor,setter,field injection
    • scope: singleton/prototype
    • custom annotation:
      • @Inject
      • @Scope
      • @Singleton
      • @Named
      • @Prototype

Requirement

PHP 7.2 or later

Installing knot-lib/di

The recommended way to install knot-lib/di is through Composer.

composer require knot-lib/di

After installing, you need to require Composer's autoloader:

require 'vendor/autoload.php';

Usage

1.Building Container

Container will be created by builder class or directly created. You can choose the way of creation container as you like.
There are several method of container building:

  • Simple(no container builder)
  • Bootstrap Container Builder
  • Compiler Container Builder
  • Modular Container Builder
  • Cached Compiler Container Builder

Simple(no container builder)

You can build container without any built-in container builder.
This method is simple and fastest, but container initializing code will be complex if you use many components.

use knot-lib\Di\Container;
use Sample\Person;
 
$container = Container();
$container['david'] = function(Container $c){
   return new Person('David Smith', 24, 179.2, function($target){ return 'throws '.$target.'.'; });
};
 
$david = $container['david'];
$david->sayHello();        // David Smith says "Hello! My name is David Smith. I am 24 years old."

Bootstrap Container Builder

Botstrap container builder will setup a container by code written in callback function(bootstrap). This method is very fast, but may be redundant.

use knot-lib\Di\Container;
use knot-lib\Di\BootstrapContainerBuilder;
use Sample\Person;
  
$bootstrap = function(Container $c){
       $c['david'] = function(Container $c){
           return new Person('David Smith', 24, 179.2, function($target){ return 'throws '.$target.'.'; });
       };
   };
$bulder = new BootstrapContainerBuilder($bootstrap);
$container = $bulder->build();
  
$david = $container['david'];
$david->sayHello();        // David Smith says "Hello! My name is David Smith. I am 24 years old."

Compiler Container Builder

Compiler container builder will setup a container by configuration file(supports .json/.yml/.php). This container builder generates bootstrap code dynamically in cache directory, so container activation time will be greatly reduced after the second time.

sample.php
use knot-lib\Di\CompilerContainerBuilder;
use knot-lib\Config\Util\ConfigFileUtil;
  
$config = ConfigFileUtil::loadConfigFromFile('components.json');
$container = (new CompilerContainerBuilder($config))->build();
  
$david = $container['david'];
$david->sayHello();        // David Smith says "Hello! My name is David Smith. I am 24 years old."
components.json
{
  "compiler": {
    "vendor_dir": "../vendor",
    "autoloaders": [
      "../sample_autoloader2.php"
    ]
  },
  
  "cache": {
    "root": "../cache",
    "filename": "demo_di"
  },
 
  "components": [
    {
      "id": "david",
      "type": "object",
      "class_name": "Sample\\Person",
      "injections": [
        {
          "new": [ "David Smith", "@int: 24", "@float: 179.2", "@func: function($target){ return 'throws '.$target.'.'; }" ]
        }
      ]
    }
  ]
}
@ directive

As above config file(components.json), you an use '@xxx' direcive in component config file.
There are '@xxx' direcives below.

Directive Replaced Sample
@id: [component_id] other component identified by [component_id] @id: logger
@func: [function_code] [function_code] @func: function($a) { echo $s; }
@int: [integer_value] [integer_value] @int: 100
@float: [float_value] [float_value] @float: 3.14
@bool: [bool_value] [bool_value]('yes' or '1' or 'on' regarded as true) @bool: yes
@bool: true
@json: [json_data] decoded json @json: { 'age': '21', 'name': 'david' }
@env: [key] $_ENV value($_ENV['key']) @env: USER

Modular Container Builder

Modular container builder will setup a container by installing "container module". Container module is a set of container initializing code.
BootstrapContainerBuilder provide only one set of initializing code, but ModularContainerBuilder accepts multiple modules if you need.

use knot-lib\Di\Container;
use knot-lib\Di\ModularContainerBuilder;
use knot-lib\Di\ContainerModuleInterface;
use Sample\Person;
 
class DavidModule implements ContainerModuleInterface
{
    public function install(Container $container)
    {
        $container['david'] = function(){
           return new Person('David Smith', 24, 179.2, function($target){ return 'throws '.$target.'.'; });
        };
    }
}
 
$builder = new ModularContainerBuilder([ new DavidModule() ]);
$container = $builder->build();
 
$david = $container['david'];
$david->sayHello();        // David Smith says "Hello! My name is David Smith. I am 24 years old."

2.Using Container

get

// get service
$service = $container['my-service'];
 
// do something
$service->doSomething();

set/unset

You can set instance or value to container in the same way. Also you can set factory code for generate instance or constant value for lazy creation.

// set object
$container['my-favorite-fruits'] = new MyService();
 
// set object factory(lazy creation)
$container['my-service'] = function(){
    return new MyService();
};
  
// set constant value
$container['my-favorite-fruits'] = [ 'apple', 'banana' ];
  
// set constant value factory(lazy creation)
$container['my-service'] = function(){
    return [ 'apple', 'banana' ];
};
 
// unset entry
unset($container['my-favorite-fruits']);

slot

Slot is information about container component. You can lock or restrict component via slot object.

// get slot object
$slot = $contianer->slot('something');
 
// lock the slot(in this case, 'something' slot will be locked)
$slot->lock();
 
// type restriction
$slot->mustBeTypeOf('array');
 
// instance-of restriction
$slot->mustBeInstanceOf('MyServiceInterface');

Tips

getting new instance for each access

By default, container returns existing object after second access. But you can retrieve new object for each access by calling Container#newInstance() method.

$cart = $container['cart'];
echo 'count:' . $cart->countItems();   // count: 0
$cart->addItem('apple');
echo 'count:' . $cart->countItems();   // count: 1
 
$cart = $container['cart'];
echo 'count:' . $cart->countItems();   // count: 1
 
$cart = $container->newInstance('cart');
echo 'count:' . $cart->countItems();   // count: 0

Protect component

You can protect component for using Slot#lock() method.

$container['cart'] = new MyCart();
 
// lock slot
$container->slot('cart')->lock();
 
// this will fail becasue the slot is locked
$container['cart'] = new AnotherCart();     // throws SlotIsLockedException

Restrict component type

You can restrict component type for using Slot#mustBeTypeOf() method.

$container['something'] = new MyCart();
 
// restrict slot to array value
$container->slot('something')->mustBeTypeOf('array');
 
// this will fail because AnotherCart is not array
$container['something'] = new AnotherCart();     // throws SlotTypeRestrictionViolationException
 
// array value can be set
$container['something'] = [ 'apple', 'banana' ];

Restrict component class or interface

You can restrict component class or interface for using Slot#mustBeInstanceOf() method.

$container['something'] = new MyCart();
 
// restrict slot to array value
$container->slot('something')->mustBeInstanceOf('MyServiceInterface');
 
// this will fail because AnotherCart does not implement MyServiceInterface
$container['something'] = new AnotherCart();     // throws SlotInstanceOfRestrictionViolationException
 
// interface implemented instance can be set in this slot
$container['something'] = new MyService;        // class MyService implements MyServiceInterface

Extending component

You can extend component by using Container#extend() method.

// register my service
$container['my_service'] = new MyService();
 
// extend my serice(set max item count)
$container->extend('my_service', function($my_service) {
    $my_service->setMaxItems(10);
});

echo 'max: ' . $container['my_service']->getMaxItems();       // max: 10
 
// register array
$container['my_favorite_fruits'] = ['apple'];
 
// add banana to my favorites array
$container->extend('my_favorite_fruits', function(&$favorites) {
    $favorites[] = 'banana';
});

echo implode(', ',$container['my_favorite_fruits']);     // apple, banana

Compiler config file format & parameters

Compiler config file can be written in json, YAML, and PHP format. All parameters mest begin with array element.

Component config file: json format sample
{
  "compiler": {
    "vendor_dir": "../vendor",
    "autoloaders": [
      "../sample_autoloader2.php"
    ]
  },
  
  "cache": {
    "root": "../cache",
    "filename": "demo_di"
  },
 
  "slots": [
    {
      "id": "david",
      "type": "object",
      "instanceOf": "\\Sample\\PersonInterface",
      "locked": true
    }
  ],
 
  "components": [
    {
      "id": "david",
      "type": "object",
      "class_name": "Sample\\Person",
      "injections": [
        {
          "new": [ "David Smith", "@int: 24", "@float: 179.2", "@func: function($target){ return 'throws '.$target.'.'; }" ]
        }
      ]
    }
  ]
}
Component config file: YAML format sample
compiler:
  vendor_dir: "../vendor"
  autoloaders:
      "../sample_autoloader2.php"
  
cache:
  root: "../cache",
  filename: demo_di

slots:
  - id: david,
    type: object,
    instanceOf: \Sample\PersonInterface,
    locked: true

components:
  - id: david,
    type: object,
    class_name: Sample\Person,
    injections": 
    new: 
      - "David Smith"
      - "@int: 24"
      - "@float: 179.2"
      - "@func: function($target){ return 'throws '.$target.'.'; }"
Component config file: PHP format sample
<?php
return [
    'compiler' => [
        'vendor_dir' => '../vendor',
        'autoloaders' => [ '../sample_autoloader2.php' ],
    ],
    'cache' => [
        'root' => '../cache',
        'filename' => 'demo_di',
    ],
    'slots' => [
        [
            'id' => 'david',
            'type' => 'object',
            'instanceOf' => '\Sample\PersonInterface',
            'locked' => true,
        ]
    ],
    'components' => [
        [
            'id' => 'david',
            'type' => 'object',
            'class_name' => 'Sample\Person',
            'injections' => [
                'new' => [
                    "David Smith",
                    "@int: 24",
                    "@float: 179.2",
                    "@func: function(\$target){ return 'throws '.\$target.'.'; }",
                ]
            ]        
        ]
    ],
];
Component config file: parameters
Parameter Mandatory Explain Sample
compiler/vendor_dir Yes Indicates composer vendor directory path vendor_dir: "../vendor"
compiler/autoloaders No Indicates user defined autoloaders file. In the file, use spl_autoload_register globally to register your own autoloader. autoloaders: "../my_autoloader.php"
cache/root No Indicates cache directory. Compiler will output bootstrap PHP code in this directory. root: "../cache"
cache/filename No Indicates file basename of bootstrap cache code. The fulle name of cache file will be generated by adding postfix '.php' filename: "my_bootstrap"
slots No Indicates slot array, which is attached to container component to restrict it. See slot item parameters -
components Yes Indicates component array. See component item parameters -
Component config file: slot item parameters
Parameter Explain Sample
id Indicates slot id id: "my_service"
type Indicates type of slot type: object
instanceOf Indicates class or intarface which component implements or inherit instanceOf: MyServiceInterface
locked Indicates slot is locked. If slot is locked, you can not overwrite it. locked: true
Component config file: component item parameters
Parameter Explain Sample
id Indicates component id id: "my_service"
type Indicates type of component type: object
class Indicates class of component instanceOf: MyService
injections Indicates array of injections. See [component injections parameter](#component_injections) locked: true
Component config file: component injections parameter
Parameter Value Type Explain Sample
new array Indicates constructor injection. Value will be passed to constructor new: [ "param1", "param2" ]
method string Indicates method injection. Value means method name. method: doSomething
property string Indicates property injection. Value means property name. The property must be public. property: tax_ratio
params array Indicates parameters of method injection. Value must be array. params: [ "pram1", "param2" ]
value int
string
bool
float
null
array
Indicates value of property injection. value: "tomato"

Requirement

PHP 7.1 or later

Installing knot-lib/di

The recommended way to install knot-lib/di is through Composer.

composer require knot-lib/di

After installing, you need to require Composer's autoloader:

require 'vendor/autoload.php';

License

This library is licensed under the MIT license.

Author

stk2k

Disclaimer

This software is no warranty.

We are not responsible for any results caused by the use of this software. Please use the responsibility of the your self.