Container   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 186
rs 8.6
c 0
b 0
f 0
wmc 37

16 Methods

Rating   Name   Duplication   Size   Complexity  
B runDecoratorsOnObject() 0 13 5
A ensureProviderIsObject() 0 6 2
A addProvider() 0 2 1
A addDelayedServiceProvider() 0 2 1
A get() 0 8 1
A configureObject() 0 6 2
A isDecoratorsGroupRegistered() 0 4 3
B create() 0 16 6
A init() 0 3 1
A setServiceProviders() 0 3 2
A addDecorator() 0 2 1
A setDecorators() 0 3 2
A getDefinitionOf() 0 4 2
A registerServiceProvider() 0 9 3
A createFactoryFor() 0 4 1
A registerDelayedServiceProviderFor() 0 10 4
1
<?php
2
3
namespace PHPKitchen\DI;
4
5
use PHPKitchen\DI\Contracts\ServiceProvider;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PHPKitchen\DI\ServiceProvider. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use yii\base\InvalidConfigException;
7
8
/**
9
 * Represents advanced Dependency Injection container.
10
 *
11
 * Provides such features as:
12
 * - decorators: allows to decorate objects without affecting their container definitions.
13
 * - service providers: allows to group complex dependencies definitions in a single class that bootstraps
14
 * service or services.
15
 * - factories: generate factories for classes dynamically.
16
 *
17
 * @package core\di
18
 * @author Dmitry Kolodko <[email protected]>
19
 */
20
class Container extends \yii\di\Container implements Contracts\Container {
21
    /**
22
     * @var Contracts\ObjectDecorator[]|array
23
     */
24
    protected $_decorators;
25
    /**
26
     * @var Contracts\DelayedServiceProvider[]|\SplObjectStorage
27
     */
28
    protected $delayedServiceProviders;
29
    /**
30
     * @var string default class for factory.
31
     */
32
    public $factoryClass = ClassFactory::class;
33
34
    public function init() {
35
        parent::init();
36
        $this->delayedServiceProviders = new \SplObjectStorage();
37
    }
38
39
    public function addProvider($provider) {
40
        $this->registerServiceProvider($provider);
41
    }
42
43
    public function addDecorator($objectName, $decorator) {
44
        $this->_decorators[$objectName][] = $decorator;
45
    }
46
47
    public function createFactoryFor($class) {
48
        return $this->create([
49
            'class' => $this->factoryClass,
50
            'className' => $class,
51
        ]);
52
    }
53
54
    /**
55
     * Creates a new object using the given configuration.
56
     *
57
     * @param string|array|callable $type the object type. This can be specified in one of the following forms:
58
     *
59
     * - a string: representing the class name of the object to be created
60
     * - a configuration array: the array must contain a `class` element which is treated as the object class,
61
     *   and the rest of the name-value pairs will be used to initialize the corresponding object properties
62
     * - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`).
63
     *   The callable should return a new instance of the object being created.
64
     *
65
     * @param array $params the constructor parameters
66
     *
67
     * @return object the created object
68
     * @throws InvalidConfigException if the configuration is invalid.
69
     * @see \yii\di\Container
70
     */
71
    public function create($type, array $params = []) {
72
        if (is_string($type)) {
73
            $object = $this->get($type, $params);
74
        } elseif (is_array($type) && isset($type['class'])) {
75
            $class = $type['class'];
76
            unset($type['class']);
77
            $object = $this->get($class, $params, $type);
78
        } elseif (is_callable($type, true)) {
79
            $object = $this->invoke($type, $params);
80
        } elseif (is_array($type)) {
81
            throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
82
        } else {
83
            throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
84
        }
85
86
        return $object;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     * @override
92
     */
93
    public function get($class, $params = [], $config = []) {
94
        $this->registerDelayedServiceProviderFor($class);
95
96
        $object = parent::get($class, $params, $config);
97
98
        $this->runDecoratorsOnObject($class, $object);
99
100
        return $object;
101
    }
102
103
    protected function registerDelayedServiceProviderFor($classOrInterface) {
104
        $delayedServiceProviders = $this->delayedServiceProviders;
105
        if ($delayedServiceProviders->count() === 0) {
106
            return;
107
        }
108
109
        foreach ($delayedServiceProviders as $delayedServiceProvider) {
110
            if ($delayedServiceProvider->provides($classOrInterface)) {
111
                $delayedServiceProvider->register();
112
                $delayedServiceProviders->detach($delayedServiceProvider);
113
            }
114
        }
115
    }
116
117
    /**
118
     * Configures an object with the initial property values.
119
     *
120
     * @param object $object the object to be configured
121
     * @param array $properties the property initial values given in terms of name-value pairs.
122
     *
123
     * @return object the object itself
124
     */
125
    public function configureObject($object, $properties) {
126
        foreach ($properties as $name => $value) {
127
            $object->$name = $value;
128
        }
129
130
        return $object;
131
    }
132
133
    /**
134
     * @param string $definitionName class or name of definition in container
135
     *
136
     * @return array definition or empty array if definition not set.
137
     */
138
    public function getDefinitionOf($definitionName) {
139
        $definitions = $this->getDefinitions();
140
141
        return isset($definitions[$definitionName]) ? $definitions[$definitionName] : [];
142
    }
143
144
    protected function runDecoratorsOnObject($decoratorsGroupName, $object) {
145
        if (!$this->isDecoratorsGroupRegistered($decoratorsGroupName)) {
146
            return;
147
        }
148
        foreach ($this->_decorators[$decoratorsGroupName] as &$decorator) {
149
            if (is_callable($decorator)) {
150
                call_user_func($decorator, $object);
151
                continue;
152
            }
153
            if (!is_object($decorator)) {
154
                $decorator = $this->create($decorator);
155
            }
156
            $decorator->decorate($object);
157
        }
158
    }
159
160
    protected function isDecoratorsGroupRegistered($decoratorsGroup) {
161
        $objectName = is_object($decoratorsGroup) ? get_class($decoratorsGroup) : $decoratorsGroup;
162
163
        return isset($this->_decorators[$objectName]) && !empty($this->_decorators[$objectName]);
164
    }
165
166
    /**
167
     * @param ServiceProvider $serviceProvider
168
     *
169
     * @throws InvalidConfigException
170
     */
171
    protected function registerServiceProvider($serviceProvider) {
172
        $serviceProvider = $this->ensureProviderIsObject($serviceProvider);
173
174
        if (!($serviceProvider instanceof Contracts\ServiceProvider)) {
175
            throw new InvalidConfigException('Service provider should be an instance of ' . ServiceProvider::class);
176
        } elseif ($serviceProvider instanceof Contracts\DelayedServiceProvider) {
177
            $this->addDelayedServiceProvider($serviceProvider);
178
        } else {
179
            $serviceProvider->register();
180
        }
181
    }
182
183
    protected function addDelayedServiceProvider($provider) {
184
        $this->delayedServiceProviders->attach($provider);
185
    }
186
187
    protected function ensureProviderIsObject($provider) {
188
        if (!is_object($provider)) {
189
            $provider = $this->create($provider);
190
        }
191
192
        return $provider;
193
    }
194
195
    //region ---------------------- SETTERS -------------------------------
196
197
    public function setDecorators($name, array $decorators) {
198
        foreach ($decorators as $decorator) {
199
            $this->addDecorator($name, $decorator);
200
        }
201
    }
202
203
    public function setServiceProviders(array $serviceProviders) {
204
        foreach ($serviceProviders as $serviceProvider) {
205
            $this->registerServiceProvider($serviceProvider);
206
        }
207
    }
208
    //endregion
209
}