Passed
Push — master ( 0452a9...a6d17d )
by Alexey
04:49
created

MutableContainer::isResolvableCallable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.2098

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 9
ccs 5
cts 7
cp 0.7143
crap 3.2098
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\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 12
    public function bind(string $id, $service)
68
    {
69 12
        if (is_string($service)) {
70 8
            $this->bindClass($id, $service);
71 4
        } elseif (is_object($service)) {
72 4
            $this->bindInstance($id, $service);
73
        } else {
74
            throw new InvalidArgumentException('Invalid service provided. Class name or instance expected.');
75
        }
76 10
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 12
    public function factory(string $id, $callable, $shared = false)
82
    {
83 12
        $reflectedCallable = new Invokable($callable);
84 12
        if (!$this->isResolvableCallable($reflectedCallable)) {
85
            throw new InvalidArgumentException('Invalid callable provided.');
86
        }
87
88
        $this->register($id, $shared, function ($id) use ($reflectedCallable) {
89 12
            $this->callableDefinitions[$id] = $reflectedCallable;
90 12
        });
91 12
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96 36
    protected function instantiateService(string $id, array $arguments)
97
    {
98 36
        $object = parent::instantiateService($id, $arguments);
99 30
        $this->inflector->inflect($object);
100
101 29
        return $this->decorator->decorate($id, $object, $this->isShared($id));
102
    }
103
104
    /**
105
     * @param ServiceDecoratorContract $decorator
106
     */
107 44
    protected function setDecorator(ServiceDecoratorContract $decorator)
108
    {
109 44
        $this->decorator = $decorator;
110 44
    }
111
112
    /**
113
     * @param ServiceInflectorContract $inflector
114
     */
115 44
    protected function setInflector(ServiceInflectorContract $inflector)
116
    {
117 44
        $this->inflector = $inflector;
118 44
    }
119
120
    /**
121
     * @param string $id
122
     * @param string $class
123
     * @return void
124
     * @throws InvalidArgumentException
125
     */
126 8
    private function bindClass(string $id, string $class)
127
    {
128 8
        if (!$this->isResolvableService($class)) {
129 1
            throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
130
        }
131
        $this->register($id, true, function ($id) use ($class) {
132 6
                $this->classDefinitions[$id] = $class;
133 7
        });
134 6
    }
135
136
    /**
137
     * @param string $id
138
     * @param object $instance
139
     * @return void
140
     * @throws InvalidArgumentException
141
     */
142 4
    private function bindInstance(string $id, $instance)
143
    {
144 4
        if (!$this->isConcrete($instance)) {
145
            throw new InvalidArgumentException('Invalid instance provided.');
146
        }
147 4
        $this->register($id, true, function ($id) use ($instance) {
148 4
            $this->instances[$id] = $instance;
149 4
        });
150 4
    }
151
152
    /**
153
     * Check if subject service is an object instance.
154
     *
155
     * @param mixed $service
156
     * @return bool
157
     */
158 4
    private function isConcrete($service): bool
159
    {
160 4
        return is_object($service) && !$service instanceof Closure;
161
    }
162
163
    /**
164
     * Verifies that provided callable can be called by service container.
165
     *
166
     * @param Invokable $reflectedCallable
167
     * @return bool
168
     */
169 12
    private function isResolvableCallable(Invokable $reflectedCallable): bool
170
    {
171
        // If array represents callable we need to be sure it's an object or a resolvable service id.
172 12
        $callable = $reflectedCallable->callable();
173
174 12
        return $reflectedCallable->isFunction()
175 6
               || is_object($callable[0])
176 12
               || $this->isResolvableService($callable[0]);
177
    }
178
179
    /**
180
     * Registers binding.
181
     * After this method call binding can be resolved by container.
182
     *
183
     * @param string $id
184
     * @param bool $shared
185
     * @param Closure $registrationCallback
186
     * @return void
187
     */
188 23
    private function register(string $id, bool $shared, Closure $registrationCallback)
189
    {
190
        // Check if correct service is provided.
191 23
        $this->validateId($id);
192 22
        $id = $this->normalize($id);
193
194
        // Clean up previous bindings, if any.
195 22
        unset($this->instances[$id], $this->shared[$id], $this->keys[$id]);
196
197
        // Register service with provided callback.
198 22
        $registrationCallback($id);
199
200
        // Mark service as shared when needed.
201 22
        $this->shared[$id] = $shared ?: null;
202
203
        // Save service key to make it recognizable by container.
204 22
        $this->keys[$id] = true;
205 22
    }
206
207
    /**
208
     * Validate service identifier. Throw an Exception in case of invalid value.
209
     *
210
     * @param string $id
211
     * @return void
212
     * @throws InvalidArgumentException
213
     */
214 23
    private function validateId(string $id)
215
    {
216 23
        if (!interface_exists($id) && !class_exists($id)) {
217 1
            throw new InvalidArgumentException(
218 1
                sprintf('Invalid service id "%s". Service id must be an existing interface or class name.', $id)
219
            );
220
        }
221 22
    }
222
223
}