Yii Dependency Injection PSR-11 compatible

Overview

Yii Dependency Injection


Latest Stable Version Total Downloads Build status Scrutinizer Code Quality Code Coverage Mutation testing badge static analysis type-coverage

PSR-11 compatible dependency injection container that is able to instantiate and configure classes resolving dependencies.

Features

  • PSR-11 compatible.
  • Supports property injection, constructor injection and method injection.
  • Detects circular references.
  • Accepts array definitions. Could be used with mergeable configs.
  • Provides optional autoload fallback for classes without explicit definition.
  • Allows delegated lookup and has composite container.
  • Supports aliasing.
  • Supports service providers.
  • Has state resetter for long-running workers serving multiple requests such as RoadRunner or Swoole.
  • Supports container delegates.

Requirements

  • PHP 7.4 or higher.
  • Multibyte String PHP extension.

Installation

The package could be installed with composer:

composer require yiisoft/di --prefer-dist

Using the container

Usage of the DI container is fairly simple: You first initialize it with an array of definitions. The array keys are usually interface names. It will then use these definitions to create an object whenever that type is requested. This happens for example when fetching a type directly from the container somewhere in the application. But objects are also created implicitly if a definition has a dependency to another definition.

Usually a single container is used for the whole application. It is often configured either in the entry script such as index.php or a configuration file:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions($definitions);

$container = new Container($config);

The definitions could be stored in a .php file that returns an array:

return [
    EngineInterface::class => EngineMarkOne::class,
    'full_definition' => [
        'class' => EngineMarkOne::class,
        '__construct()' => [42], 
        '$propertyName' => 'value',
        'setX()' => [42],
    ],
    'closure' => fn (SomeFactory $factory) => $factory->create('args'),
    'static_call_preferred' => fn () => MyFactory::create('args'),
    'static_call_supported' => [MyFactory::class, 'create'],
    'object' => new MyClass(),
];

As seen above an object can be defined in several ways:

  • In the simple case an interface definition maps an id to a particular class.
  • A full definition describes how to instantiate a class in more detail:
    • class contains the name of the class to be instantiated.
    • __construct() holds an array of constructor arguments.
    • The rest of the config are property values (prefixed with $) and method calls, postfixed with (). They are set/called in the order they appear in the array.
  • Closures are useful if instantiation is tricky and can better be described in code. When using these, arguments are auto-wired by type. ContainerInterface could be used to get current container instance.
  • If it is even more complicated, it is a good idea to move such code into a factory and reference it as a static call.
  • While it is usually not a good idea, you can also set an already instantiated object into the container.

See yiisoft/definitions for more information.

After the container is configured, a service can be obtained via get():

/** @var \Yiisoft\Di\Container $container */
$object = $container->get('interface_name');

Note, however, that it is bad practice using a container directly. It is much better to rely on autowiring as provided by the Injector available from the yiisoft/injector package.

Using aliases

The DI container supports aliases via the Yiisoft\Definitions\Reference class. This way objects can also be retrieved by a more handy name:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        'engine_one' => EngineInterface::class,
    ]);

$container = new Container($config);
$object = $container->get('engine_one');

Composite containers

A composite container combines multiple containers in a single container. When using this approach, objects should only be fetched from the composite container.

use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$composite = new CompositeContainer();

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);
$carContainer = new Container($carConfig);

$bikeConfig = ContainerConfig::create()
    ->withDefinitions([
        BikeInterface::class => Bike::class
    ]);

$bikeContainer = new Container($bikeConfig);
$composite->attach($carContainer);
$composite->attach($bikeContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `Bike` class.
$bike = $composite->get(BikeInterface::class);

Note, that containers attached earlier override dependencies of containers attached later.

use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$carConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        CarInterface::class => Car::class
    ]);

$carContainer = new Container($carConfig);

$composite = new CompositeContainer();
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkOne` class.
$engine = $car->getEngine();

$engineConfig = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkTwo::class,
    ]);

$engineContainer = new Container($engineConfig);

$composite = new CompositeContainer();
$composite->attach($engineContainer);
$composite->attach($carContainer);

// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkTwo` class.
$engine = $composite->get(EngineInterface::class);

Using service providers

A service provider is a special class that is responsible for providing complex services or groups of dependencies for the container and extensions of existing services.

A provider should extend from Yiisoft\Di\ServiceProviderInterface and must contain a getDefinitions() and getExtensions() methods. It should only provide services for the container and therefore should only contain code that is related to this task. It should never implement any business logic or other functionality such as environment bootstrap or applying changes to database.

A typical service provider could look like:

