Passed
Push — master ( e77146...4128b2 )
by Alexey
03:21
created

MutableContainer::isConcrete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.5

Importance

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