AnnotationReader::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace WebStream\Annotation\Reader;
4
5
use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
6
use WebStream\Annotation\Base\IAnnotatable;
7
use WebStream\Annotation\Base\IClass;
8
use WebStream\Annotation\Base\IExtension;
9
use WebStream\Annotation\Base\IMethod;
10
use WebStream\Annotation\Base\IMethods;
11
use WebStream\Annotation\Base\IProperty;
12
use WebStream\Annotation\Base\IRead;
13
use WebStream\Annotation\Reader\Extend\ExtendReader;
14
use WebStream\Container\Container;
0 ignored issues
show
Bug introduced by
The type WebStream\Container\Container was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use WebStream\DI\Injector;
0 ignored issues
show
Bug introduced by
The type WebStream\DI\Injector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use WebStream\Exception\Delegate\ExceptionDelegator;
0 ignored issues
show
Bug introduced by
The type WebStream\Exception\Delegate\ExceptionDelegator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use WebStream\Exception\Extend\AnnotationException;
0 ignored issues
show
Bug introduced by
The type WebStream\Exception\Extend\AnnotationException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
19
/**
20
 * AnnotationReader
21
 * @author Ryuichi TANAKA.
22
 * @since 2014/05/10
23
 * @version 0.4
24
 */
25
class AnnotationReader
26
{
27
    use Injector;
28
29
    /**
30
     * @var IAnnotatable インスタンス
31
     */
32
    private IAnnotatable $instance;
33
34
    /**
35
     * @var array<string> 読み込み可能アノテーション情報
36
     */
37
    private array $readableMap;
38
39
    /**
40
     * @var array<ExtendReader> 拡張アノテーションリーダー
41
     */
42
    private array $extendReaderMap;
43
44
    /**
45
     * @var array<string> アノテーション情報リスト
46
     */
47
    private array $annotationInfoList;
48
49
    /**
50
     * @var array<string> アノテーション情報リスト(拡張リーダー処理済み)
51
     */
52
    private array $annotationInfoExtendList;
0 ignored issues
show
introduced by
The private property $annotationInfoExtendList is not used, and could be removed.
Loading history...
53
54
    /**
55
     * @var ExceptionDelegator 読み込み時の例外
56
     */
57
    private ExceptionDelegator $exception;
58
59
    /**
60
     * @var string アクションメソッド
61
     */
62
    private string $actionMethod;
63
64
    /**
65
     * @var Container デフォルト依存コンテナ
66
     */
67
    private Container $defaultContainer;
68
69
    /**
70
     * constructor
71
     * @param IAnnotatable $instance
72
     */
73 148
    public function __construct(IAnnotatable $instance)
74
    {
75 148
        $this->initialize();
76 148
        $this->instance = $instance;
77
    }
78
79
    /**
80
     * 初期化処理
81
     */
82 148
    private function initialize()
83
    {
84 148
        $this->readableMap = [];
85 148
        $this->extendReaderMap = [];
86 148
        $this->annotationInfoList = [];
87 148
        $this->defaultContainer = new Container(false);
88
    }
89
90
    /**
91
     * アノテーション情報リストを返却する
92
     * @return array<mixed> アノテーション情報リスト
93
     * @throws \ReflectionException
94
     */
95 47
    public function getAnnotationInfoList(): array
96
    {
97 47
        if (empty($this->extendReaderMap)) {
98 23
            return $this->annotationInfoList;
99
        }
100
101 24
        foreach ($this->annotationInfoList as $key => $annotationInfo) {
102 24
            if (!array_key_exists($key, $this->extendReaderMap)) {
103
                continue;
104
            }
105 24
            $readerClasspath = $this->extendReaderMap[$key];
106 24
            $refClass = new \ReflectionClass($readerClasspath);
107 24
            $reader = $refClass->newInstance();
108 24
            $reader->read($annotationInfo);
109 24
            $this->annotationInfoList[$key] = $reader->getAnnotationInfo();
110
        }
111
112 24
        return $this->annotationInfoList;
113
    }
114
115
    /**
116
     * 発生した例外を返却する
117
     * @return ExceptionDelegator 発生した例外
118
     */
119 100
    public function getException(): ?ExceptionDelegator
120
    {
121 100
        return $this->exception ?? null;
122
    }
123
124
    /**
125
     * アクションメソッドを設定する
126
     * @param string アクションメソッド
0 ignored issues
show
Documentation Bug introduced by
The doc comment アクションメソッド at position 0 could not be parsed: Unknown type name 'アクションメソッド' at position 0 in アクションメソッド.
Loading history...
127
     */
128 147
    public function setActionMethod(string $actionMethod)
129
    {
130 147
        $this->actionMethod = $actionMethod;
131
    }
132
133
    /**
134
     * 読み込み可能アノテーション情報を設定する
135
     * @param string アノテーションクラスパス
0 ignored issues
show
Documentation Bug introduced by
The doc comment アノテーションクラスパス at position 0 could not be parsed: Unknown type name 'アノテーションクラスパス' at position 0 in アノテーションクラスパス.
Loading history...
136
     * @param Container|null $container
137
     */
138 142
    public function readable(string $classpath, Container $container = null)
139
    {
140 142
        $this->readableMap[$classpath] = $container;
141
    }
142
143
     /**
144
      * 拡張アノテーションリーダーを設定する
145
      * @param string アノテーションクラスパス
0 ignored issues
show
Documentation Bug introduced by
The doc comment アノテーションクラスパス at position 0 could not be parsed: Unknown type name 'アノテーションクラスパス' at position 0 in アノテーションクラスパス.
Loading history...
146
      * @param string 拡張アノテーションリーダークラスパス
147
      */
148 26
    public function useExtendReader(string $annotationClasspath, string $readerClasspath)
149
    {
150 26
        $this->extendReaderMap[$annotationClasspath] = $readerClasspath;
151
    }
152
153
    /**
154
     * アノテーション情報を読み込む
155
     */
156
    public function read()
157
    {
158
        try {
159
            $this->readClass();
160
            $this->readMethod();
161
            $this->readProperty();
162
        } catch (\Exception $e) {
163
            $this->initialize();
164
            throw new AnnotationException($e);
165
        }
166
    }
167
168
    /**
169
     * クラス情報を読み込む
170
     * @throws \ReflectionException
171
     */
172 2
    public function readClass()
173
    {
174 2
        $reader = new DoctrineAnnotationReader();
175 2
        $refClass = new \ReflectionClass($this->instance);
176
177 2
        while ($refClass !== false) {
178 2
            $annotations = $reader->getClassAnnotations($refClass);
179
180 2
            if (!empty($annotations)) {
181 2
                for ($i = 0, $count = count($annotations); $i < $count; $i++) {
182 2
                    $annotation = $annotations[$i];
183
184 2
                    if (!$annotation instanceof IClass) {
185
                        continue;
186
                    }
187
188 2
                    $key = get_class($annotation);
189 2
                    $container = null;
190 2
                    if (!array_key_exists($key, $this->readableMap)) {
191
                        if ($annotation instanceof IExtension) {
192
                            $container = $this->defaultContainer;
193
                        } else {
194
                            continue;
195
                        }
196
                    } else {
197 2
                        $container = $this->readableMap[$key];
198
                    }
199
200
                    try {
201 2
                        $annotation->onClassInject($this->instance, $refClass, $container);
202 1
                    } catch (\Exception $e) {
203 1
                        if (!isset($this->exception)) {
204 1
                            $this->exception = new ExceptionDelegator($this->instance, $e);
205
                        }
206 1
                        continue;
207
                    }
208
209
                    // IReadを実装している場合、任意のデータを返却する
210 1
                    if ($annotation instanceof IRead) {
211 1
                        if (!array_key_exists($key, $this->annotationInfoList)) {
212 1
                            $this->annotationInfoList[$key] = [];
213
                        }
214 1
                        $this->annotationInfoList[$key][] = $annotation->getAnnotationInfo();
215
                    }
216
                }
217
            }
218
219 2
            $refClass = $refClass->getParentClass();
220
        }
221
    }
222
223
    /**
224
     * メソッド情報を読み込む
225
     */
226 146
    public function readMethod()
227
    {
228 146
        $reader = new DoctrineAnnotationReader();
229 146
        $refClass = new \ReflectionClass($this->instance);
230
231 146
        while ($refClass !== false) {
232 146
            foreach ($refClass->getMethods() as $refMethod) {
233 146
                if ($refClass->getName() !== $refMethod->class) {
234
                    continue;
235
                }
236
237 146
                $annotations = $reader->getMethodAnnotations($refMethod);
238 146
                if (empty($annotations)) {
239 25
                    continue;
240
                }
241
242 146
                for ($i = 0, $count = count($annotations); $i < $count; $i++) {
243 146
                    $annotation = $annotations[$i];
244
245 146
                    if (!$annotation instanceof IMethod && !$annotation instanceof IMethods) {
246
                        continue;
247
                    }
248
249
                    // IMethodを実装している場合、アクションメソッドのアノテーション以外は読み込まない
250
                    // PHPのメソッドは大文字小文字を区別しないため、そのまま比較するとルーティング解決結果と実際のメソッド名が合わないケースがある
251
                    // PHPの仕様に合わせてメソッド名の文字列比較は小文字に変換してから行う
252 146
                    if ($annotation instanceof IMethod && strtolower($this->actionMethod) !== strtolower($refMethod->name)) {
253 105
                        continue;
254
                    }
255
256
                    // 読み込み可能なアノテーション以外は読み込まない
257 146
                    $key = get_class($annotation);
258 146
                    $container = null;
259 146
                    if (!array_key_exists($key, $this->readableMap)) {
260 6
                        if ($annotation instanceof IExtension) {
261 1
                            $container = $this->defaultContainer;
262
                        } else {
263 5
                            continue;
264
                        }
265
                    } else {
266 140
                        $container = $this->readableMap[$key];
267
                    }
268
269
                    try {
270 141
                        $annotation->onMethodInject($this->instance, $refMethod, $container);
271 56
                    } catch (\Exception $e) {
272 56
                        if (!isset($this->exception)) {
273 56
                            $this->exception = new ExceptionDelegator($this->instance, $e, $this->actionMethod);
274
                        }
275 56
                        continue;
276
                    }
277
278
                    // IReadを実装している場合、任意のデータを返却する
279 85
                    if ($annotation instanceof IRead) {
280 41
                        if (!array_key_exists($key, $this->annotationInfoList)) {
281 41
                            $this->annotationInfoList[$key] = [];
282
                        }
283 41
                        $this->annotationInfoList[$key][] = $annotation->getAnnotationInfo();
284
                    }
285
                }
286
            }
287
288 146
            $refClass = $refClass->getParentClass();
289
        }
290
    }
291
292
    /**
293
     * プロパティ情報を読み込む
294
     */
295
    private function readProperty()
296
    {
297
        $reader = new DoctrineAnnotationReader();
298
        $refClass = $this->refClass;
299
300
        while ($refClass !== false) {
301
            foreach ($refClass->getProperties() as $refProperty) {
302
                if ($refClass->getName() !== $refProperty->class) {
303
                    continue;
304
                }
305
306
                $annotations = $reader->getPropertyAnnotations($refProperty);
307
308
                // アノテーション定義がなければ次へ
309
                if (empty($annotations)) {
310
                    continue;
311
                }
312
313
                for ($i = 0, $count = count($annotations); $i < $count; $i++) {
314
                    $annotation = $annotations[$i];
315
316
                    if (!$annotation instanceof IProperty) {
317
                        continue;
318
                    }
319
320
                    $key = get_class($annotation);
321
                    $container = null;
322
                    if (!array_key_exists($key, $this->readableMap)) {
323
                        if ($annotation instanceof IExtension) {
324
                            $container = $this->defaultContainer;
325
                        } else {
326
                            continue;
327
                        }
328
                    } else {
329
                        $container = $this->readableMap[$key];
330
                    }
331
332
                    try {
333
                        $annotation->onPropertyInject($this->instance, $refProperty, $container);
334
                    } catch (\Exception $e) {
335
                        if (!isset($this->exception)) {
336
                            $this->exception = new ExceptionDelegator($this->instance, $e);
337
                        }
338
                        continue;
339
                    }
340
341
                    // IReadを実装している場合、任意のデータを返却する
342
                    if ($annotation instanceof IRead) {
343
                        if (!array_key_exists($key, $this->annotationInfoList)) {
344
                            $this->annotationInfoList[$key] = [];
345
                        }
346
                        $this->annotationInfoList[$key][] = $annotation->onInjected();
0 ignored issues
show
Bug introduced by
The method onInjected() does not exist on WebStream\Annotation\Base\IExtension. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

346
                        /** @scrutinizer ignore-call */ 
347
                        $this->annotationInfoList[$key][] = $annotation->onInjected();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method onInjected() does not exist on WebStream\Annotation\Base\IRead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

346
                        /** @scrutinizer ignore-call */ 
347
                        $this->annotationInfoList[$key][] = $annotation->onInjected();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method onInjected() does not exist on WebStream\Annotation\Base\IProperty. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

346
                        /** @scrutinizer ignore-call */ 
347
                        $this->annotationInfoList[$key][] = $annotation->onInjected();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
                    }
348
                }
349
            }
350
351
            $refClass = $refClass->getParentClass();
352
        }
353
    }
354
}
355