Passed
Push — main ( 95098f...5a6d0d )
by Thierry
35:37 queued 24:37
created

AnnotationReader::getMembersAttrs()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
nc 3
nop 1
dl 0
loc 22
rs 9.8666
c 1
b 0
f 0
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\InputData;
18
use Jaxon\App\Metadata\Metadata;
19
use Jaxon\App\Metadata\MetadataReaderInterface;
20
use Jaxon\Annotations\Annotation\AbstractAnnotation;
21
use Jaxon\Annotations\Annotation\AfterAnnotation;
22
use Jaxon\Annotations\Annotation\BeforeAnnotation;
23
use Jaxon\Annotations\Annotation\CallbackAnnotation;
24
use Jaxon\Annotations\Annotation\DataBagAnnotation;
25
use Jaxon\Annotations\Annotation\ExcludeAnnotation;
26
use Jaxon\Annotations\Annotation\UploadAnnotation;
27
use Jaxon\Annotations\Annotation\ContainerAnnotation;
28
use Jaxon\Exception\SetupException;
29
use mindplay\annotations\AnnotationException;
30
use mindplay\annotations\AnnotationManager;
31
use mindplay\annotations\standard\VarAnnotation;
32
33
use function array_filter;
34
use function count;
35
use function is_a;
36
37
class AnnotationReader implements MetadataReaderInterface
38
{
39
    /**
40
     * @var AnnotationManager
41
     */
42
    protected $xManager;
43
44
    /**
45
     * @var Metadata
46
     */
47
    protected $xMetadata;
48
49
    /**
50
     * Properties types, read from the "var" annotations.
51
     *
52
     * @var array
53
     */
54
    protected $aPropTypes;
55
56
    /**
57
     * The type of the class member being currently processed.
58
     *
59
     * @var string
60
     */
61
    protected $sCurrMemberType;
62
63
    /**
64
     * The constructor
65
     *
66
     * @param AnnotationManager $xManager
67
     */
68
    public function __construct(AnnotationManager $xManager)
69
    {
70
        $this->xManager = $xManager;
71
        $this->xManager->registry['upload'] = UploadAnnotation::class;
72
        $this->xManager->registry['databag'] = DataBagAnnotation::class;
73
        $this->xManager->registry['exclude'] = ExcludeAnnotation::class;
74
        $this->xManager->registry['before'] = BeforeAnnotation::class;
75
        $this->xManager->registry['after'] = AfterAnnotation::class;
76
        $this->xManager->registry['di'] = ContainerAnnotation::class;
77
        $this->xManager->registry['callback'] = CallbackAnnotation::class;
78
        // Missing standard annotations.
79
        // We need to define this, otherwise they throw an exception, and make the whole processing fail.
80
        $this->xManager->registry['const'] = false;
81
        $this->xManager->registry['inheritDoc'] = false;
82
        $this->xManager->registry['template'] = false;
83
        $this->xManager->registry['param-closure-this'] = false;
84
    }
85
86
    /**
87
     * @return array
88
     */
89
    public function getPropTypes(): array
90
    {
91
        return $this->aPropTypes;
92
    }
93
94
    /**
95
     * @return bool
96
     */
97
    public function annotationIsOnProperty(): bool
98
    {
99
        return $this->sCurrMemberType === AnnotationManager::MEMBER_PROPERTY;
100
    }
101
102
    /**
103
     * @param string $sClass
104
     *
105
     * @return void
106
     * @throws AnnotationException
107
     */
108
    private function getClassAttrs(string $sClass): void
109
    {
110
        // Only keep the annotations declared in this package.
111
        /** @var array<AbstractAnnotation> */
112
        $aAnnotations = array_filter(
113
            $this->xManager->getClassAnnotations($sClass),
114
            fn($xAnnotation) => is_a($xAnnotation, AbstractAnnotation::class)
115
        );
116
        // First check if the class is excluded.
117
        foreach($aAnnotations as $xAnnotation)
118
        {
119
            if(is_a($xAnnotation, ExcludeAnnotation::class))
120
            {
121
                $xAnnotation->saveValue($this->xMetadata);
122
            }
123
        }
124
        if($this->xMetadata->isExcluded())
125
        {
126
            return;
127
        }
128
129
        foreach($aAnnotations as $xAnnotation)
130
        {
131
            if(!is_a($xAnnotation, ExcludeAnnotation::class))
132
            {
133
                $xAnnotation->saveValue($this->xMetadata);
134
            }
135
        }
136
    }
137
138
    /**
139
     * @param string $sClass
140
     * @param string $sProperty
141
     *
142
     * @return void
143
     * @throws AnnotationException
144
     */
145
    private function getPropertyAttrs(string $sClass, string $sProperty): void
146
    {
147
        /** @var array<ContainerAnnotation> */
148
        // Only keep the annotations declared in this package.
149
        $aAnnotations = array_filter(
150
            $this->xManager->getPropertyAnnotations($sClass, $sProperty),
151
            function($xAnnotation) use($sProperty) {
152
                // Save the property type
153
                if(is_a($xAnnotation, VarAnnotation::class))
154
                {
155
                    $this->aPropTypes[$sProperty] = $xAnnotation->type;
156
                }
157
                // Only container annotations are allowed on properties
158
                return is_a($xAnnotation, ContainerAnnotation::class);
159
            }
160
        );
161
        if(count($aAnnotations) > 1)
162
        {
163
            throw new AnnotationException('Only one @di annotation is allowed on a property');
164
        }
165
166
        foreach($aAnnotations as $xAnnotation)
167
        {
168
            $xAnnotation->setAttr($sProperty);
169
            $xAnnotation->saveValue($this->xMetadata);
170
        }
171
    }
172
173
    /**
174
     * @param string $sClass
175
     * @param string $sMethod
176
     *
177
     * @return void
178
     * @throws AnnotationException
179
     */
180
    private function getMethodAttrs(string $sClass, string $sMethod): void
181
    {
182
        // Only keep the annotations declared in this package.
183
        /** @var array<AbstractAnnotation> */
184
        $aAnnotations = array_filter(
185
            $this->xManager->getMethodAnnotations($sClass, $sMethod),
186
            fn($xAnnotation) => is_a($xAnnotation, AbstractAnnotation::class)
187
        );
188
        foreach($aAnnotations as $xAnnotation)
189
        {
190
            $xAnnotation->saveValue($this->xMetadata, $sMethod);
191
        }
192
    }
193
194
    /**
195
     * @inheritDoc
196
     * @throws SetupException
197
     */
198
    public function getAttributes(InputData $xInput): ?Metadata
199
    {
200
        ContainerAnnotation::$xReader = $this;
201
        $this->aPropTypes = [];
202
        $this->xMetadata = new Metadata();
0 ignored issues
show
Bug introduced by
The call to Jaxon\App\Metadata\Metadata::__construct() has too few arguments starting with bIsExcluded. ( Ignorable by Annotation )

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

202
        $this->xMetadata = /** @scrutinizer ignore-call */ new Metadata();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
203
        $sClass = $xInput->getReflectionClass()->getName();
204
205
        try
206
        {
207
            // Processing class annotations
208
            $this->sCurrMemberType = AnnotationManager::MEMBER_CLASS;
209
210
            $this->getClassAttrs($sClass);
211
            if($this->xMetadata->isExcluded())
212
            {
213
                // The entire class is not to be exported.
214
                return $this->xMetadata;
215
            }
216
217
            // Processing properties annotations
218
            $this->sCurrMemberType = AnnotationManager::MEMBER_PROPERTY;
219
220
            // Properties annotations
221
            foreach($xInput->getProperties() as $sProperty)
222
            {
223
                $this->getPropertyAttrs($sClass, $sProperty);
224
            }
225
226
            // Processing methods annotations
227
            $this->sCurrMemberType = AnnotationManager::MEMBER_METHOD;
228
229
            foreach($xInput->getMethods() as $sMethod)
230
            {
231
                $this->getMethodAttrs($sClass, $sMethod);
232
            }
233
234
            return $this->xMetadata;
235
        }
236
        catch(AnnotationException $e)
237
        {
238
            throw new SetupException($e->getMessage());
239
        }
240
    }
241
}
242