Skip to content

Files

Latest commit

b7fbed5 · Apr 23, 2024

History

History
117 lines (87 loc) · 3.53 KB

README.md

File metadata and controls

117 lines (87 loc) · 3.53 KB

Injector package

General usage

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

$config = ContainerConfig::create()
    ->withDefinitions([
        EngineInterface::class => EngineMarkTwo::class,
    ]);
$container = new Container($config);

$getEngineName = static function (EngineInterface $engine) {
    return $engine->getName();
};

$injector = new Injector($container);
echo $injector->invoke($getEngineName);
// outputs "Mark Two"

In the code above we feed our container to Injector when creating it. Any PSR-11 container could be used. When invoke is called, injector reads method signature of the method invoked and, based on type hinting automatically obtains objects for corresponding interfaces from the container.

Sometimes you either don't have an object in container or want to explicitly specify arguments. It could be done like the following:

use Yiisoft\Injector\Injector;

/** @var $dataProvider DataProvider */
$dataProvider = /* ... */;
$result = (new Injector($container))->invoke([$calculator, 'calculate'], ['multiplier' => 5.0, $dataProvider]);

In the above the "calculate" method looks like the following:

public function calculate(DataProvider $dataProvider, float $multiplier)
{
    // ...
}

We have passed two arguments. One is multiplier. It is explicitly named. Such arguments passed as is. Another is data provider. It is not named explicitly so injector finds matching parameter that has the same type.

Creating an instance of an object of a given class behaves similar to invoke():

use Yiisoft\Injector\Injector;

class StringFormatter
{
    public function __construct($string, \Yiisoft\I18n\MessageFormatterInterface $formatter)
    {
        // ...
    }
    public function getFormattedString(): string
    {
        // ...
    }
}

$stringFormatter = (new Injector($container))->make(StringFormatter::class, ['string' => 'Hello World!']);

$result = $stringFormatter->getFormattedString();

The object isn't saved into container so make() works well for short-living dynamically created objects.

How it works

Both invoke() and make() are selecting arguments automatically for the method or constructor called based on parameter names / types and an optional array of explicit values.

Algorithm is the following:

Algorithm

Additionally:

  • Passing unnamed argument that is not an object results in an exception.

  • Each argument used only once.

  • Unused unnamed explicit arguments are passed at the end of arguments list. Their values could be obtained with func_get_args().

  • Unused named arguments are ignored.

  • If parameters are accepting arguments by reference, arguments should be explicitly passed by reference:

    use Yiisoft\Injector\Injector;
    
    $foo = 1;
    $increment = function (int &$value) {
        ++$value;
    };
    (new Injector($container))->invoke($increment, ['value' => &$foo]);
    echo $foo; // 2

Reflection caching

Injector uses Reflection API to analyze class signatures. By default, it creates new Reflection objects each time. Call withCacheReflections(true) to prevent this behavior and cache reflection objects. It is recommended to enable caching in production environment, because it improves performance. If you use async frameworks such as RoadRunner, AMPHP or Swoole don't forget to reset injector state.

use Yiisoft\Injector\Injector;

$injector = (new Injector($container))
    ->withCacheReflections(true);