Completed
Push — develop ( b69568...59e403 )
by Paul
03:27
created

Container::addAutoResolvableArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpUnitGen\Container;
4
5
use PhpUnitGen\Container\ContainerInterface\ContainerInterface;
6
use PhpUnitGen\Exception\ContainerException;
7
use PhpUnitGen\Exception\NotFoundException;
8
use Respect\Validation\Validator;
9
10
/**
11
 * Class Container.
12
 *
13
 * @author     Paul Thébaud <[email protected]>.
14
 * @copyright  2017-2018 Paul Thébaud <[email protected]>.
15
 * @license    https://opensource.org/licenses/MIT The MIT license.
16
 * @link       https://github.com/paul-thebaud/phpunit-generator
17
 * @since      Class available since Release 2.0.0.
18
 */
19
class Container implements ContainerInterface
20
{
21
    /**
22
     * @var callable[] $customResolvable All objects resolvable from a callable.
23
     */
24
    private static $customResolvable = [];
25
26
    /**
27
     * @var string[] $autoResolvable All objects resolvable automatically.
28
     */
29
    private static $autoResolvable = [];
30
31
    /**
32
     * @var object[] $instances All objects instances.
33
     */
34
    private static $instances = [];
35
36
    /**
37
     * {@inheritdoc}
38
     */
39
    public function setResolver(string $id, callable $resolver): void
40
    {
41
        self::$customResolvable[$id] = $resolver;
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function setInstance(string $id, object $instance): void
48
    {
49
        self::$instances[$id] = $instance;
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function set(string $id, string $class = null): void
56
    {
57
        self::$autoResolvable[$id] = $class ?? $id;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function addAutoResolvableArray(array $autoResolvable): void
64
    {
65
        array_merge(self::$autoResolvable, $autoResolvable);
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function get($id): object
72
    {
73
        return $this->resolve($id);
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function has($id): bool
80
    {
81
        try {
82
            $this->resolve($id);
83
        } catch (NotFoundException $exception) {
84
            return false;
85
        } catch (ContainerException $exception) {
86
            return false;
87
        }
88
        return true;
89
    }
90
91
    /**
92
     * Try to retrieve a service instance.
93
     *
94
     * @param string $id The service identifier.
95
     *
96
     * @return object The service.
97
     *
98
     * @throws ContainerException If the service identifier is not a string.
99
     * @throws NotFoundException If the service does not exists.
100
     */
101
    private function resolve($id): object
102
    {
103
        if (! Validator::stringType()->validate($id)) {
104
            throw new ContainerException('Identifier is not a string.');
105
        }
106
        return $this->resolveInstance($id);
107
    }
108
109
    /**
110
     * Try to retrieve a service instance from the instances array.
111
     *
112
     * @param string $id The service identifier.
113
     *
114
     * @return object The service.
115
     *
116
     * @throws ContainerException If the service identifier is not a string.
117
     */
118
    private function resolveInstance(string $id): object
119
    {
120
        if (Validator::key($id)->validate(self::$instances)) {
121
            return self::$instances[$id];
122
        }
123
        return $this->resolveCustomResolvable($id);
124
    }
125
126
    /**
127
     * Try to retrieve a service instance from the custom resolvable array.
128
     *
129
     * @param string $id The service identifier.
130
     *
131
     * @return object The service.
132
     *
133
     * @throws ContainerException If the service identifier is not a string.
134
     */
135
    private function resolveCustomResolvable(string $id): object
136
    {
137
        if (Validator::key($id)->validate(self::$customResolvable)) {
138
            return self::$instances[$id] = (self::$customResolvable[$id])($this);
139
        }
140
        return $this->resolveAutomaticResolvable($id);
141
    }
142
143
    /**
144
     * Try to retrieve a service instance from the automatic resolvable array.
145
     *
146
     * @param string $id The service identifier.
147
     *
148
     * @return object The service.
149
     *
150
     * @throws ContainerException If the service identifier is not a string.
151
     */
152
    private function resolveAutomaticResolvable(string $id): object
153
    {
154
        if (Validator::key($id)->validate(self::$autoResolvable)) {
155
            return self::$instances[$id] = $this->autoResolve(self::$autoResolvable[$id]);
156
        }
157
        return $this->autoResolve($id);
158
    }
159
160
    /**
161
     * Try to automatically create a service.
162
     *
163
     * @param string $class The service class.
164
     *
165
     * @return object
166
     *
167
     * @throws ContainerException If the service cannot be constructed.
168
     */
169
    private function autoResolve(string $class): object
170
    {
171
        if (! class_exists($class)) {
172
            throw new ContainerException(sprintf('Class "%s" does not exists.', $class));
173
        }
174
175
        $reflection = new \ReflectionClass($class);
176
177
        if (! $reflection->isInstantiable()) {
178
            throw new ContainerException(sprintf('Class "%s" is not instantiable.', $class));
179
        }
180
        return $this->buildInstance($reflection);
181
    }
182
183
    /**
184
     * Build a new instance of a class from reflection class and auto-resolved constructor arguments.
185
     *
186
     * @param \ReflectionClass $reflection The reflection class.
187
     *
188
     * @return object The built instance.
189
     *
190
     * @throws ContainerException If the class constructor is not public.
191
     */
192
    private function buildInstance(\ReflectionClass $reflection): object
193
    {
194
        if (($constructor = $reflection->getConstructor()) === null) {
195
            return $reflection->newInstance();
196
        }
197
        if (! $constructor->isPublic()) {
198
            throw new ContainerException(
199
                sprintf('Class "%s" has no public constructor.', $reflection->getName())
200
            );
201
        }
202
        $constructorParameters = [];
203
        foreach ($constructor->getParameters() as $parameter) {
204
            $constructorParameters[] = $this->resolve($parameter->getClass()->getName());
205
        }
206
        return $reflection->newInstanceArgs($constructorParameters);
207
    }
208
}
209