AnnotationReader   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 71
c 2
b 0
f 0
dl 0
loc 210
rs 10
wmc 22

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getMembersAttrs() 0 22 4
A getClassAttrs() 0 3 1
A getPropTypes() 0 3 1
A annotationIsOnProperty() 0 3 1
B getAttributes() 0 59 10
A getMethodAttrs() 0 3 1
A __construct() 0 16 1
A getPropertyAttrs() 0 25 3
1
<?php
2
3
/**
4
 * AnnotationReader.php
5
 *
6
 * Jaxon annotation reader.
7
 *
8
 * @package jaxon-annotations
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2022 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-annotations
13
 */
14
15
namespace Jaxon\Annotations;
16
17
use Jaxon\App\Metadata\InputDataInterface;
18
use Jaxon\App\Metadata\Metadata;
19
use Jaxon\App\Metadata\MetadataInterface;
20
use Jaxon\App\Metadata\MetadataReaderInterface;
21
use Jaxon\Annotations\Annotation\AbstractAnnotation;
22
use Jaxon\Annotations\Annotation\AfterAnnotation;
23
use Jaxon\Annotations\Annotation\BeforeAnnotation;
24
use Jaxon\Annotations\Annotation\CallbackAnnotation;
25
use Jaxon\Annotations\Annotation\DataBagAnnotation;
26
use Jaxon\Annotations\Annotation\ExcludeAnnotation;
27
use Jaxon\Annotations\Annotation\UploadAnnotation;
28
use Jaxon\Annotations\Annotation\ContainerAnnotation;
29
use Jaxon\Exception\SetupException;
30
use mindplay\annotations\AnnotationException;
31
use mindplay\annotations\AnnotationManager;
32
use mindplay\annotations\standard\VarAnnotation;
33
34
use function array_filter;
35
use function array_merge;
36
use function count;
37
use function is_a;
38
39
class AnnotationReader implements MetadataReaderInterface
40
{
41
    /**
42
     * @var AnnotationManager
43
     */
44
    protected $xManager;
45
46
    /**
47
     * Properties types, read from the "var" annotations.
48
     *
49
     * @var array
50
     */
51
    protected $aPropTypes;
52
53
    /**
54
     * The type of the class member being currently processed.
55
     *
56
     * @var string
57
     */
58
    protected $sCurrMemberType;
59
60
    /**
61
     * The constructor
62
     *
63
     * @param AnnotationManager $xManager
64
     */
65
    public function __construct(AnnotationManager $xManager)
66
    {
67
        $this->xManager = $xManager;
68
        $this->xManager->registry['upload'] = UploadAnnotation::class;
69
        $this->xManager->registry['databag'] = DataBagAnnotation::class;
70
        $this->xManager->registry['exclude'] = ExcludeAnnotation::class;
71
        $this->xManager->registry['before'] = BeforeAnnotation::class;
72
        $this->xManager->registry['after'] = AfterAnnotation::class;
73
        $this->xManager->registry['di'] = ContainerAnnotation::class;
74
        $this->xManager->registry['callback'] = CallbackAnnotation::class;
75
        // Missing standard annotations.
76
        // We need to define this, otherwise they throw an exception, and make the whole processing fail.
77
        $this->xManager->registry['const'] = false;
78
        $this->xManager->registry['inheritDoc'] = false;
79
        $this->xManager->registry['template'] = false;
80
        $this->xManager->registry['param-closure-this'] = false;
81
    }
82
83
    /**
84
     * @return array
85
     */
86
    public function getPropTypes(): array
87
    {
88
        return $this->aPropTypes;
89
    }
90
91
    /**
92
     * @return bool
93
     */
94
    public function annotationIsOnProperty(): bool
95
    {
96
        return $this->sCurrMemberType === AnnotationManager::MEMBER_PROPERTY;
97
    }
98
99
    /**
100
     * @param array $aAnnotations
101
     *
102
     * @return array<array>
103
     * @throws AnnotationException
104
     */
105
    private function getMembersAttrs(array $aAnnotations): array
106
    {
107
        // Only keep the annotations declared in this package.
108
        $aAnnotations = array_filter($aAnnotations, function($xAnnotation) {
109
            return is_a($xAnnotation, AbstractAnnotation::class);
110
        });
111
112
        $aAttributes = [];
113
        foreach($aAnnotations as $xAnnotation)
114
        {
115
            $xAnnotation->setReader($this);
116
            $sName = $xAnnotation->getName();
117
            $xAnnotation->setPrevValue($aAttributes[$sName] ?? null);
118
            $xValue = $xAnnotation->getValue();
119
            if($sName === 'protected' && !$xValue)
120
            {
121
                // Ignore annotation @exclude with value false
122
                continue;
123
            }
124
            $aAttributes[$sName] = $xValue;
125
        }
126
        return $aAttributes;
127
    }
128
129
    /**
130
     * @param string $sClass
131
     *
132
     * @return array<array>
133
     * @throws AnnotationException
134
     */
135
    private function getClassAttrs(string $sClass): array
136
    {
137
        return $this->getMembersAttrs($this->xManager->getClassAnnotations($sClass));
138
    }
139
140
    /**
141
     * @param string $sClass
142
     * @param string $sMethod
143
     *
144
     * @return array<array>
145
     * @throws AnnotationException
146
     */
147
    private function getMethodAttrs(string $sClass, string $sMethod): array
148
    {
149
        return  $this->getMembersAttrs($this->xManager->getMethodAnnotations($sClass, $sMethod));
150
    }
151
152
    /**
153
     * @param string $sClass
154
     * @param string $sProperty
155
     *
156
     * @return array<array>
157
     * @throws AnnotationException
158
     */
159
    private function getPropertyAttrs(string $sClass, string $sProperty): array
160
    {
161
        /** @var array<ContainerAnnotation> */
162
        $aAnnotations = $this->xManager->getPropertyAnnotations($sClass, $sProperty);
163
        // Only keep the annotations declared in this package.
164
        $aAnnotations = array_filter($aAnnotations, function($xAnnotation) use($sProperty) {
165
            // Save the property type
166
            if(is_a($xAnnotation, VarAnnotation::class))
167
            {
168
                $this->aPropTypes[$sProperty] = $xAnnotation->type;
169
            }
170
            // Only container annotations are allowed on properties
171
            return is_a($xAnnotation, ContainerAnnotation::class);
172
        });
173
174
        $aAttributes = [];
175
        foreach($aAnnotations as $xAnnotation)
176
        {
177
            $xAnnotation->setReader($this);
178
            $xAnnotation->setAttr($sProperty);
179
            $sName = $xAnnotation->getName();
180
            $xAnnotation->setPrevValue($aAttributes[$sName] ?? null);
181
            $aAttributes[$sName] = $xAnnotation->getValue();
182
        }
183
        return $aAttributes;
184
    }
185
186
    /**
187
     * @inheritDoc
188
     * @throws SetupException
189
     */
190
    public function getAttributes(InputDataInterface $xInput): ?MetadataInterface
191
    {
192
        $this->aPropTypes = [];
193
        $sClass = $xInput->getReflectionClass()->getName();
194
195
        try
196
        {
197
            // Processing properties annotations
198
            $this->sCurrMemberType = AnnotationManager::MEMBER_PROPERTY;
199
200
            $aPropAttrs = [];
201
            // Properties annotations
202
            foreach($xInput->getProperties() as $sProperty)
203
            {
204
                $aPropertyAttrs = $this->getPropertyAttrs($sClass, $sProperty);
205
                foreach($aPropertyAttrs as $sName => $xValue)
206
                {
207
                    $aPropAttrs[$sName] = array_merge($aPropAttrs[$sName] ?? [], $xValue);
208
                }
209
            }
210
211
            // Processing class annotations
212
            $this->sCurrMemberType = AnnotationManager::MEMBER_CLASS;
213
214
            $aClassAttrs = $this->getClassAttrs($sClass);
215
            if(isset($aClassAttrs['protected']))
216
            {
217
                // The entire class is not to be exported.
218
                return new Metadata(true, [], []);
219
            }
220
221
            // Merge attributes and class annotations
222
            foreach($aPropAttrs as $sName => $xValue)
223
            {
224
                $aClassAttrs[$sName] = array_merge($aClassAttrs[$sName] ?? [], $xValue);
225
            }
226
227
            // Processing methods annotations
228
            $this->sCurrMemberType = AnnotationManager::MEMBER_METHOD;
229
230
            $aAttributes = count($aClassAttrs) > 0 ? ['*' => $aClassAttrs] : [];
231
            $aProtected = [];
232
            foreach($xInput->getMethods() as $sMethod)
233
            {
234
                $aMethodAttrs = $this->getMethodAttrs($sClass, $sMethod);
235
                if(isset($aMethodAttrs['protected']))
236
                {
237
                    $aProtected[] = $sMethod; // The method is not to be exported.
238
                }
239
                elseif(count($aMethodAttrs) > 0)
240
                {
241
                    $aAttributes[$sMethod] = $aMethodAttrs;
242
                }
243
            }
244
            return new Metadata(false, $aAttributes, $aProtected);
245
        }
246
        catch(AnnotationException $e)
247
        {
248
            throw new SetupException($e->getMessage());
249
        }
250
    }
251
}
252