Completed
Push — master ( 95a6e8...b8af6d )
by Filipe
07:12
created

Container   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 10
dl 0
loc 298
ccs 100
cts 100
cp 1
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A get() 0 19 4
A has() 0 11 3
B register() 0 27 4
A make() 0 15 3
A injectOn() 0 10 2
A setParent() 0 5 1
A createValueDefinition() 0 9 1
A createFactoryDefinition() 0 8 1
A resolve() 0 7 2
A resolveSingleton() 0 10 2
A registerObject() 0 11 2
A parentHas() 0 7 2
1
<?php
2
3
/**
4
 * This file is part of slick/di package
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Di;
11
12
use Interop\Container\ContainerInterface;
13
use Interop\Container\Exception\ContainerException;
14
use Slick\Di\Definition\DefinitionList;
15
use Slick\Di\Definition\Factory;
16
use Slick\Di\Definition\Scope;
17
use Slick\Di\Definition\Value;
18
use Slick\Di\Exception\InvalidArgumentException;
19
use Slick\Di\Exception\NotFoundException;
20
21
/**
22
 * Dependency injection container
23
 *
24
 * @package Slick\Di
25
 * @author  Filipe Silva <[email protected]>
26
 */
27
class Container implements ContainerInterface
28
{
29
30
    /**
31
     * @var DefinitionList
32
     */
33
    protected $definitions;
34
35
    /**
36
     * @var array
37
     */
38
    protected static $instances = [];
39
40
    /**
41
     * @readwrite
42
     * @var ContainerInterface
43
     */
44
    protected $parent;
45
46
    private static $containerKeys = [
47
        'Interop\Container\ContainerInterface',
48
        'Slick\Di\Container'
49
    ];
50
51
    /**
52
     * Initialise the container with an empty definitions list
53
     */
54 74
    public function __construct()
55
    {
56 74
        $this->definitions = new DefinitionList();
57 74
        $existingKey = reset(self::$containerKeys);
58 74
        if (array_key_exists($existingKey, static::$instances)) {
59 72
            $this->setParent(static::$instances[$existingKey]);
60 72
        }
61
62 74
        foreach (self::$containerKeys as $key) {
63 74
            $this->register($key, $this);
64 74
            static::$instances[$key] = $this;
65 74
        }
66 74
    }
67
68
    /**
69
     * Finds an entry of the container by its identifier and returns it.
70
     *
71
     * @param string $id Identifier of the entry to look for.
72
     *
73
     * @throws NotFoundException  No entry was found for this identifier.
74
     * @throws ContainerException Error while retrieving the entry.
75
     *
76
     * @return mixed Entry.
77
     */
78 52
    public function get($id)
79
    {
80 52
        if (!$this->has($id)) {
81 2
            throw new NotFoundException(
82 2
                "There is no entry with '{$id}' name in the ".
83
                "dependency container."
84 2
            );
85
        }
86
87 50
        $entry = (!$this->definitions->offsetExists($id))
88 50
            ? $this->parent->get($id)
89 50
            : $this->resolve($this->definitions[$id]);
90
91 50
        if (is_object($entry)) {
92 46
            $entry = $this->injectOn($entry);
93 46
        }
94
95 50
        return $entry;
96
    }
97
98
    /**
99
     * Returns true if the container can return an entry for the given identifier.
100
     * Returns false otherwise.
101
     *
102
     * @param string $id Identifier of the entry to look for.
103
     *
104
     * @return boolean
105
     */
106 56
    public function has($id)
107
    {
108 56
        if (!is_string($id)) {
109 28
            return false;
110
        }
111
112 56
        if (!$this->definitions->offsetExists($id)) {
113 18
            return $this->parentHas($id);
114
        }
115 52
        return true;
116
    }
117
118
    /**
119
     * Adds a definition or a value to the container
120
     *
121
     * If the $definition parameter is a scalar a Value definition is created
122
     * and added to the definitions list.
123
     *
124
     * If the $value is a callable a factory definition will be created
125
     *
126
     * @param string|DefinitionInterface $definition
127
     * @param mixed                      $value
128
     * @param array                      $parameters
129
     * @param Scope|string               $scope
130
     *
131
     * @return $this|self
132
     */
133 74
    public function register(
134
        $definition, $value = null, array $parameters = [],
135
        $scope = Scope::SINGLETON)
136
    {
137 74
        if (! $definition instanceof DefinitionInterface) {
138 72
            if (is_callable($value)) {
139 32
                $value = $this->createFactoryDefinition(
140 32
                    (string) $definition,
141 32
                    $value,
142 32
                    $parameters,
143 32
                    new Scope((string) $scope)
144 32
                );
145 32
            }
146
147 72
            $definition = ($value instanceof DefinitionInterface)
148 72
                ? $value->setName($definition)
0 ignored issues
show
Bug introduced by
The method setName() does not seem to exist on object<Slick\Di\DefinitionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
149 72
                : $definition = $this->createValueDefinition(
150 72
                    (string) $definition,
151
                    $value
152 72
                );
153 72
        }
154
155 72
        $definition->setContainer($this);
156
157 72
        $this->definitions->append($definition);
158 72
        return $this;
159
    }
160
161
    /**
162
     * Creates the object for provided class name
163
     *
164
     * This method creates factory definition that can be retrieved from
165
     * the container by using it FQ class name.
166
     *
167
     * If there are satisfiable dependencies in the container the are injected.
168
     *
169
     * @param string $className  FQ class name
170
     * @param array  $parameters An array of constructor parameters
171
     * @param string $scope      The definition scope
172
     *
173
     * @return mixed
174
     */
175 10
    public function make(
176
        $className, array $parameters = [], $scope = Scope::SINGLETON)
177
    {
178 10
        if (!class_exists($className)) {
179 2
            throw new InvalidArgumentException(
180
                "DI container cannot make object. Class does not exists."
181 2
            );
182
        }
183
184 8
        if (!$this->has($className)) {
185 4
            $this->registerObject($className, $parameters, $scope);
186 4
        }
187
188 8
        return $this->get($className);
189
    }
190
191
    /**
192
     * Inject known dependencies on provide object
193
     *
194
     * @param object $object
195
     *
196
     * @return mixed
197
     */
198 46
    public function injectOn($object)
199
    {
200 46
        $injectedObject = $object;
201 46
        $inspector = new DependencyInspector($this, $object);
202 46
        $definition = $inspector->getDefinition();
203 46
        if ($inspector->isSatisfiable()) {
204 46
            $injectedObject = $definition->resolve();
205 46
        }
206 46
        return $injectedObject;
207
    }
208
209
    /**
210
     * Set parent container for interoperability
211
     *
212
     * @ignoreInject
213
     * @param ContainerInterface $container
214
     * @return $this
215
     */
216 70
    public function setParent(ContainerInterface $container)
217
    {
218 70
        $this->parent = $container;
219 70
        return $this;
220
    }
221
222
    /**
223
     * Creates a value definition for register
224
     *
225
     * @param string $name  The name for the definition
226
     * @param mixed  $value The value that the definition will resolve
227
     *
228
     * @return Value A Value definition object
229
     */
230 72
    private function createValueDefinition($name, $value)
231
    {
232 72
        return new Value(
233
            [
234 72
                'name' => $name,
235
                'value' => $value
236 72
            ]
237 72
        );
238
    }
239
240
    /**
241
     * Creates a Factory definition
242
     *
243
     * @param string   $name
244
     * @param callable $callback
245
     * @param array    $params
246
     * @param Scope    $scope
247
     *
248
     * @return Factory
249
     */
250 32
    private function createFactoryDefinition(
251
        $name, Callable $callback, array $params, Scope $scope)
252
    {
253 32
        $definition = new Factory(['name' => $name]);
254
        return $definition
255 32
            ->setCallable($callback, $params)
256 32
            ->setScope($scope);
257
    }
258
259
    /**
260
     * Returns the resolved object for provide definition
261
     *
262
     * @param DefinitionInterface $definition
263
     * @return mixed|null
264
     */
265 50
    private function resolve(DefinitionInterface $definition)
266
    {
267 50
        if ($definition->getScope() == Scope::Prototype()) {
268 8
            return $definition->resolve();
269
        }
270 46
        return $this->resolveSingleton($definition);
271
    }
272
273
    /**
274
     * Resolve instances and save then for singleton use
275
     *
276
     * @param DefinitionInterface $definition
277
     * @return mixed
278
     */
279 46
    private function resolveSingleton(DefinitionInterface $definition)
280
    {
281 46
        $key = $definition->getName();
282 46
        $hasInstance = array_key_exists($key, static::$instances);
283
284 46
        if (!$hasInstance) {
285 14
            static::$instances[$key] = $definition->resolve();
286 14
        }
287 46
        return static::$instances[$key];
288
    }
289
290
    /**
291
     * Creates and registers an object definition
292
     *
293
     * @param string       $name       FQ class name
294
     * @param array        $parameters Constructor parameters
295
     * @param string $scope      Definition scope
296
     *
297
     * @return $this|self
298
     */
299 4
    private function registerObject($name, $parameters, $scope)
300
    {
301 4
        $inspector = new DependencyInspector($this, $name);
302 4
        $definition = $inspector->getDefinition();
303 4
        if (!empty($parameters)) {
304 2
            $definition->setConstructArgs($parameters);
305 2
        }
306 4
        $definition->setScope(new Scope((string) $scope));
307 4
        $this->register($definition);
308 4
        return $this;
309
    }
310
311
    /**
312
     * Check if parent has provided key
313
     *
314
     * @param string $key
315
     * @return bool
316
     */
317 18
    private function parentHas($key)
318
    {
319 18
        if (is_null($this->parent)) {
320 10
            return false;
321
        }
322 16
        return $this->parent->has($key);
323
    }
324
}
325