Passed
Push — master ( 066bd3...0139c3 )
by Alexey
03:59
created

Container::setDecorator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types = 1);
2
3
namespace Venta\Container;
4
5
use Closure;
6
use ReflectionClass;
7
use Venta\Container\Exception\ArgumentResolverException;
8
use Venta\Container\Exception\CircularReferenceException;
9
use Venta\Container\Exception\NotFoundException;
10
use Venta\Container\Exception\UninstantiableServiceException;
11
use Venta\Container\Exception\UnresolvableDependencyException;
12
use Venta\Contracts\Container\Container as ContainerContract;
13
use Venta\Contracts\Container\Invoker as InvokerContract;
14
use Venta\Contracts\Container\ServiceDecorator as ServiceDecoratorContract;
15
use Venta\Contracts\Container\ServiceInflector as ServiceInflectorContract;
16
17
/**
18
 * Class Container
19
 *
20
 * @package Venta\Container
21
 */
22
class Container extends ServiceRegistry implements ContainerContract
23
{
24
    /**
25
     * @var ServiceDecoratorContract
26
     */
27
    private $decorator;
28
29
    /**
30
     * Array of container service callable factories.
31
     *
32
     * @var Closure[]
33
     */
34
    private $factories = [];
35
36
    /**
37
     * @var ServiceInflectorContract
38
     */
39
    private $inflector;
40
41
    /**
42
     * @var InvokerContract
43
     */
44
    private $invoker;
45
46
    /**
47
     * Array of container service identifiers currently being resolved.
48
     *
49
     * @var string[]
50
     */
51
    private $resolving = [];
52
53
    /**
54
     * Container constructor.
55
     */
56 45
    public function __construct()
57
    {
58 45
        $argumentResolver = new ArgumentResolver($this);
59 45
        $this->setInvoker(new Invoker($this, $argumentResolver));
60 45
        $this->setInflector(new ServiceInflector($argumentResolver));
61 45
        $this->setDecorator(new ServiceDecorator($this, $this->inflector(), $this->invoker));
62 45
    }
63
64
    /**
65
     * @inheritDoc
66
     * @param callable|string $callable Callable to call OR class name to instantiate and invoke.
67
     */
68 12
    public function call($callable, array $arguments = [])
69
    {
70 12
        return $this->invoker->call($callable, $arguments);
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76 36
    public function get($id, array $arguments = [])
77
    {
78 36
        $id = $this->normalize($id);
79
        // We try to resolve alias first to get a real service id.
80 36
        if (!$this->isResolvableService($id)) {
81 2
            throw new NotFoundException($id, $this->resolving);
82
        }
83
84
        // Look up service in resolved instances first.
85 34
        $object = $this->instance($id);
86 34
        if (!empty($object)) {
87 5
            $object = $this->decorator()->decorate($id, $object, true);
88
89 5
            return $object;
90
        }
91
92
        // Detect circular references.
93
        // We mark service as being resolved to detect circular references through out the resolution chain.
94 30
        if (isset($this->resolving[$id])) {
95 3
            throw new CircularReferenceException($id, $this->resolving);
96
        } else {
97 30
            $this->resolving[$id] = $id;
98
        }
99
100
        try {
101
            // Instantiate service and apply inflections.
102 30
            $object = $this->instantiateService($id, $arguments);
103 26
            $this->inflector()->inflect($object);
104 25
            $object = $this->decorator()->decorate($id, $object, $this->isShared($id));
105
106
            // Cache shared instances.
107 25
            if ($this->isShared($id)) {
108 1
                $this->bindInstance($id, $object);
109
            }
110
111 25
            return $object;
112 5
        } catch (ArgumentResolverException $resolveException) {
113 1
            throw new UnresolvableDependencyException($id, $this->resolving, $resolveException);
114
        } finally {
115 30
            unset($this->resolving[$id]);
116
        }
117
    }
118
119
    /**
120
     * @inheritDoc
121
     */
122 24
    public function has($id): bool
123
    {
124 24
        return $this->isResolvableService($this->normalize($id));
125
    }
126
127
    /**
128
     * @inheritDoc
129
     */
130 1
    public function isCallable($callable): bool
131
    {
132 1
        return $this->invoker->isCallable($callable);
133
    }
134
135
    /**
136
     * @inheritDoc
137
     */
138 29
    protected function decorator(): ServiceDecoratorContract
139
    {
140 29
        return $this->decorator;
141
    }
142
143
    /**
144
     * @inheritDoc
145
     */
146 45
    protected function inflector(): ServiceInflectorContract
147
    {
148 45
        return $this->inflector;
149
    }
150
151
    /**
152
     * @param ServiceDecoratorContract $decorator
153
     */
154 45
    protected function setDecorator(ServiceDecoratorContract $decorator)
155
    {
156 45
        $this->decorator = $decorator;
157 45
    }
158
159
    /**
160
     * @param ServiceInflectorContract $inflector
161
     */
162 45
    protected function setInflector(ServiceInflectorContract $inflector)
163
    {
164 45
        $this->inflector = $inflector;
165 45
    }
166
167
    /**
168
     * @param InvokerContract $invoker
169
     * @return void
170
     */
171 45
    protected function setInvoker(InvokerContract $invoker)
172
    {
173 45
        $this->invoker = $invoker;
174 45
    }
175
176
    /**
177
     * Forbid container cloning.
178
     *
179
     * @codeCoverageIgnore
180
     */
181
    private function __clone()
182
    {
183
    }
184
185
    /**
186
     * Create callable factory with resolved arguments from callable.
187
     *
188
     * @param Invokable $invokable
189
     * @return Closure
190
     */
191 12
    private function createServiceFactoryFromCallable(Invokable $invokable): Closure
192
    {
193
        return function (array $arguments = []) use ($invokable) {
194 12
            return $this->invoker->invoke($invokable, $arguments);
195 12
        };
196
    }
197
198
    /**
199
     * Create callable factory with resolved arguments from class name.
200
     *
201
     * @param string $class
202
     * @return Closure
203
     * @throws UninstantiableServiceException
204
     */
205 23
    private function createServiceFactoryFromClass(string $class): Closure
206
    {
207 23
        $reflection = new ReflectionClass($class);
208 23
        if (!$reflection->isInstantiable()) {
209 1
            throw new UninstantiableServiceException($class, $this->resolving);
210
        }
211 22
        $constructor = $reflection->getConstructor();
212
213 22
        if ($constructor && $constructor->getNumberOfParameters() > 0) {
214 16
            $invokable = new Invokable($constructor);
215
216
            return function (array $arguments = []) use ($invokable) {
217 16
                return $this->invoker->invoke($invokable, $arguments);
218 16
            };
219
        }
220
221 20
        return function () use ($class) {
222 20
            return new $class();
223 20
        };
224
    }
225
226
    /**
227
     * Create callable factory for the subject service.
228
     *
229
     * @param string $id
230
     * @param array $arguments
231
     * @return mixed
232
     */
233 30
    private function instantiateService(string $id, array $arguments)
234
    {
235 30
        $instance = $this->instance($id);
236 30
        if (!empty($instance)) {
237
            return $instance;
238
        }
239
240 30
        if (!isset($this->factories[$id])) {
241 30
            if (!empty($this->callableDefinition($id))) {
242 12
                $this->factories[$id] = $this->createServiceFactoryFromCallable($this->callableDefinition($id));
243 23
            } elseif (!empty($this->classDefinition($id)) && $this->classDefinition($id) !== $id) {
244
                // Recursive call allows to bind contract to contract.
245 4
                return $this->instantiateService($this->classDefinition($id), $arguments);
246
            } else {
247 23
                $this->factories[$id] = $this->createServiceFactoryFromClass($id);
248
            }
249
        }
250
251 29
        return ($this->factories[$id])($arguments);
252
    }
253
254
}
255