Completed
Push — develop ( 67e161...8b0772 )
by Paul
05:50
created

Container::autoResolve()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace PhpUnitGen\Container;
4
5
use PhpUnitGen\Exception\ContainerException;
6
use PhpUnitGen\Exception\NotFoundException;
7
use Respect\Validation\Validator;
8
9
/**
10
 * Class Container.
11
 *
12
 * @author     Paul Thébaud <[email protected]>.
13
 * @copyright  2017-2018 Paul Thébaud <[email protected]>.
14
 * @license    https://opensource.org/licenses/MIT The MIT license.
15
 * @link       https://github.com/paul-thebaud/phpunit-generator
16
 * @since      Class available since Release 2.0.0.
17
 */
18
class Container implements ContainerInterface
19
{
20
    /**
21
     * @var callable[] $customResolvable All objects resolvable from a callable.
22
     */
23
    private $customResolvable = [];
24
25
    /**
26
     * @var string[] $autoResolvable All objects resolvable automatically.
27
     */
28
    private $autoResolvable = [];
29
30
    /**
31
     * @var object[] $instances All objects instances.
32
     */
33
    private $instances = [];
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function setResolver(string $id, callable $resolver): void
39
    {
40
        $this->customResolvable[$id] = $resolver;
41
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function setInstance(string $id, object $instance): void
47
    {
48
        $this->instances[$id] = $instance;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function set(string $id, string $class = null): void
55
    {
56
        $this->autoResolvable[$id] = $class ?? $id;
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function get($id): object
63
    {
64
        return $this->resolve($id);
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function has($id): bool
71
    {
72
        try {
73
            $this->resolve($id);
74
        } catch (NotFoundException $exception) {
75
            return false;
76
        } catch (ContainerException $exception) {
77
            return false;
78
        }
79
        return true;
80
    }
81
82
    /**
83
     * Try to retrieve a service instance.
84
     *
85
     * @param string $id The service identifier.
86
     *
87
     * @return object The service.
88
     *
89
     * @throws ContainerException If the service identifier is not a string.
90
     * @throws NotFoundException If the service does not exists.
91
     */
92
    private function resolve($id): object
93
    {
94
        if (! is_string($id)) {
95
            throw new ContainerException("Identifier is not a string.");
96
        }
97
        if (Validator::key($id)->validate($this->instances)) {
98
            return $this->instances[$id];
99
        }
100
        if (Validator::key($id)->validate($this->customResolvable)) {
101
            return $this->instances[$id] = $this->customResolvable[$id]($this);
102
        }
103
        if (Validator::key($id)->validate($this->autoResolvable)) {
104
            return $this->instances[$id] = $this->autoResolve($this->autoResolvable[$id]);
105
        }
106
        throw new NotFoundException(sprintf('Service of identifier "%s" not found.', $id));
107
    }
108
109
    /**
110
     * Try to automatically create a service.
111
     *
112
     * @param string $class The service class.
113
     *
114
     * @return object
115
     *
116
     * @throws ContainerException If the service cannot be constructed.
117
     */
118
    private function autoResolve(string $class): object
119
    {
120
        if (! class_exists($class)) {
121
            throw new ContainerException(sprintf("Class %s does not exists.", $class));
122
        }
123
124
        $reflection = new \ReflectionClass($class);
125
126
        if (! $reflection->isInstantiable()) {
127
            throw new ContainerException(sprintf("Class %s is not instantiable.", $class));
128
        }
129
        if (($constructor = $reflection->getConstructor()) === null) {
130
            return $reflection->newInstance();
131
        }
132
        $constructorParameters = [];
133
        foreach ($constructor->getParameters() as $parameter) {
134
            $constructorParameters[] = $this->resolve($parameter->getClass()->getName());
135
        }
136
        return $reflection->newInstanceArgs($constructorParameters);
137
    }
138
}
139