Container::__set()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
namespace Lebran;
3
4
use Closure;
5
use ArrayAccess;
6
use ReflectionClass;
7
use ReflectionMethod;
8
use ReflectionFunction;
9
use ReflectionParameter;
10
use Lebran\Container\NotFoundException;
11
use Lebran\Container\InjectableInterface;
12
use Interop\Container\ContainerInterface;
13
use Lebran\Container\ServiceProviderInterface;
14
15
/**
16
 * Lebran\Container it's a component that implements Dependency Injection/Service Location patterns.
17
 * Supports string, object and anonymous function definition. Allows using the array and magical syntax.
18
 *
19
 *                              Example
20
 *  <code>
21
 *      // Create service container
22
 *      $di = new \Lebran\Container();
23
 *
24
 *      // Container supports 3 types of definition
25
 *
26
 *      // Type 1: Object
27
 *      $di->set('myservice', new \MyNamespace\MyService());
28
 *
29
 *      // Type 2: String
30
 *      $di->set('myservice2', '\MyNamespace\MyService2');
31
 *
32
 *      // Type 3: Closure
33
 *      $di->set('myservice3', function(){
34
 *          return new \MyNamespace\MyService3();
35
 *      });
36
 *
37
 *      // Getting service
38
 *      $di->get('myservice');
39
 *  </code>
40
 *
41
 * @package    Container
42
 * @version    1.0
43
 * @author     Roman Kritskiy <[email protected]>
44
 * @license    MIT
45
 * @copyright  2015 - 2016 Roman Kritskiy
46
 */
