Passed
Push — master ( 44e82b...0d3e11 )
by Akmal
59s
created

Di::getService()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace OpenEngine\Mika\Core\Components\Di;
4
5
use OpenEngine\Mika\Core\Components\Di\Exceptions\ClassNotFoundException;
6
use OpenEngine\Mika\Core\Components\Di\Exceptions\MethodNotFoundException;
7
use OpenEngine\Mika\Core\Components\Di\Exceptions\MissingMethodArgumentException;
8
use OpenEngine\Mika\Core\Components\Di\Exceptions\ServiceNotFoundException;
9
use OpenEngine\Mika\Core\Components\Di\Interfaces\DiConfigInterface;
10
use Psr\Container\ContainerInterface;
11
use ReflectionClass;
12
use ReflectionNamedType;
13
14
class Di implements ContainerInterface
15
{
16
    /**
17
     * Created services
18
     *
19
     * @var object[]
20
     */
21
    private $singletons;
22
23
    /**
24
     * Registered service names
25
     *
26
     * @var string[]
27
     */
28
    private $services;
29
30
    /**
31
     * Di constructor.
32
     * @param DiConfigInterface $diConfig
33
     */
34
    public function __construct(DiConfigInterface $diConfig)
35
    {
36
        $diConfig->registerObject(ContainerInterface::class, $this);
37
        $this->services = $diConfig->getServices();
38
        $this->singletons = $diConfig->getServiceObjects();
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     * @throws MethodNotFoundException
44
     * @throws MissingMethodArgumentException
45
     */
46
    public function get($id): object
47
    {
48
        if (!$this->has($id)) {
49
            throw new ServiceNotFoundException('Service ' . $id . ' is not found');
50
        }
51
52
        $singleton = $this->getSingleton($id);
53
54
        if ($singleton !== null) {
55
            return $singleton;
56
        }
57
58
        return $this->createObject($this->getService($id));
59
    }
60
61
    /**
62
     * @inheritdoc
63
     */
64
    public function has($id): bool
65
    {
66
        return $this->getService($id) !== null;
67
    }
68
69
    /**
70
     * @param string $className
71
     * @return object
72
     * @throws ClassNotFoundException
73
     * @throws MethodNotFoundException
74
     * @throws MissingMethodArgumentException
75
     */
76
    public function createObject(string $className): object
77
    {
78
        $params = $this->createMethodDepends($className, '__construct');
79
80
        try {
81
            $reflector = new ReflectionClass($className);
82
        } catch (\ReflectionException $e) {
83
            throw new ClassNotFoundException('Class ' . $className . ' is not found');
84
        }
85
86
        return $reflector->newInstanceArgs($params);
87
    }
88
89
    /**
90
     * Create method depends
91
     *
92
     * Method returns list of params with initialized depends.
93
     * All services for method will be initialized automatically.
94
     * Other params you must specify on parameter $knownParams.
95
     *
96
     * For example:
97
     * ```
98
     * class Foo {
99
     *      public method bar(BazInterface $baz, Baz2Interface $baz2, string $baz3, int $param4): void
100
     *      {
101
     *          // ... code ...
102
     *      }
103
     * }
104
     *
105
     * // $baz and $baz2 will initialized by automatically.
106
     * // $baz3 and $param4 you must specify
107
     * App::getContainer()->createMethodDepends(Foo::class, "bar", ['baz3' => 'Test', 'param4' => 13])
108
     *
109
     * ```
110
     *
111
     * @param string $className
112
     * @param string $methodName
113
     * @param array $knownParams You can specify some parameter values if you already know what method needs
114
     * @return array
115
     * @throws MethodNotFoundException
116
     * @throws MissingMethodArgumentException
117
     */
118
    public function createMethodDepends(string $className, string $methodName, array $knownParams = []): array
119
    {
120
        $result = [];
121
122
        /**
123
         * @var ReflectionNamedType $type
124
         */
125
        foreach ($this->getMethodParams($className, $methodName) as $name => $type) {
126
            if ($type !== null && !$this->isScalar($type->getName())) {
127
                $result[$name] = $this->get($type->getName());
128
                continue;
129
            }
130
131
            if (!isset($knownParams[$name])) {
132
                throw new MissingMethodArgumentException('Missing argument ' . $name . ' for method ' . $methodName);
133
            }
134
135
            if ($type === null) {
136
                $result[$name] = $knownParams[$name];
137
                continue;
138
            }
139
140
            $this->addCastedVar($result, $name, $type->getName(), $knownParams[$name]);
141
        }
142
143
        return $result;
144
    }
145
146
    /**
147
     * @param array $methodDepends
148
     * @param string $paramName
149
     * @param string $type
150
     * @param mixed $var
151
     */
152
    private function addCastedVar(array &$methodDepends, string $paramName, string $type, $var): void
153
    {
154
        switch ($type) {
155
            case 'int':
156
                $methodDepends[$paramName] = (int)$var;
157
                break;
158
159
            case 'float':
160
                $methodDepends[$paramName] = (float)$var;
161
                break;
162
163
            case 'bool':
164
                $methodDepends[$paramName] = (bool)$var;
165
                break;
166
167
            case 'string':
168
            default:
169
                $methodDepends[$paramName] = (string)$var;
170
                break;
171
        }
172
    }
173
174
    /**
175
     * @param string $id
176
     * @return string|null
177
     */
178
    private function getService(string $id): ?string
179
    {
180
        if (isset($this->services[$id])) {
181
            return $this->services[$id];
182
        }
183
184
        return null;
185
    }
186
187
    /**
188
     * @param string $id
189
     * @return \object|null
190
     */
191
    private function getSingleton(string $id): ?object
192
    {
193
        if (isset($this->singletons[$id])) {
194
            return $this->singletons[$id];
195
        }
196
197
        return null;
198
    }
199
200
    /**
201
     * @param string $className
202
     * @param string $methodName
203
     * @return array
204
     * @throws MethodNotFoundException
205
     */
206
    private function getMethodParams(string $className, string $methodName): array
207
    {
208
        $result = [];
209
210
        try {
211
            $method = new \ReflectionMethod($className, $methodName);
212
        } catch (\ReflectionException $e) {
213
            if ($methodName === '__construct') {
214
                return $result;
215
            }
216
217
            throw new MethodNotFoundException($e->getMessage());
218
        }
219
220
        foreach ($method->getParameters() as $parameter) {
221
            $result [$parameter->name] = $parameter->getType();
222
        }
223
224
        return $result;
225
    }
226
227
    /**
228
     * @param string $typeName
229
     * @return bool
230
     */
231
    private function isScalar(string $typeName): bool
232
    {
233
        return \in_array($typeName, ['int', 'string', 'float', 'bool']);
234
    }
235
}
236