Completed
Push — master ( 0e4d92...a2a057 )
by Roman
02:05
created

Container::resolveOptions()   C

Complexity

Conditions 8
Paths 15

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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