use Yiisoft\Di\Container;
use Yiisoft\Di\ServiceProviderInterface;

class CarFactoryProvider extends ServiceProviderInterface
{
    public function getDefinitions(): array
    {
        return [
            CarFactory::class => [
                'class' => CarFactory::class,
                '$color' => 'red',
            ], 
            EngineInterface::class => SolarEngine::class,
            WheelInterface::class => [
                'class' => Wheel::class,
                '$color' => 'black',
            ],
            CarInterface::class => [
                'class' => BMW::class,
                '$model' => 'X5',
            ],
        ];    
    }
     
    public function getExtensions(): array
    {
        return [
            // Note that Garage should already be defined in container 
            Garage::class => function(ContainerInterface $container, Garage $garage) {
                $car = $container->get(CarFactory::class)->create();
                $garage->setCar($car);
                
                return $garage;
            }
        ];
    } 
}

Here we created a service provider responsible for bootstrapping of a car factory with all its dependencies.

An extension is a callable that returns a modified service object. In our case we get existing Garage service and put a car into the garage by calling the method setCar(). Thus, before applying this provider, we had an empty garage and with the help of the extension we fill it.

To add this service provider to a container you can pass either its class or a configuration array in the additional config:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withProviders([CarFactoryProvider::class]);

$container = new Container($config);

When a service provider is added, its getDefinitions() and getExtensions() methods are called immediately both services and their extensions get registered into the container.

Container tags

You can tag services in the following way:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
            'tags' => ['car'], 
        ],
        RedCarService::class => [
            'definition' => fn () => new RedCarService(),
            'tags' => ['car'],
        ],
    ]);

$container = new Container($config);

Now we can get tagged services from the container in the following way:

$container->get('tag@car');

The result is an array that contains two instances: BlueCarService and RedCarService.

Another way to tag services is setting tags via container constructor:

[BlueCarService::class, RedCarService::class] ]); $container = new Container($config);">
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([  
        BlueCarService::class => [
            'class' => BlueCarService::class,
        ],
        RedCarService::class => fn () => new RedCarService(),
    ])
    ->withTags([
        // "car" tag has references to both blue and red cars
        'car' => [BlueCarService::class, RedCarService::class]
    ]);

$container = new Container($config);

Resetting services state

Despite stateful services is not a great practice, these are inevitable in many cases. When you build long-running applications with tools like Swoole or RoadRunner you should reset the state of such services every request. For this purpose you can use StateResetter with resetters callbacks:

$resetter = new StateResetter();
$resetter->setResetters([
    MyServiceInterface::class => function () {
        $this->reset(); // a method of MyServiceInterface
    },
]);

The callback has access to the private and protected properties of the service instance, so you can set initial state of the service efficiently without creating a new instance.

The reset itself should be triggered after each request-response cycle. For RoadRunner it would look like the following:

while ($request = $psr7->acceptRequest()) {
    $response = $application->handle($request);
    $psr7->respond($response);
    $application->afterEmit($response);
    $container->get(\Yiisoft\Di\StateResetter::class)->reset();
    gc_collect_cycles();
}

Setting resetters in definitions

Reset state is defined for each individual service by providing "reset" callback in the following way:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkOne::class,
        EngineMarkOne::class => [
            'class' => EngineMarkOne::class,
            'setNumber()' => [42],
            'reset' => function () {
                $this->number = 42;
            },
        ],
    ]);

$container = new Container($config);

Note: resetters from definitons work only if you don't set StateResetter in definition or service providers.

Configuring StateResetter manually

To manually add resetters or in case you use Yii DI composite container with a third party container that does not support state reset natively, state resetter could be configured separately. The following example is PHP-DI:

MyServiceInterface::class => function () {
    // ...
},
StateResetter::class => function () {
    $resetter = new StateResetter();
    $resetter->setResetters([
        MyServiceInterface::class => function () {
            $this->reset(); // a method of MyServiceInterface
        },
    ]);
    return $resetter;
}

Specifying metadata for non-array definitions

In order to specify some metadata, such as in cases of "resetting services state" or "container tags" above, for non-array definitions, the following syntax could be used:

LogTarget::class => [
    'definition' => static function (LoggerInterface $logger) use ($params) {
        $target = ...
        return $target;
    },
    'reset' => function () use ($params) {
        ...
    },
],

In the above we have explicitly moved definition itself to "definition" key.

Delegates

Container delegates define. Each delegate is a callable returning a container instance that is used in case a service can not be found in primary container:

function (ContainerInterface $container): ContainerInterface
{

}

