Completed
Push — master ( c02f0d...91d1a4 )
by Ryuichi
02:00
created

AnnotationReader::getAnnotationInfoList()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

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