Completed
Push — develop ( cec1b5...82146f )
by Paul
02:04
created

Container::setResolver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
eloc 1
nc 1
nop 2
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 get($id): object
64
    {
65
        return $this->resolve($id);
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function has($id): bool
72
    {
73
        try {
74
            $this->resolve($id);
75
        } catch (NotFoundException $exception) {
76
            return false;
77
        } catch (ContainerException $exception) {
78
            return false;
79
        }
80
        return true;
81
    }
82
83
    /**
84
     * Try to retrieve a service instance.
85
     *
86
     * @param string $id The service identifier.
87
     *
88
     * @return object The service.
89
     *
90
     * @throws ContainerException If the service identifier is not a string.
91
     * @throws NotFoundException If the service does not exists.
92
     */
93
    private function resolve($id): object
94
    {
95
        if (! Validator::stringType()->validate($id)) {
96
            throw new ContainerException('Identifier is not a string.');
97
        }
98
        return $this->resolveInstance($id);
99
    }
100
101
    /**
102
     * Try to retrieve a service instance from the instances array.
103
     *
104
     * @param string $id The service identifier.
105
     *
106
     * @return object The service.
107
     *
108
     * @throws ContainerException If the service identifier is not a string.
109
     */
110
    private function resolveInstance(string $id): object
111
    {
112
        if (Validator::key($id)->validate(self::$instances)) {
113
            return self::$instances[$id];
114
        }
115
        return $this->resolveCustomResolvable($id);
116
    }
117
118
    /**
119
     * Try to retrieve a service instance from the custom resolvable array.
120
     *
121
     * @param string $id The service identifier.
122
     *
123
     * @return object The service.
124
     *
125
     * @throws ContainerException If the service identifier is not a string.
126
     */
127
    private function resolveCustomResolvable(string $id): object
128
    {
129
        if (Validator::key($id)->validate(self::$customResolvable)) {
130
            return self::$instances[$id] = (self::$customResolvable[$id])($this);
131
        }
132
        return $this->resolveAutomaticResolvable($id);
133
    }
134
135
    /**
136
     * Try to retrieve a service instance from the automatic resolvable array.
137
     *
138
     * @param string $id The service identifier.
139
     *
140
     * @return object The service.
141
     *
142
     * @throws ContainerException If the service identifier is not a string.
143
     */
144
    private function resolveAutomaticResolvable(string $id): object
145
    {
146
        if (Validator::key($id)->validate(self::$autoResolvable)) {
147
            return self::$instances[$id] = $this->autoResolve(self::$autoResolvable[$id]);
148
        }
149
        return $this->autoResolve($id);
150
    }
151
152
    /**
153
     * Try to automatically create a service.
154
     *
155
     * @param string $class The service class.
156
     *
157
     * @return object
158
     *
159
     * @throws ContainerException If the service cannot be constructed.
160
     */
161
    private function autoResolve(string $class): object
162
    {
163
        if (! class_exists($class)) {
164
            throw new ContainerException(sprintf('Class "%s" does not exists.', $class));
165
        }
166
167
        $reflection = new \ReflectionClass($class);
168
169
        if (! $reflection->isInstantiable()) {
170
            throw new ContainerException(sprintf('Class "%s" is not instantiable.', $class));
171
        }
172
        return $this->buildInstance($reflection);
173
    }
174
175
    /**
176
     * Build a new instance of a class from reflection class and auto-resolved constructor arguments.
177
     *
178
     * @param \ReflectionClass $reflection The reflection class.
179
     *
180
     * @return object The built instance.
181
     *
182
     * @throws ContainerException If the class constructor is not public.
183
     */
184
    private function buildInstance(\ReflectionClass $reflection): object
185
    {
186
        if (($constructor = $reflection->getConstructor()) === null) {
187
            return $reflection->newInstance();
188
        }
189
        if (! $constructor->isPublic()) {
190
            throw new ContainerException(
191
                sprintf('Class "%s" has no public constructor.', $reflection->getName())
192
            );
193
        }
194
        $constructorParameters = [];
195
        foreach ($constructor->getParameters() as $parameter) {
196
            $constructorParameters[] = $this->resolve($parameter->getClass()->getName());
197
        }
198
        return $reflection->newInstanceArgs($constructorParameters);
199
    }
200
}
201