In order to configure delegates use additional config:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()->withDelegates([
    function (ContainerInterface $container): ContainerInterface {
        // ...
    }
]);


$container = new Container($config);

Tuning for production

By default, the container validates definitions right when they are set. In production environment, it makes sense to turn it off:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withValidate(false);

$container = new Container($config);

Strict mode

Container may work in strict mode, i.e. when everything in the container should be defined explicitly. In order to turn it on use the following code:

use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()
    ->withStrictMode(true);

$container = new Container($config);

Further reading

Benchmarks

To run benchmarks execute the next command

composer require phpbench/phpbench $ ./vendor/bin/phpbench run

Note: Only works for php 7.4.

Result example

\Yiisoft\Di\Tests\Benchmark\ContainerBench

benchConstructStupid....................I4 [μ Mo]/r: 438.566 435.190 (μs) [μSD μRSD]/r: 9.080μs 2.07%
benchConstructSmart.....................I4 [μ Mo]/r: 470.958 468.942 (μs) [μSD μRSD]/r: 2.848μs 0.60%
benchSequentialLookups # 0..............R5 I4 [μ Mo]/r: 2,837.000 2,821.636 (μs) [μSD μRSD]/r: 34.123μs 1.20%
benchSequentialLookups # 1..............R1 I0 [μ Mo]/r: 12,253.600 12,278.859 (μs) [μSD μRSD]/r: 69.087μs 0.56%
benchRandomLookups # 0..................R5 I4 [μ Mo]/r: 3,142.200 3,111.290 (μs) [μSD μRSD]/r: 87.639μs 2.79%
benchRandomLookups # 1..................R1 I2 [μ Mo]/r: 13,298.800 13,337.170 (μs) [μSD μRSD]/r: 103.891μs 0.78%
benchRandomLookupsComposite # 0.........R1 I3 [μ Mo]/r: 3,351.600 3,389.104 (μs) [μSD μRSD]/r: 72.516μs 2.16%
benchRandomLookupsComposite # 1.........R1 I4 [μ Mo]/r: 13,528.200 13,502.881 (μs) [μSD μRSD]/r: 99.997μs 0.74%
\Yiisoft\Di\Tests\Benchmark\ContainerMethodHasBench

benchPredefinedExisting.................R1 I4 [μ Mo]/r: 0.115 0.114 (μs) [μSD μRSD]/r: 0.001μs 1.31%
benchUndefinedExisting..................R5 I4 [μ Mo]/r: 0.436 0.432 (μs) [μSD μRSD]/r: 0.008μs 1.89%
benchUndefinedNonexistent...............R5 I4 [μ Mo]/r: 0.946 0.942 (μs) [μSD μRSD]/r: 0.006μs 0.59%
8 subjects, 55 iterations, 5,006 revs, 0 rejects, 0 failures, 0 warnings 
(best [mean mode] worst) = 0.113 [4,483.856 4,486.051] 0.117 (μs) 
⅀T: 246,612.096μs μSD/r 43.563μs μRSD/r: 1.336%

Warning! These summary statistics can be misleading. You should always verify the individual subject statistics before drawing any conclusions.

Legend

  • μ: Mean time taken by all iterations in variant.
  • Mo: Mode of all iterations in variant.
  • μSD: μ standard deviation.
  • μRSD: μ relative standard deviation.
  • best: Maximum time of all iterations (minimal of all iterations).
  • mean: Mean time taken by all iterations.
  • mode: Mode of all iterations.
  • worst: Minimum time of all iterations (minimal of all iterations).

Command examples

  • Default report for all benchmarks that outputs the result to CSV-file

$ ./vendor/bin/phpbench run --report=default --progress=dots --output=csv_file

Generated MD-file example

DI benchmark report

suite: 1343b1dc0589cb4e985036d14b3e12cb430a975b, date: 2020-02-21, stime: 16:02:45

