Completed
Branch master (370ffb)
by Roman
02:26
created

Container::offsetGet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace Lebran;
3
4
use Closure;
5
use ArrayAccess;
6
use ReflectionClass;
7
use ReflectionMethod;
8
use ReflectionFunction;
9
use Lebran\Container\NotFoundException;
10
use Lebran\Container\InjectableInterface;
11
use Interop\Container\ContainerInterface;
12
use Lebran\Container\ServiceProviderInterface;
13
14
/**
15
 * Lebran\Di it's a component that implements Dependency Injection/Service Location patterns.
16
 * Supports string, object and anonymous function definition. Allows using the array syntax.
17
 *
18
 *                              Examples
19
 *  <code>
20
 *      $di = \Lebran\Di\Container();
21
 *
22
 *      // Using string definition
23
 *      $di->set('test', '\Lebran\App\TestController');
24
 *
25
 *      // Using object definition (singleton)
26
 *      $di->set('test',  new \Lebran\App\TestController('param1'));
27
 *
28
 *      // Using anonymous function definition
29
 *      $di->set('test',  function ($param1, $param2) {
30
 *          return new \Lebran\App\TestController($param1, $param2)
31
 *      });
32
 *
33
 *  </code>
34
 *
35
 * @package    Di
36
 * @version    2.0.0
37
 * @author     Roman Kritskiy <[email protected]>
38
 * @license    GNU Licence
39
 * @copyright  2014 - 2015 Roman Kritskiy
40
 */
