Passed
Push — master ( 559900...ef8c69 )
by Alexey
10:10
created

MutableContainer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1.0019

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 9
ccs 7
cts 8
cp 0.875
crap 1.0019
rs 9.6666
c 0
b 0
f 0
1
<?php declare(strict_types = 1);
2
3
namespace Venta\Container;
4
5
use Closure;
6
use InvalidArgumentException;
7
use Venta\Contracts\Container\Invoker as InvokerContract;
8
use Venta\Contracts\Container\MutableContainer as MutableContainerContract;
9
use Venta\Contracts\Container\ServiceDecorator as ServiceDecoratorContract;
10
use Venta\Contracts\Container\ServiceInflector as ServiceInflectorContract;
11
12
/**
13
 * Class MutableContainer
14
 *
15
 * @package Venta\Container
16
 */
17
class MutableContainer extends AbstractContainer implements MutableContainerContract
18
{
19
20
    /**
21
     * @var ServiceDecoratorContract
22
     */
23
    private $decorator;
24
25
    /**
26
     * @var ServiceInflectorContract
27
     */
28
    private $inflector;
29
30
    /**
31
     * @inheritDoc
32
     */
33 31
    public function __construct()
34
    {
35 31
        $resolver = new ArgumentResolver($this);
36 31
        parent::__construct($resolver);
37 31
        $this->setInflector(new ServiceInflector($resolver));
38 31
        $this->setDecorator(new ServiceDecorator($this, $this->inflector, $this->invoker()));
39
40 31
        $this->bindInstance(InvokerContract::class, $this->invoker());
41 31
    }
42
43
44
    /**
45
     * @inheritDoc
46
     */
47 4
    public function addDecorator(string $id, $decorator)
48
    {
49 4
        $id = $this->normalize($id);
50
51
        // Check if correct id is provided.
52 4
        if (!$this->isResolvableService($id)) {
53
            throw new InvalidArgumentException('Invalid id provided.');
54
        }
55
56 4
        $this->decorator->addDecorator($id, $decorator);
57 4
    }
58
59
    /**
60
     * @inheritDoc
61
     */
62 4
    public function addInflection(string $id, string $method, array $arguments = [])
63
    {
64 4
        $this->inflector->addInflection($id, $method, $arguments);
65 3
    }
66
67
    /**
68
     * @inheritDoc
69
     */
70 10
    public function bind(string $id, $service)
71
    {
72 10
        if (is_string($service)) {
73 8
            $this->bindClass($id, $service);
74 2
        } elseif (is_object($service)) {
75 2
            $this->bindInstance($id, $service);
76
        } else {
77
            throw new InvalidArgumentException('Invalid service provided. Class name or instance expected.');
78
        }
79 8
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84 12
    public function factory(string $id, $callable, $shared = false)
85
    {
86 12
        $reflectedCallable = new Invokable($callable);
87 12
        if (!$this->isResolvableCallable($reflectedCallable)) {
88
            throw new InvalidArgumentException('Invalid callable provided.');
89
        }
90
91
        $this->register($id, $shared, function ($id) use ($reflectedCallable) {
92 12
            $this->callableDefinitions[$id] = $reflectedCallable;
93 12
        });
94 12
    }
95
96
    /**
97
     * @inheritDoc
98
     */
99 27
    protected function instantiateService(string $id, array $arguments)
100
    {
101 27
        $object = parent::instantiateService($id, $arguments);
102 22
        $this->inflector->inflect($object);
103
104 21
        return $this->decorator->decorate($id, $object, $this->isShared($id));
105
    }
106
107
    /**
108
     * @param ServiceDecoratorContract $decorator
109
     */
110 31
    protected function setDecorator(ServiceDecoratorContract $decorator)
111
    {
112 31
        $this->decorator = $decorator;
113 31
    }
114
115
    /**
116
     * @param ServiceInflectorContract $inflector
117
     */
118 31
    protected function setInflector(ServiceInflectorContract $inflector)
119
    {
120 31
        $this->inflector = $inflector;
121 31
    }
122
123
    /**
124
     * @param string $id
125
     * @param string $class
126
     * @return void
127
     * @throws InvalidArgumentException
128
     */
129 8
    private function bindClass(string $id, string $class)
130
    {
131 8
        if (!$this->isResolvableService($class)) {
132 1
            throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
133
        }
134
        $this->register($id, true, function ($id) use ($class) {
135 6
                $this->classDefinitions[$id] = $class;
136 7
        });
137 6
    }
138
139
    /**
140
     * @param string $id
141
     * @param object $instance
142
     * @return void
143
     * @throws InvalidArgumentException
144
     */
145 31
    private function bindInstance(string $id, $instance)
146
    {
147 31
        if (!$this->isConcrete($instance)) {
148
            throw new InvalidArgumentException('Invalid instance provided.');
149
        }
150 31
        $this->register($id, true, function ($id) use ($instance) {
151 31
            $this->instances[$id] = $instance;
152 31
        });
153 31
    }
154
155
    /**
156
     * Check if subject service is an object instance.
157
     *
158
     * @param mixed $service
159
     * @return bool
160
     */
161 31
    private function isConcrete($service): bool
162
    {
163 31
        return is_object($service) && !$service instanceof Closure;
164
    }
165
166
    /**
167
     * Verifies that provided callable can be called by service container.
168
     *
169
     * @param Invokable $reflectedCallable
170
     * @return bool
171
     */
172 12
    private function isResolvableCallable(Invokable $reflectedCallable): bool
173
    {
174
        // If array represents callable we need to be sure it's an object or a resolvable service id.
175 12
        $callable = $reflectedCallable->callable();
176
177 12
        return $reflectedCallable->isFunction()
178 6
               || is_object($callable[0])
179 12
               || $this->isResolvableService($callable[0]);
180
    }
181
182
    /**
183
     * Registers binding.
184
     * After this method call binding can be resolved by container.
185
     *
186
     * @param string $id
187
     * @param bool $shared
188
     * @param Closure $registrationCallback
189
     * @return void
190
     */
191 31
    private function register(string $id, bool $shared, Closure $registrationCallback)
192
    {
193
        // Check if correct service is provided.
194 31
        $this->validateId($id);
195 31
        $id = $this->normalize($id);
196
197
        // Clean up previous bindings, if any.
198 31
        unset($this->instances[$id], $this->shared[$id], $this->keys[$id]);
199
200
        // Register service with provided callback.
201 31
        $registrationCallback($id);
202
203
        // Mark service as shared when needed.
204 31
        $this->shared[$id] = $shared ?: null;
205
206
        // Save service key to make it recognizable by container.
207 31
        $this->keys[$id] = true;
208 31
    }
209
210
    /**
211
     * Validate service identifier. Throw an Exception in case of invalid value.
212
     *
213
     * @param string $id
214
     * @return void
215
     * @throws InvalidArgumentException
216
     */
217 31
    private function validateId(string $id)
218
    {
219 31
        if (!interface_exists($id) && !class_exists($id)) {
220 1
            throw new InvalidArgumentException(
221 1
                sprintf('Invalid service id "%s". Service id must be an existing interface or class name.', $id)
222
            );
223
        }
224 31
    }
225
226
}