benchmark subject set revs iter mem_peak time_rev comp_z_value comp_deviation
ContainerBench benchConstructStupid 0 1000 0 1,416,784b 210.938μs -1.48σ -1.1%
ContainerBench benchConstructStupid 0 1000 1 1,416,784b 213.867μs +0.37σ +0.27%
ContainerBench benchConstructStupid 0 1000 2 1,416,784b 212.890μs -0.25σ -0.18%
ContainerBench benchConstructStupid 0 1000 3 1,416,784b 215.820μs +1.60σ +1.19%
ContainerBench benchConstructStupid 0 1000 4 1,416,784b 212.891μs -0.25σ -0.18%
ContainerBench benchConstructSmart 0 1000 0 1,426,280b 232.422μs -1.03σ -0.5%
ContainerBench benchConstructSmart 0 1000 1 1,426,280b 232.422μs -1.03σ -0.5%
ContainerBench benchConstructSmart 0 1000 2 1,426,280b 233.398μs -0.17σ -0.08%
ContainerBench benchConstructSmart 0 1000 3 1,426,280b 234.375μs +0.69σ +0.33%
ContainerBench benchConstructSmart 0 1000 4 1,426,280b 235.351μs +1.54σ +0.75%
... skipped ... ... ... ... ... ... ... ...
ContainerMethodHasBench benchPredefinedExisting 0 1000 0 1,216,144b 81.055μs -0.91σ -1.19%
ContainerMethodHasBench benchPredefinedExisting 0 1000 1 1,216,144b 83.985μs +1.83σ +2.38%
ContainerMethodHasBench benchPredefinedExisting 0 1000 2 1,216,144b 82.032μs 0.00σ 0.00%
ContainerMethodHasBench benchPredefinedExisting 0 1000 3 1,216,144b 82.031μs 0.00σ 0.00%
ContainerMethodHasBench benchPredefinedExisting 0 1000 4 1,216,144b 81.055μs -0.91σ -1.19%
... skipped ... ... ... ... ... ... ... ...

Legend

  • benchmark: Benchmark class.
  • subject: Benchmark class method.
  • set: Set of data (provided by ParamProvider).
  • revs: Number of revolutions (represent the number of times that the code is executed).
  • iter: Number of iteration.
  • mem_peak: (mean) Peak memory used by iteration as retrieved by memory_get_peak_usage.
  • time_rev: Mean time taken by all iterations in variant.
  • comp_z_value: Z-score.
  • comp_deviation: Relative deviation (margin of error).
  • Aggregate report for the lookup group that outputs the result to console and CSV-file

$ ./vendor/bin/phpbench run --report=aggregate --progress=dots --output=csv_file --output=console --group=lookup

Notice

Available groups: construct lookup has

Generated MD-file example

DI benchmark report

suite: 1343b1d2654a3819c72a96d236302b70a504dac7, date: 2020-02-21, stime: 13:27:32

benchmark subject set revs its mem_peak best mean mode worst stdev rstdev diff
ContainerBench benchSequentialLookups 0 1000 5 1,454,024b 168.945μs 170.117μs 169.782μs 171.875μs 0.957μs 0.56% 1.00x
ContainerBench benchSequentialLookups 1 1000 5 1,445,296b 3,347.656μs 3,384.961μs 3,390.411μs 3,414.062μs 21.823μs 0.64% 19.90x
ContainerBench benchSequentialLookups 2 1000 5 1,445,568b 3,420.898μs 3,488.477μs 3,447.260μs 3,657.227μs 85.705μs 2.46% 20.51x
ContainerBench benchRandomLookups 0 1000 5 1,454,024b 169.922μs 171.875μs 171.871μs 173.828μs 1.381μs 0.80% 1.01x
ContainerBench benchRandomLookups 1 1000 5 1,445,296b 3,353.515μs 3,389.844μs 3,377.299μs 3,446.289μs 31.598μs 0.93% 19.93x
ContainerBench benchRandomLookups 2 1000 5 1,445,568b 3,445.313μs 3,587.696μs 3,517.823μs 3,749.023μs 115.850μs 3.23% 21.09x
ContainerBench benchRandomLookupsComposite 0 1000 5 1,454,032b 297.852μs 299.610μs 298.855μs 302.734μs 1.680μs 0.56% 1.76x
ContainerBench benchRandomLookupsComposite 1 1000 5 1,445,880b 3,684.570μs 3,708.984μs 3,695.731μs 3,762.695μs 28.297μs 0.76% 21.80x
ContainerBench benchRandomLookupsComposite 2 1000 5 1,446,152b 3,668.946μs 3,721.680μs 3,727.407μs 3,765.625μs 30.881μs 0.83% 21.88x

Legend

  • benchmark: Benchmark class.
  • subject: Benchmark class method.
  • set: Set of data (provided by ParamProvider).
  • revs: Number of revolutions (represent the number of times that the code is executed).
  • its: Number of iterations (one measurement for each iteration).
  • mem_peak: (mean) Peak memory used by each iteration as retrieved by memory_get_peak_usage.
  • best: Maximum time of all iterations in variant.
  • mean: Mean time taken by all iterations in variant.
  • mode: Mode of all iterations in variant.
  • worst: Minimum time of all iterations in variant.
  • stdev: Standard deviation.
  • rstdev: The relative standard deviation.
  • diff: Difference between variants in a single group.

Testing

Unit testing