41
class Container implements ContainerInterface, ArrayAccess
42
{
43
    /**
44
     * @var self
45
     */
46
    protected static $instance;
47
48
    /**
49
     * Store services.
50
     *
51
     * @var array
52
     */
53
    protected $services = [];
54
55
    /**
56
     * @var array
57
     */
58
    protected $shared = [];
59
60
    /**
61
     * Returns last container instance.
62
     *
63
     * @return Container
64
     */
65
    public static function instance()
66
    {
67
        return static::$instance;
68
    }
69
70
    /**
71
     * Initialisation
72
     */
73
    public function __construct()
74
    {
75
        static::$instance = $this;
76
    }
77
78
    /**
79
     * Registers a service in the services container.
80
     *
81
     * @param string $id         Service id.
82
     * @param mixed  $definition Service definition.
83
     * @param bool   $shared     Shared or not.
84
     *
85
     * @return self
86
     */
87
    public function set($id, $definition, $shared = false)
88
    {
89
        $id = trim(trim($id, '\\'));
90
        if (is_string($definition)) {
91
            $definition = trim(trim($definition, '\\'));
92
        }
93
        $this->services[$id] = compact('definition', 'shared');
94
        return $this;
95
    }
96
97
    /**
98
     * Registers a shared service in the services container.
99
     *
100
     * @param string $id         Service id.
101
     * @param mixed  $definition Service definition.
102
     *
103
     * @return self
104
     */
105
    public function shared($id, $definition)
106
    {
107
        return $this->set($id, $definition, true);
108
    }
109
110
    /**
111
     * Finds an entry of the container by its identifier and returns it.
112
     *
113
     * @param string $id     Identifier of the entry to look for.
114
     * @param array  $params Parameter for service construct.
115
     *
116
     * @throws NotFoundException  No entry was found for this identifier.
117
     * @throws ContainerException Error while retrieving the entry.
118
     *
119
     * @return mixed Entry.
120
     */
121
    public function get($id, array $params = [])
122
    {
123
        if (array_key_exists($id, $this->shared)) {
124
            return $this->shared[$id];
125
        }
126
127
        $shared = false;
128
        if (array_key_exists($id, $this->services)) {
129
            $definition = $this->services[$id]['definition'];
130
            if ($this->services[$id]['shared']) {
131
                $shared = true;
132
            }
133
        } else {
134
            $definition = $id;
135
        }
136
        $instance = $this->resolveService($definition, $params);
137
138
        if ($shared) {
139
            $this->shared[$id] = $instance;
140
        }
141
142
        if ($instance instanceof InjectableInterface) {
143
            $instance->setDi($this);
144
        }
145
146
        return $instance;
147
    }
148
149
    /**
150
     *
151
     *
152
     * @param callable $callback
153
     * @param array    $params
154
     *
155
     * @return mixed
156
     * @throws ContainerException Error while retrieving the entry.
157
     * @throws NotFoundException No entry was found for this identifier.
158
     */
159
    public function call(callable $callback, array $params = [])
160
    {
161
        if (is_string($callback) && strpos($callback, '::') === true) {
162
            $callback = explode('::', $callback);
163
        }
164
        if (is_array($callback)) {
165
            $reflection = new ReflectionMethod($callback[0], $callback[1]);
166
        } else {
167
            $reflection = new ReflectionFunction($callback);
168
        }
169
170
        return call_user_func_array(
171
            $callback,
172
            $this->resolveOptions(
173
                $reflection->getParameters(),
174
                $params
175
            )
176
        );
177
    }
178
179
    /**
180
     * Resolves the service.
181
     *
182
     * @param mixed $definition The definition of service.
183
     * @param array $params     Parameters for service construct.
184
     *
185
     * @return mixed Entry.
186
     * @throws ContainerException Error while retrieving the entry.
187
     * @throws NotFoundException No entry was found for this identifier.
188
     */
189
    protected function resolveService($definition, array $params)
190
    {
191
        switch (gettype($definition)) {
192
            case 'string':
193
                if (array_key_exists($definition, $this->services)) {
194
                    return $this->get($definition, $params);
195
                } else if (class_exists($definition)) {
196
                    $parameters = [];
197
                    $reflection = new ReflectionClass($definition);
198
                    if (($construct = $reflection->getConstructor())) {
199
                        $parameters = $this->resolveOptions(
200
                            $construct->getParameters(),
201
                            $params
202
                        );
203
                    }
204
                    return $reflection->newInstanceArgs($parameters);
205
                } else {
206
                    throw new NotFoundException('');
207
                }
208
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
209
            case 'object':
210
                if ($definition instanceof Closure) {
211
                    return call_user_func_array($definition->bindTo($this), $params);
212
                } else {
213
                    return clone $definition;
214
                }
215
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
216
            default:
217
                throw new ContainerException('');
218
        }
219
    }
220
221
    /**
222
     * Resolve parameters of service construct.
223
     *
224
     * @param array $dependencies
225
     * @param array $parameters
226
     *
227
     * @return array Resolved parameters.
228
     * @throws ContainerException Error while retrieving the entry.
229
     * @throws NotFoundException No entry was found for this identifier.
230
     */
231
    protected function resolveOptions(array $dependencies, array $parameters)
232
    {
233
        foreach ($parameters as $key => $value) {
234
            if (is_numeric($key)) {
235
                unset($parameters[$key]);
236
                $parameters[$dependencies[$key]->name] = $value;
237
            }
238
        }
239
240
        $resolved = [];
241
        foreach ($dependencies as $parameter) {
242
            /** @var \ReflectionParameter $parameter */
243
            if (array_key_exists($parameter->name, $parameters)) {
244
                $resolved[] = $parameters[$parameter->name];
245
            } else if (($type = $parameter->getClass())) {
246
                try{
247
                    $params = [];
248
                    if (array_key_exists($type->name, $parameters)) {
249
                        $params = $parameters[$type->name];
250
                        unset($parameters[$type->name]);
251
                    }
252
                    $resolved[] = $this->get($type->name, $params);
253
                } catch(ContainerException $e){
254
                    if ($parameter->isOptional()) {
255
                        $resolved[] = $parameter->getDefaultValue();
256
                    } else {
257
                        throw $e;
258
                    }
259
                }
260
            } else {
261
                if ($parameter->isOptional()) {
262
                    $resolved[] = $parameter->getDefaultValue();
263
                } else {
264
                    throw new ContainerException('');
265
                }
266
            }
267
        }
268
269
        return $resolved;
270
    }
271
272
    /**
273
     * Removes a service in the services container.
274
     *
275
     * @param string $id Service id.
276
     *
277
     * @return void
278
     */
279
    public function remove($id)
280
    {
281
        unset($this->services[$id]);
282
    }
283
284
    /**
285
     * Returns true if the container can return an entry for the given identifier.
286
     * Returns false otherwise.
287
     *
288
     * @param string $id Identifier of the entry to look for.
289
     *
290
     * @return bool
291
     */
292
    public function has($id)
293
    {
294
        return array_key_exists($id, $this->services);
295
    }
296
297
    /**
298
     * Merge two containers into one.
299
     *
300
     * @param ServiceProviderInterface $provider Another container.
301
     *
302
     * @return self
303
     */
304
    public function register(ServiceProviderInterface $provider)
305
    {
306
        $provider->register($this);
307
        return $this;
308
    }
309
310
    /**
311
     * Check whether the service is shared or not.
312
     *
313
     * @param string $id Service id.
314
     *
315
     * @return bool True if shared, false - not.
316
     */
317
    public function isShared($id)
318
    {
319
        return $this->has($id)?$this->services[$id]['shared']:false;
320
    }
321
322
    /**
323
     * Sets if the service is shared or not.
324
     *
325
     * @param string $id     Service id.
326
     * @param bool   $shared Shared or not.
327
     *
328
     * @return self
329
     */
330
    public function setShared($id, $shared = true)
331
    {
332
        if ($this->has($id)) {
333
            $this->services[$id]['shared'] = $shared;
334
        }
335
        return $this;
336
    }
337
338
    /**
339
     * Allows to register a shared service using the array syntax.
340
     *
341
     * @param string $id         Service id.
342
     * @param mixed  $definition Service definition.
343
     *
344
     * @return self
345
     */
346
    public function offsetSet($id, $definition)
347
    {
348
        return $this->set($id, $definition);
349
    }
350
351
    /**
352
     * Finds an entry of the container by its identifier and returns it.
353
     *
354
     * @param string $id Identifier of the entry to look for.
355
     *
356
     * @throws NotFoundException  No entry was found for this identifier.
357
     * @throws ContainerException Error while retrieving the entry.
358
     *
359
     * @return mixed Entry.
360
     */
361
    public function offsetGet($id)
362
    {
363
        return $this->get($id);
364
    }
365
366
    /**
367
     * Removes a service from the services container using the array syntax.
368
     *
369
     * @param string $id Service id.
370
     *
371
     * @return void
372
     */
373
    public function offsetUnset($id)
374
    {
375
        $this->remove($id);
376
    }
377
378
    /**
379
     * Returns true if the container can return an entry for the given identifier.
380
     * Returns false otherwise.
381
     *
382
     * @param string $id Identifier of the entry to look for.
383
     *
384
     * @return bool
385
     */
386
    public function offsetExists($id)
387
    {
388
        return $this->has($id);
389
    }
390
391
    /**
392
     * Allows to register a shared service using the array syntax.
393
     *
394
     * @param string $id         Service id.
395
     * @param mixed  $definition Service definition.
396
     *
397
     * @return self
398
     */
399
    public function __set($id, $definition)
400
    {
401
        $this->set($id, $definition, true);
402
    }
403
404
    /**
405
     * Finds an entry of the container by its identifier and returns it.
406
     *
407
     * @param string $id Identifier of the entry to look for.
408
     *
409
     * @throws NotFoundException  No entry was found for this identifier.
410
     * @throws ContainerException Error while retrieving the entry.
411
     *
412
     * @return mixed Entry.
413
     */
414
    public function __get($id)
415
    {
416
        return $this->get($id);
417
    }
418
419
    /**
420
     * Removes a service from the services container using the array syntax.
421
     *
422
     * @param string $id Service id.
423
     *
424
     * @return void
425
     */
426
    public function __unset($id)
427
    {
428
        $this->remove($id);
429
    }
430
431
    /**
432
     * Returns true if the container can return an entry for the given identifier.
433
     * Returns false otherwise.
434
     *
435
     * @param string $id Identifier of the entry to look for.
436
     *
437
     * @return bool
438
     */
439
    public function __isset($id)
440
    {
441
        return $this->has($id);
442
    }
443
}