Container   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 36
c 2
b 0
f 0
dl 0
loc 140
rs 10
wmc 17

8 Methods

Rating   Name   Duplication   Size   Complexity  
A buildInstance() 0 16 4
A resolveInstance() 0 6 2
A resolveAutomaticResolvable() 0 6 2
A has() 0 8 2
A autoResolve() 0 12 3
A get() 0 6 2
A setInstance() 0 3 1
A set() 0 3 1
1
<?php
2
3
/**
4
 * This file is part of PhpUnitGen.
5
 *
6
 * (c) 2017-2018 Paul Thébaud <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.md
9
 * file that was distributed with this source code.
10
 */
11
12
namespace PhpUnitGen\Container;
13
14
use PhpUnitGen\Exception\ContainerException;
15
use Psr\Container\ContainerInterface;
16
use Respect\Validation\Validator;
17
18
/**
19
 * Class Container.
20
 *
21
 * @author     Paul Thébaud <[email protected]>.
22
 * @copyright  2017-2018 Paul Thébaud <[email protected]>.
23
 * @license    https://opensource.org/licenses/MIT The MIT license.
24
 * @link       https://github.com/paul-thebaud/phpunit-generator
25
 * @since      Class available since Release 2.0.0.
26
 */
27
class Container implements ContainerInterface
28
{
29
    /**
30
     * @var string[] $autoResolvable All objects resolvable automatically.
31
     */
32
    private $autoResolvable = [];
33
34
    /**
35
     * @var mixed[] $instances All objects instances.
36
     */
37
    private $instances = [];
38
39
    /**
40
     * Add to available services an instance of an object.
41
     *
42
     * @param string $id       The service identifier.
43
     * @param mixed  $instance An object instance.
44
     */
45
    public function setInstance(string $id, $instance): void
46
    {
47
        $this->instances[$id] = $instance;
48
    }
49
50
    /**
51
     * Add to available services a class which can be construct by the container resolve method.
52
     *
53
     * @param string      $id    The service identifier.
54
     * @param string|null $class The class name, null if it can $id as a class name.
55
     */
56
    public function set(string $id, string $class = null): void
57
    {
58
        $this->autoResolvable[$id] = $class ?? $id;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function has($id): bool
65
    {
66
        try {
67
            $this->get($id);
68
        } catch (ContainerException $exception) {
69
            return false;
70
        }
71
        return true;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function get($id)
78
    {
79
        if (! Validator::stringType()->validate($id)) {
80
            throw new ContainerException('Identifier is not a string');
81
        }
82
        return $this->resolveInstance($id);
83
    }
84
85
    /**
86
     * Try to retrieve a service instance from the instances array.
87
     *
88
     * @param string $id The service identifier.
89
     *
90
     * @return mixed The service.
91
     *
92
     * @throws ContainerException If the service identifier is not a string.
93
     */
94
    private function resolveInstance(string $id)
95
    {
96
        if (Validator::key($id)->validate($this->instances)) {
97
            return $this->instances[$id];
98
        }
99
        return $this->resolveAutomaticResolvable($id);
100
    }
101
102
    /**
103
     * Try to retrieve a service instance from the automatic resolvable array.
104
     *
105
     * @param string $id The service identifier.
106
     *
107
     * @return mixed The service.
108
     *
109
     * @throws ContainerException If the service identifier is not a string.
110
     */
111
    private function resolveAutomaticResolvable(string $id)
112
    {
113
        if (Validator::key($id)->validate($this->autoResolvable)) {
114
            return $this->instances[$id] = $this->autoResolve($this->autoResolvable[$id]);
115
        }
116
        return $this->autoResolve($id);
117
    }
118
119
    /**
120
     * Try to automatically create a service.
121
     *
122
     * @param string $class The service class.
123
     *
124
     * @return mixed The built instance.
125
     *
126
     * @throws ContainerException If the service cannot be constructed.
127
     */
128
    private function autoResolve(string $class)
129
    {
130
        try {
131
            $reflection = new \ReflectionClass($class);
132
        } catch (\ReflectionException $exception) {
133
            throw new ContainerException(sprintf('Class "%s" does not exists', $class));
134
        }
135
136
        if (! $reflection->isInstantiable()) {
137
            throw new ContainerException(sprintf('Class "%s" is not instantiable', $class));
138
        }
139
        return $this->buildInstance($reflection);
140
    }
141
142
    /**
143
     * Build a new instance of a class from reflection class and auto-resolved constructor arguments.
144
     *
145
     * @param \ReflectionClass $reflection The reflection class.
146
     *
147
     * @return mixed The built instance.
148
     *
149
     * @throws ContainerException If the class constructor is not public.
150
     */
151
    private function buildInstance(\ReflectionClass $reflection)
152
    {
153
        if (($constructor = $reflection->getConstructor()) === null) {
154
            return $reflection->newInstance();
155
        }
156
        $constructorParameters = [];
157
        foreach ($constructor->getParameters() as $parameter) {
158
            if (($parameterClass = $parameter->getClass()) === null) {
159
                throw new ContainerException(sprintf(
160
                    'Class "%s" constructor has a scalar / callable / array type parameter',
161
                    $reflection->getName()
162
                ));
163
            }
164
            $constructorParameters[] = $this->get($parameterClass->getName());
165
        }
166
        return $reflection->newInstanceArgs($constructorParameters);
167
    }
168
}
169