Creating an application in Yii3 - part 3 the container di.
Tags: yii3, php, yiiframework
Published: 2023-01-06 15:55:35

In our post today, we will talk about the [container di](https://github.com/yiisoft/di),  but first we must know some important terms, which will help us understand the functioning.

**Dependency injection** according to **Wikipedia “**In [software engineering](https://en.wikipedia.org/wiki/Software_engineering), **dependency injection** is a [design pattern](https://en.wikipedia.org/wiki/Software_design_pattern) in which an [object](https://en.wikipedia.org/wiki/Object_(computer_science)) or [function](https://en.wikipedia.org/wiki/Subroutine) receives other objects or functions that it depends on. A form of [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control), dependency injection aims to [separate the concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) of constructing objects and using them, leading to [loosely](https://en.wikipedia.org/wiki/Loose_coupling) [coupled](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) programs.[[1]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-1)[[2]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-MarkSeeman2011P4-2)[[3]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-3) The pattern ensures that an object or function which wants to use a given [service](https://en.wikipedia.org/wiki/Service_(systems_architecture)) should not have to know how to construct those services. Instead, the receiving '[client](https://en.wikipedia.org/wiki/Client_(computing))' (object or function) is provided with its dependencies by external code (an 'injector'), which it is not aware of.[[4]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-HollywoodPrinciple.c2-4) Dependency injection helps by making implicit dependencies explicit and helps solve the following problems:[[5]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-5)

- How can a [class](https://en.wikipedia.org/wiki/Class_(computer_programming)) be independent from the creation of the objects it depends on?
- How can an application, and the objects it uses support different configurations?
- How can the behavior of a piece of code be changed without editing it directly?

In addition, dependency injection is used for keeping code in-line with the [dependency inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle).[[6]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-6)[[7]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-7)

**Fundamentally, dependency injection consists of [passing parameters to a method](https://en.wikipedia.org/wiki/Parameter_(computer_programming)).**[[8]](https://en.wikipedia.org/wiki/Dependency_injection#cite_note-8)”

Let’s look at the following example:

There are two ways of re-using things in OOP: inheritance and composition.

```php
/**
 * Inheritance is simple.
 */
class Cache
{
    public function getCachedValue($key)
    {
        // ..
    }
}

class CachedWidget extends Cache
{
    public function render(): string
    {
        $output = $this->getCachedValue('cachedWidget');

        if ($output !== null) {
            return $output;
        }

        // ...        
    }
}
```

The issue here is that these two are becoming unnecessarily coupled or inter-dependent making them more fragile.

```php
/**
 * Composition.
 */
interface CacheInterface
{
    public function getCachedValue($key);
}

final class Cache implements CacheInterface
{
    public function getCachedValue($key)
    {
        // ..
    }
}

final class CachedWidget
{
    private CacheInterface $cache;

    // Dependency injection.
    public function __construct(CacheInterface $cache)
    {
        $this->cache = $cache;
    }
    
    public function render(): string
    {
        $output = $this->cache->getCachedValue('cachedWidget');
        if ($output !== null) {
            return $output;
        }

        // ...        
    }
}
```

In the above we've avoided unnecessary inheritance and used interface to reduce coupling. You can replace cache implementation without changing so it is becoming more stable`CachedWidget`.

The here is a dependency: an object another object depends on. The process of putting an instance of dependency into an object () is called **dependency injection**. There are multiple ways to perform it:`CacheInterface CachedWidget`.

1. Constructor injection. Best for mandatory dependencies.
2. Method injection. Best for optional dependencies.
3. Property injection. Better to be avoided in PHP except maybe data transfer objects.

**Using the container di**: Usage of the [container di](https://github.com/yiisoft/di) 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 or a configuration file:`index.php`

```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;

$config = ContainerConfig::create()->withDefinitions($definitions);
$container = new Container($config);
```

```php
/**
 * Definitions.
 * Very important all the values, parameters must go between square brackets.
 */
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. could be used to get current container instance. `ContainerInterface`
- 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.

[Definitions](https://github.com/yiisoft/definitions)  is describing a way to create and configure a service, an object or return any other value. It must implement  `Yiisoft\Definitions\Contract\DefinitionInterface` that has a single method `resolve(ContainerInterface $container)`**.**

References are typically stored in the container or a factory and are resolved into object at the moment of obtaining a service instance or creating an object.

**Array definition** allows describing a service or an object declaratively:

```php
use \Yiisoft\Definitions\ArrayDefinition;

$definition = ArrayDefinition::fromConfig(
    [
        'class' => MyServiceInterface::class,
        '__construct()' => [42], 
        '$propertyName' => 'value',
        'setName()' => ['Alex'],
    ],
);
$object = $definition->resolve($container);
```

`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.

**Callable definition** builds an object by executing a callable injecting dependencies based on types used in its signature:

```php
use \Yiisoft\Definitions\CallableDefinition;

$definition = new CallableDefinition(
    fn (SomeFactory $factory) => $factory->create('args')
);
$object = $definition->resolve($container);

// or 

$definition = new CallableDefinition(
    fn () => MyFactory::create('args')
);
$object = $definition->resolve($container);

// or

$definition = new CallableDefinition(
    [MyFactory::class, 'create']
);
$object = $definition->resolve($container);
```

In the above we use a closure, a static call and a static method passed as array-callable. In each case we determine and pass dependencies based on the types of arguments in the callable signature.

**Parameter definition** resolves an object based on information from `ReflectionParameter`
instance:

```php
use \Yiisoft\Definitions\ParameterDefinition;

$definition = new ParameterDefinition($reflectionParameter);
$object = $definition->resolve($container);
```

**Value** **definition** resolves value passed as is:

```php
use \Yiisoft\Definitions\ValueDefinition;

$definition = new ValueDefinition(42, 'int');
$value = $definition->resolve($container); // 42
```

**References** point to other definitions so when defining a definition you can use other definitions as its dependencies:

```php
[
    InterfaceA::class => ConcreteA::class,
    'alternativeForA' => ConcreteB::class,

    MyService::class => [
        '__construct()' => [
            Reference::to('alternativeForA'),
        ],
    ],
]
```

Optional reference returns `null` when there's no corresponding definition in container:

```php
[
    MyService::class => [
        '__construct()' => [
            // If container doesn't have definition for `EventDispatcherInterface`
            // reference returns `null` when resolving dependencies
            Reference::optional(EventDispatcherInterface::class), 
        ],
    ],
]
```

**Dynamic** reference  defines a dependency to a service not defined in the container:

```php
[
   MyService::class => [
       '__construct()' => [
           DynamicReference::to(
					     [
                  'class' => SomeClass::class,
                  '$someProp' => 15
               ],
           )
       ]
   ]
]
```

In order to pass an array of IDs as references to a property or an argument,  `Yiisoft\Definitions\ReferencesArray` or  `Yiisoft\Definitions\DynamicReferencesArray`
 could be used:

```php
File: params.php
return [
   'yiisoft/data-response' => [
       'contentFormatters' => [
           'text/html' => HtmlDataResponseFormatter::class,
           'application/xml' => XmlDataResponseFormatter::class,
           'application/json' => JsonDataResponseFormatter::class,
       ],
   ],
];
```

```php
File: contentnegotiator.php

ContentNegotiator::class => [
    '__construct()' => [
        'contentFormatters' => ReferencesArray::from(
             $params['yiisoft/data-response']['contentFormatters']
        ),
    ],
],
```

After explaining the functioning, it seems that the configurations are complex, but they are not, the [container di](https://github.com/yiisoft/di), does all the work for you, applying the best practices, you just have to learn the syntax of the container and the references, and everything will be simple, now  let's see the actual example in our [app template](https://github.com/yiisoft/app).

```php
File: config/common/logger.php

<?php

declare(strict_types=1);

use Psr\Log\LoggerInterface;
use Yiisoft\Definitions\ReferencesArray;
use Yiisoft\Log\Logger;
use Yiisoft\Log\Target\File\FileTarget;

/** @var array $params */

return [
    LoggerInterface::class => [
        'class' => Logger::class,
        '__construct()' => [
            'targets' => ReferencesArray::from(
						    [
                   FileTarget::class,
                ],
            ),
        ],
    ],
];
```

```php
File: config/common/translator.php

<?php

declare(strict_types=1);

use Yiisoft\Aliases\Aliases;
use Yiisoft\Translator\CategorySource;
use Yiisoft\Translator\IntlMessageFormatter;
use Yiisoft\Translator\Message\Php\MessageSource;

/** @var array $params */

return [
    // Configure application CategorySource
    'translation.app' => [
        'definition' => static function (Aliases $aliases) use ($params) {
            return new CategorySource(
                $params['yiisoft/translator']['defaultCategory'],
                new MessageSource($aliases->get('@messages')),
                new IntlMessageFormatter(),
            );
        },
        'tags' => ['translation.categorySource'],
    ],
];
```

```php
file: config/web/application.php

<?php

declare(strict_types=1);

use App\Handler\NotFoundHandler;
use Yiisoft\Definitions\DynamicReference;
use Yiisoft\Definitions\Reference;
use Yiisoft\Injector\Injector;
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;

/** @var array $params */

return [
    \Yiisoft\Yii\Http\Application::class => [
        '__construct()' => [
            'dispatcher' => DynamicReference::to(static function (Injector $injector) use ($params) {
                return ($injector->make(MiddlewareDispatcher::class))->withMiddlewares($params['middlewares']);
            }),
            'fallbackHandler' => Reference::to(NotFoundHandler::class),
        ],
    ],
    \Yiisoft\Yii\Middleware\Locale::class => [
        '__construct()' => [
            'locales' => $params['locale']['locales'],
            'ignoredRequests' => $params['locale']['ignoredRequests'],
        ],
        'withEnableSaveLocale()' => [false],
    ],
];
```

Now we understand how to do any configuration of any [YiiFramework](https://www.yiiframework.com/) package or external, it is not necessary to have a single long and complex configuration file, we can organize it according to the group of configurations and [Yii config](https://github.com/yiisoft/config) will do the work for you, as well as the container it applies the definitions for you, with the automatic wiring facility in controllers, which makes it easy to access any container dependency without the need to use **static access** to it, or depend on the **container itself**.

In our next post we will talk about the factory, and how to get all the **power, flexibility and simplicity** of [YiiFramework](https://www.yiiframework.com/).

Wilmer Arámbula.                                                                                                                         
[Team Developer YiiFramework](https://www.yiiframework.com/team)
[Twitter follow](https://twitter.com/Terabytesoftw)