47
class Container implements ContainerInterface, ArrayAccess
48
{
49
    const MAX_DEPENDENCY_LEVEL = 30;
50
51
    /**
52
     * @var self Store for last container instance
53
     */
54
    protected static $instance;
55
56
    /**
57
     * @var array Store for services.
58
     */
59
    protected $services = [];
60
61
    /**
62
     * @var array Store for shared services.
63
     */
64
    protected $shared = [];
65
66
    /**
67
     * @var int
68
     */
69
    protected $level = 0;
70
71
    /**
72
     * Returns last container instance.
73
     *
74
     * @return Container Last container instance.
75
     */
76
    public static function instance()
77
    {
78
        return static::$instance;
79
    }
80
81
    /**
82
     * Initialisation
83
     */
84
    public function __construct()
85
    {
86
        static::$instance = $this;
87
    }
88
89
    /**
90
     * Merge two containers into one.
91
     *
92
     * @param ServiceProviderInterface $provider Service provider.
93
     *
94
     * @return self
95
     */
96
    public function register(ServiceProviderInterface $provider)
97
    {
98
        $provider->register($this);
99
        return $this;
100
    }
101
102
    /**
103
     * Registers a service in the container.
104
     *
105
     * @param string $id         Service id.
106
     * @param mixed  $definition Service definition.
107
     * @param bool   $shared     Shared or not.
108
     *
109
     * @return $this
110
     * @throws ContainerException Error while retrieving the entry.
111
     */
112
    public function set($id, $definition, $shared = false)
113
    {
114
        if (is_string($definition)) {
115
            $definition = $this->normalize($definition);
116
        }
117
        $this->services[$this->normalize($id)] = compact('definition', 'shared');
118
        return $this;
119
    }
120
121
    /**
122
     * Normalize service name.
123
     *
124
     * @param string $id Service id.
125
     *
126
     * @return string Normalized name.
127
     */
128
    protected function normalize($id)
129
    {
130
        return trim(trim($id), '\\');
131
    }
132
133
    /**
134
     * Registers a shared service in the container.
135
     *
136
     * @param string $id         Service id.
137
     * @param mixed  $definition Service definition.
138
     *
139
     * @return $this
140
     */
141
    public function shared($id, $definition)
142
    {
143
        return $this->set($id, $definition, true);
144
    }
145
146
    /**
147
     * Check whether the service is shared or not.
148
     *
149
     * @param string $id Service id.
150
     *
151
     * @return bool True if shared, false - not.
152
     */
153
    public function isShared($id)
154
    {
155
        return $this->has($id) ? $this->services[$id]['shared'] : false;
156
    }
157
158
    /**
159
     * Sets if the service is shared or not.
160
     *
161
     * @param string $id     Service id.
162
     * @param bool   $shared Shared or not.
163
     *
164
     * @return self
165
     * @throws NotFoundException No entry was found for this identifier.
166
     */
167
    public function setShared($id, $shared = true)
168
    {
169
        if ($this->has($id)) {
170
            $this->services[$id]['shared'] = $shared;
171
        } else {
172
            throw new NotFoundException('');
173
        }
174
        return $this;
175
    }
176
177
    /**
178
     * Finds an entry of the container by its identifier and returns it.
179
     *
180
     * @param string $id         Identifier of the entry to look for.
181
     * @param array  $parameters Parameter for service construct.
182
     *
183
     * @throws NotFoundException  No entry was found for this identifier.
184
     * @throws ContainerException Error while retrieving the entry.
185
     *
186
     * @return mixed Entry.
187
     */
188
    public function get($id, array $parameters = [])
189
    {
190
        if($this->level++ > static::MAX_DEPENDENCY_LEVEL){
191
            throw new ContainerException('Circular dependency.');
192
        }
193
194
        if (array_key_exists($id, $this->shared)) {
195
            return $this->shared[$id];
196
        }
197
198
        $instance = $this->resolveService(
199
            $this->has($id) ? $this->services[$id]['definition'] : $id,
200
            $parameters
201
        );
202
203
        if ($this->has($id) && $this->services[$id]['shared']) {
204
            $this->shared[$id] = $instance;
205
        }
206
207
        if ($instance instanceof InjectableInterface) {
208
            $instance->setDi($this);
209
        }
210
        $this->level = 0;
211
        return $instance;
212
    }
213
214
    /**
215
     * Resolves the service.
216
     *
217
     * @param mixed $definition The definition of service.
218
     * @param array $parameters Parameters for service construct.
219
     *
220
     * @return mixed Entry.
221
     * @throws ContainerException Error while retrieving the entry.
222
     * @throws NotFoundException No entry was found for this identifier.
223
     */
224
    protected function resolveService($definition, array $parameters = [])
225
    {
226
        switch (gettype($definition)) {
227
            case 'string':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
228
                if ($this->has($definition)) {
229
                    return $this->get($definition, $parameters);
230
                } else if (class_exists($definition)) {
231
                    $reflection = new ReflectionClass($definition);
232
                    if (($construct = $reflection->getConstructor())) {
233
                        $parameters = $this->resolveOptions(
234
                            $construct->getParameters(),
235
                            $parameters
236
                        );
237
                    }
238
                    return $reflection->newInstanceArgs($parameters);
239
                } else {
240
                    throw new NotFoundException('');
241
                }
242
            case 'object':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
243
                if ($definition instanceof Closure) {
244
                    return call_user_func_array($definition->bindTo($this), $parameters);
245
                } else {
246
                    return clone $definition;
247
                }
248
            default:
249
                throw new ContainerException('Type of definition is not correct.');
250
        }
251
    }
252
253
    /**
254
     * Resolve callback dependencies and executes him.
255
     *
256
     * @param callable $callback
257
     * @param array    $parameters
258
     *
259
     * @return mixed
260
     * @throws ContainerException Error while retrieving the entry.
261
     * @throws NotFoundException No entry was found for this identifier.
262
     */
263
    public function call(callable $callback, array $parameters = [])
264
    {
265
        if (is_array($callback)) {
266
            $reflection = new ReflectionMethod($callback[0], $callback[1]);
267
        } else {
268
            $reflection = new ReflectionFunction($callback);
269
        }
270
271
        return call_user_func_array(
272
            $callback,
273
            $this->resolveOptions(
274
                $reflection->getParameters(),
275
                $parameters
276
            )
277
        );
278
    }
279
280
    /**
281
     * Resolve parameters of service.
282
     *
283
     * @param array $dependencies
284
     * @param array $parameters
285
     *
286
     * @return array Resolved parameters.
287
     * @throws ContainerException Error while retrieving the entry.
288
     * @throws NotFoundException No entry was found for this identifier.
289
     */
290
    protected function resolveOptions(array $dependencies, array $parameters)
291
    {
292
        $resolved = [];
293
        foreach ($parameters as $key => $value) {
294
            if (is_numeric($key)) {
295
                unset($parameters[$key]);
296
                $parameters[$dependencies[$key]->name] = $value;
297
            }
298
        }
299
300
        foreach ($dependencies as $parameter) {
301
            /** @var ReflectionParameter $parameter */
302
            if (array_key_exists($parameter->name, $parameters)) {
303
                $resolved[] = $parameters[$parameter->name];
304
            } else if (($type = $parameter->getClass())) {
305
                $type       = $type->name;
306
                $resolved[] = $this->get(
307
                    $type,
308
                    array_key_exists($type, $parameters) ? $parameters[$type] : []
309
                );
310
            } else if ($parameter->isDefaultValueAvailable()) {
311
                $resolved[] = $parameter->getDefaultValue();
312
            } else {
313
                throw new ContainerException('Parameter "'.$parameter->name.'" not passed.');
314
            }
315
        }
316
317
        return $resolved;
318
    }
319
320
    /**
321
     * Removes a service in the services container.
322
     *
323
     * @param string $id Service id.
324
     *
325
     * @return void
326
     */
327
    public function remove($id)
328
    {
329
        unset($this->services[$id]);
330
    }
331
332
    /**
333
     * Returns true if the container can return an entry for the given identifier.
334
     * Returns false otherwise.
335
     *
336
     * @param string $id Identifier of the entry to look for.
337
     *
338
     * @return bool
339
     */
340
    public function has($id)
341
    {
342
        return array_key_exists($id, $this->services);
343
    }
344
345
    /**
346
     * Allows to register a shared service using the array syntax.
347
     *
348
     * @param string $id         Service id.
349
     * @param mixed  $definition Service definition.
350
     *
351
     * @return self
352
     */
353
    public function offsetSet($id, $definition)
354
    {
355
        return $this->set($id, $definition);
356
    }
357
358
    /**
359
     * Finds an entry of the container by its identifier and returns it.
360
     *
361
     * @param string $id Identifier of the entry to look for.
362
     *
363
     * @throws NotFoundException  No entry was found for this identifier.
364
     * @throws ContainerException Error while retrieving the entry.
365
     *
366
     * @return mixed Entry.
367
     */
368
    public function offsetGet($id)
369
    {
370
        return $this->get($id);
371
    }
372
373
    /**
374
     * Removes a service from the services container using the array syntax.
375
     *
376
     * @param string $id Service id.
377
     *
378
     * @return void
379
     */
380
    public function offsetUnset($id)
381
    {
382
        $this->remove($id);
383
    }
384
385
    /**
386
     * Returns true if the container can return an entry for the given identifier.
387
     * Returns false otherwise.
388
     *
389
     * @param string $id Identifier of the entry to look for.
390
     *
391
     * @return bool
392
     */
393
    public function offsetExists($id)
394
    {
395
        return $this->has($id);
396
    }
397
398
    /**
399
     * Allows to register a shared service using the array syntax.
400
     *
401
     * @param string $id         Service id.
402
     * @param mixed  $definition Service definition.
403
     *
404
     * @return self
405
     */
406
    public function __set($id, $definition)
407
    {
408
        $this->shared($id, $definition);
409
    }
410
411
    /**
412
     * Finds an entry of the container by its identifier and returns it.
413
     *
414
     * @param string $id Identifier of the entry to look for.
415
     *
416
     * @throws NotFoundException  No entry was found for this identifier.
417
     * @throws ContainerException Error while retrieving the entry.
418
     *
419
     * @return mixed Entry.
420
     */
421
    public function __get($id)
422
    {
423
        return $this->get($id);
424
    }
425
426
    /**
427
     * Removes a service from the services container using the array syntax.
428
     *
429
     * @param string $id Service id.
430
     *
431
     * @return void
432
     */
433
    public function __unset($id)
434
    {
435
        $this->remove($id);
436
    }
437
438
    /**
439
     * Returns true if the container can return an entry for the given identifier.
440
     * Returns false otherwise.
441
     *
442
     * @param string $id Identifier of the entry to look for.
443
     *
444
     * @return bool
445
     */
446
    public function __isset($id)
447
    {
448
        return $this->has($id);
449
    }
450
}