The package is tested with PHPUnit. To run tests:

./vendor/bin/phpunit

Mutation testing

The package tests are checked with Infection mutation framework with Infection Static Analysis Plugin. To run it:

./vendor/bin/roave-infection-static-analysis-plugin

Static analysis

The code is statically analyzed with Psalm. To run static analysis:

./vendor/bin/psalm

License

The Yii Dependency Injection is free software. It is released under the terms of the BSD License. Please see LICENSE for more information.

Maintained by Yii Software.

Support the project

Open Collective

Follow updates

Official website Twitter Telegram Facebook Slack

Comments
  • Refactor configuration array

    Refactor configuration array

    At the moment we have configuration array like this:

        $config = [
            '__class' => MyClass::class,
            '__construct()' => [
                'param1' => 'value1',
                'param2' => 'value2',
            ],
            'prop' => 'value',
            'aMethod()' => ['arg1', 'arg2'],
        ];
    

    We could redo it this way:

        $config = [
            '__class' => MyClass::class,
            'param1' => 'value1',
            'param2' => 'value2',
            '__configure' => [
                'prop' => 'value',
                'aMethod()' => ['arg1', 'arg2'],
            ],
        ];
    

    The convention will be throw away all items starting with '__' and use everything else as constructor params (to allow adding other special configuration options later).

    This way is more convenient for classes with normal constructors which is what we aim to. I have actually working big application switched to Yii 3 and in practice, there are more constructor parameters then properties configuration.

    status:under discussion 
    opened by hiqsol 39
  • Rework

    Rework

    Rework of the library by @sammousa and @hiqsol.

    | Q | A | ------------- | --- | Is bugfix? | no | New feature? | no | Breaks BC? | yes | Tests pass? | - | Fixed issues | -

    opened by samdark 36
  • [WIP] Rework

    [WIP] Rework

    This is a big rework, in depth review is needed.

    Changes:

    • Introduced DependencyResolverInterface, which allows for implementing a strategy for resolving dependencies. Currently there is 1 concrete implementation ClassNameResolver which looks at class names and ignores parameter names.
    • Introduced DependencyInterface, a dependency resolver must resolve a constructor or callable to an array of DependencyInterface.

    Introducing the above allowed for a simplified container implementation.

    1. Dereference a given id.
    2. Get the dependencies using a resolver.
    3. Resolve all dependencies
    4. Build the object given its definition and resolved dependencies.
    opened by SamMousa 30
  • Add `Container::create()` method

    Add `Container::create()` method

    At the present state Container is almost useless in Yii Framework scope. It is unable to replace old Container from Yii 2.0.x. In particular there is no way to create an object from array definition taking into account possible singleton definitions or predefined configurations.

    Previously this task was implemented inside Container::get() method, which accepted constructor arguments and object config as additional parameters:

    $foo = $container->get(Foo::class, ['constructor argument'], ['someField' => 'some-value']);
    

    Accepting PSR for the container makes impossible to use get() for this purpose.

    I suggest a separated method create() (or createObject()) should be introduced to incapsulate former object creation logic. Method signature:

    public function create(array $definition) : object
    

    This method should check for registered definitions and use Container::build() to create final object.

    type:enhancement status:under discussion 
    opened by klimov-paul 20
  • Fall back to autoloader when class is not in container

    Fall back to autoloader when class is not in container

    Currently there's a need to declare everything that is repetitive if we need to set a single class without an interface into container:

    \App\ConsoleCommand\CreateUser::class => \App\ConsoleCommand\CreateUser::class,
    

    In such cases we can try to fall back to class autoloading.

    type:enhancement status:ready for adoption help wanted 
    opened by samdark 19
  • Willl be Service Locator later in package?

    Willl be Service Locator later in package?

    Hi,

    I have some module that have group of similar services and I want to manipulate with these services by ServiceLocator.

    Just for information: Will you have ServiceLocator later or I can make it myself for my needs?

    opened by andrew-svirin 18
  • Idea how to reproduce Yii 2.0 container behavior

    Idea how to reproduce Yii 2.0 container behavior

    Explaining short and rough but I hope you'll catch the idea.

    \yii\di\Container class could be extended with $isSingleton property (better name is welcome). When isSingleton=true then container will work as is in this implementation and return same instance for same ID. When isSingleton=false then container will work as factory and create new instance every time for same ID.

    Then it is possible to create singleton container having non-singleton container as its parent. So former definitions config goes to non-singleton container and singletons config goes to singleton container. Then this singleton container can be used in Yii::createObject function to reproduce Yii 2.0 behavior: it will return singleton when found and create new object according to non-singleton definition otherwise.

    type:enhancement status:under discussion 
    opened by hiqsol 18
  • CompositeContainer is not working

    CompositeContainer is not working

    What steps will reproduce the problem?

    1. Run the following code:
    $container = new \Yiisoft\Di\CompositeContainer();
    $container1 = new Container([
        'first' => function () {
            return 'first';
        }
    ]);
    $container2 = new Container([
        'second' => function () {
            return  'second';
        },
        'third' => function ($c) {
            return $c->get('first') . $c->get('second') . 'third';
        },
    ]);
    
    $container->attach($container1);
    $container->attach($container2);
    var_dump($container->get('first')); // works
    var_dump($container->get('second')); // works
    var_dump($container->get('third')); // not working :C
    

    What is the expected result?

    Each $container->get() should return result

    string(5) "first" string(6) "second" string(16) "firstsecondthird"
    

    What do you get instead?

    string(5) "first" string(6) "second"
    Fatal error: Uncaught Yiisoft\Factory\Exceptions\CircularReferenceException: Circular reference to "first" detected while building: first,third in /app/vendor/yiisoft/di/src/Container.php:105 Stack trace: #0 /app/vendor/yiisoft/di/src/Container.php(80): Yiisoft\Di\Container->build('first', Array) #1 /app/public/index.php(18): Yiisoft\Di\Container->get('first') #2 /app/vendor/yiisoft/factory/src/Definitions/CallableDefinition.php(19): {closure}(Object(Yiisoft\Di\Container), Array) #3 /app/vendor/yiisoft/di/src/Container.php(134): Yiisoft\Factory\Definitions\CallableDefinition->resolve(Object(Yiisoft\Di\Container), Array) #4 /app/vendor/yiisoft/di/src/Container.php(114): Yiisoft\Di\Container->buildInternal('third', Array) #5 /app/vendor/yiisoft/di/src/Container.php(80): Yiisoft\Di\Container->build('third', Array) #6 /app/vendor/yiisoft/di/src/CompositeContainer.php(25): Yiisoft\Di\Container->get('third') #7 /app/public/index.php(25): Yiisoft\Di\CompositeContainer->get('third') #8 {main} thrown in /app/vendor/yiisoft/di/src/Container.php on line 105
    

    Any idea how to solve this?


    UPD:

    If try to do $container->get('third') only, then we get:

    Fatal error: Uncaught Yiisoft\Factory\Exceptions\NotFoundException: No definition for third in /app/vendor/yiisoft/di/src/CompositeContainer.php:30 Stack trace: #0 /app/public/index.php(25): Yiisoft\Di\CompositeContainer->get('third') #1 {main} thrown in /app/vendor/yiisoft/di/src/CompositeContainer.php on line 30
    

    It happends because thrid is not found in the first container. In the second container third is found, but first is not found.

    type:bug 
    opened by xepozz 17
  • [Feature Proposal] Add Decorators

    [Feature Proposal] Add Decorators

    Through container, we can set any default values, attach behaviors, event handlers etc.

    But if there is a bootstrap code that used in different container definitions, add behavior or state to individual objects at run-time or if you want to apply several transformations to an object returned from the container, you need to add a lot of additional code with a meaning that won't be clean to understand sometimes and that won't be really handy.

    I suggest adding support for decorators in the container.

    Specification

    Each decorator has a method decorate with one argument - object that should be decorated. No other methods are required, so the interface looks like:

    /**
     * Represents decorator of any objects.
     *
     * @see https://sourcemaking.com/design_patterns/decorator
     */
    interface ObjectDecorator {
        public function decorate($object);
    }
    

    Decorator can be assigned to a class by its name or identifier in the container, using method addDecorator of the container:

    $container->addDecorator(Calculator::class, EngineeringDecorator::class);
    $container->addDecorator(Calculator::class, ScienceDecorator::class);
    

    or through configuration:

    'container' => [
        'decorators' => [
            Calculator::class =>  [
                EngineeringDecorator::class,
                ScienceDecorator::class,
            ]
        ]
    ]
    

    Each decorator is a singleton and should be created only one time when a target object is requested from the container.

    status:under discussion 
    opened by prowwid 17
  • [RFC] Tags support

    [RFC] Tags support

    E.g. I have a telegram bot with commands implementing some CommandInterface. And I have HelpCommand which must show all available commands. Now to achieve this I must edit HelpCommand configuration every time I add new command to the bot. Using tags I can tag command as a telegram-bot-command so It will be automatically loaded by HelpCommand

    Same feature in Laravel and Symfony

    type:enhancement 
    opened by BoShurik 14
  • How to achieve multi-connection effect of yii2 through di

    How to achieve multi-connection effect of yii2 through di

    Because the configuration is covered Di always reference to redis2

    
        'queue1' => [
            '__class' => \yii\queue\redis\Queue::class,
        ],
        \yii\db\redis\Connection::class => \yii\di\Reference::to('redis1'),
        'redis1' => [
            '__class' => \yii\db\redis\Connection::class,
            'hostname' => 'redis1.com',
            'database' => 1,
        ],
    
        'queue2' => [
            '__class' => \yii\queue\redis\Queue::class,
        ],
        \yii\db\redis\Connection::class => \yii\di\Reference::to('redis2'),
        'redis2' => [
            '__class' => \yii\db\redis\Connection::class,
            'hostname' => 'redis2.com',
            'database' => 2,
        ],
    
    
        /**
         * @inheritdoc
         */
        public function __construct(SerializerInterface $serializer, Connection $redis)
        {
            parent::__construct($serializer);
            $this->redis = $redis; <- Never reids2 Connection
        }
    
    status:under discussion 
    opened by kids-return 12
  • Update vimeo/psalm requirement from ^4.29 to ^5.0

    Update vimeo/psalm requirement from ^4.29 to ^5.0

    Updates the requirements on vimeo/psalm to permit the latest version.

    Release notes

    Sourced from vimeo/psalm's releases.

    Psalm 5

    Welcome to Psalm 5!

    There's an accompanying post on psalm.dev, written by @​muglug & the current maintainers of Psalm.

    What's Changed

    Removed

    Features

    ... (truncated)

    Commits
    • 4e177bf Merge pull request #8790 from malarzm/patch-1
    • 7877570 Remove CallMapTest.php from Psalm self-analysis
    • 28188d1 Remove unfinished sentence
    • 6fff6df Merge pull request #8788 from weirdan/fix-xml-report-crashes-on-8.1
    • 874eb7d Fix crashes when XML report is used on PHP 8.1
    • 9d597cf Merge pull request #8782 from weirdan/check-runtime-requirements
    • 94dac9f Merge pull request #8783 from weirdan/bump-slevomat-coding-standard
    • ed36f2c Bump slevomat/coding-standard
    • 8fa35c2 Variables should outlive namespaces (#8779)
    • 05b8e0e Replace all references to static variables when moving class (#8780)
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies php 
    opened by dependabot[bot] 2
  • Improve UX adding short syntax passing string reference to methods' calls

    Improve UX adding short syntax passing string reference to methods' calls

    I think passing string reference could be more convenient for users: Determine the behaviour of passing strings:

    1. Hidden/short reference
    'addAdapters()' => 'tag@adapters'
    
    1. Direct/full reference
    'addAdapters()' => [Reference::to('tag@adapters')]
    

    There are short and full form. They are fully equals by meaning.

    If you need to pass 'tag@adapters' as a string parameter you need to use the following syntax:

    'addAdapters()' => ['tag@adapters']
    

    That how it works now.

    status:under discussion 
    opened by xepozz 0
  • Add Attributes to inject scalar definitions & tags

    Add Attributes to inject scalar definitions & tags

    
    public function foo(#[Inject('scalar')] $scalar, #[Tags('messageSource')] $messageSources)
    {
        // $scalar is an alternative to $container->get('scalar')
        // $messageSources is an alternative to $container->get('#messageSource')
    }
    
    

    Can be also used for class properties.

    status:under discussion type:feature 
    opened by rustamwin 1
  • Container doesn't resolve properties of already built instances after their state reset

    Container doesn't resolve properties of already built instances after their state reset

    What steps will reproduce the problem?

    Run yiisoft/demo via RR(yiisoft/yii-runner-roadrunner) 1st request is handled properly, after which StateResetter::reset() is invoked in subsequent requests container returns instances with unresolved properties

    Example for subsequent requests:

    What is the expected result?

    Screenshot from 2022-08-10 09-04-14

    What do you get instead?

    Screenshot from 2022-08-10 09-05-57

    Additional info

    | Q | A | ---------------- | --- | Version | 1.1.0 | PHP version | 8.1.2 | Operating system | Ubuntu 22.04

    type:bug status:ready for adoption 
    opened by alamagus 2
  • Improve containers types to use iterable objects

    Improve containers types to use iterable objects

    | Q | A | ------------- | --- | Is bugfix? | ✔️ | New feature? | ✔️ | Breaks BC? | ❌ | Fixed issues |

    Added ability to use iterable parameters. For example, now you cannot use Generator as tags values.

    status:code review 
    opened by xepozz 2
Releases(1.2.1)
Owner
Yii Software
Yii Framework and packages
Yii Software
A small PHP dependency injection container

Pimple Caution! Pimple is now closed for changes. No new features will be added and no cosmetic changes will be accepted either. The only accepted cha

Silex 2.6k Dec 29, 2022
The dependency injection container for humans

layout home PHP-DI is a dependency injection container meant to be practical, powerful, and framework-agnostic. Read more on the website: php-di.org G

null 2.4k Jan 4, 2023
Small but powerful dependency injection container

Container (Dependency Injection) This package is compliant with PSR-1, PSR-2, PSR-4 and PSR-11. If you notice compliance oversights, please send a pat

The League of Extraordinary Packages 779 Dec 30, 2022
💎 Flexible, compiled and full-featured Dependency Injection Container with perfectly usable autowiring and support for all new PHP 7 features.

Nette Dependency Injection (DI) Introduction Purpose of the Dependecy Injection (DI) is to free classes from the responsibility for obtaining objects

Nette Foundation 781 Dec 15, 2022
🚀 PHP Service Container with fast and cachable dependency injection.

ClanCats Container A PHP Service Container featuring a simple meta-language with fast and compilable dependency injection. Requires PHP >= 7.0 Pros: M

ClanCats 28 Apr 13, 2022
Dependency Injection System

Aura.Di A serializable dependency injection container with constructor and setter injection, interface and trait awareness, configuration inheritance,

Aura for PHP 342 Dec 1, 2022
Twittee is the smallest, and still useful, Dependency Injection Container in PHP

What is Twittee? Twittee is the smallest, and still useful, Dependency Injection Container in PHP; it is also probably one of the first public softwar

null 133 Dec 5, 2022
IoC Dependency Injector

auryn auryn is a recursive dependency injector. Use auryn to bootstrap and wire together S.O.L.I.D., object-oriented PHP applications. How It Works Am

Daniel Lowrey 725 Nov 23, 2022
IoC Dependency Injector

auryn auryn is a recursive dependency injector. Use auryn to bootstrap and wire together S.O.L.I.D., object-oriented PHP applications. How It Works Am

Daniel Lowrey 710 Apr 15, 2021
Dependency Manager for PHP

Composer - Dependency Management for PHP Composer helps you declare, manage, and install dependencies of PHP projects. See https://getcomposer.org/ fo

Composer 27.2k Dec 31, 2022
DI Container (PSR-11)

My DI Container It's my own implementation PSR-11 Container Interface. Installation composer require scruwi/container Init $container = new Container(

null 4 Mar 15, 2022
This repository holds all interfaces related to PSR-11 (Container Interface).

Container interface This repository holds all interfaces related to PSR-11 (Container Interface). Note that this is not a Container implementation of

PHP-FIG 9.6k Jan 4, 2023
PSR-11 compatible Dependency Injection Container for PHP.

bitexpert/disco This package provides a PSR-11 compatible, annotation-based dependency injection container. Have a look at the disco-demos project to

bitExpert AG 137 Sep 29, 2022
A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package.

Net A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package. Features: No hard dependencies; Favours

Minibase 16 Jun 7, 2022
A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package.

Net A small, modern, PSR-7 compatible PSR-17 and PSR-18 network library for PHP, inspired by Go's net package. Features: No hard dependencies; Favours

Minibase 16 Jun 7, 2022
A small PHP dependency injection container

Pimple Caution! Pimple is now closed for changes. No new features will be added and no cosmetic changes will be accepted either. The only accepted cha

Silex 2.6k Dec 29, 2022
The dependency injection container for humans

layout home PHP-DI is a dependency injection container meant to be practical, powerful, and framework-agnostic. Read more on the website: php-di.org G

null 2.4k Jan 4, 2023
Small but powerful dependency injection container

Container (Dependency Injection) This package is compliant with PSR-1, PSR-2, PSR-4 and PSR-11. If you notice compliance oversights, please send a pat

The League of Extraordinary Packages 779 Dec 30, 2022
💎 Flexible, compiled and full-featured Dependency Injection Container with perfectly usable autowiring and support for all new PHP 7 features.

Nette Dependency Injection (DI) Introduction Purpose of the Dependecy Injection (DI) is to free classes from the responsibility for obtaining objects

Nette Foundation 781 Dec 15, 2022
🚀 PHP Service Container with fast and cachable dependency injection.

ClanCats Container A PHP Service Container featuring a simple meta-language with fast and compilable dependency injection. Requires PHP >= 7.0 Pros: M

ClanCats 28 Apr 13, 2022