Passed
Push — main ( 7f530d...7aae9b )
by Thierry
05:54
created

AnnotationReader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 13
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 17
rs 9.8333
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\ExportAnnotation;
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 count;
36
use function is_a;
37
38
class AnnotationReader implements MetadataReaderInterface
39
{
40
    /**
41
     * @var AnnotationManager
42
     */
43
    protected $xManager;
44
45
    /**
46
     * @var Metadata
47
     */
48
    protected $xMetadata;
49
50
    /**
51
     * Properties types, read from the "var" annotations.
52
     *
53
     * @var array
54
     */
55
    protected $aPropTypes;
56
57
    /**
58
     * The type of the class member being currently processed.
59
     *
60
     * @var string
61
     */
62
    protected $sCurrMemberType;
63
64
    /**
65
     * The constructor
66
     *
67
     * @param AnnotationManager $xManager
68
     */
69
    public function __construct(AnnotationManager $xManager)
70
    {
71
        $this->xManager = $xManager;
72
        $this->xManager->registry['upload'] = UploadAnnotation::class;
73
        $this->xManager->registry['databag'] = DatabagAnnotation::class;
74
        $this->xManager->registry['exclude'] = ExcludeAnnotation::class;
75
        $this->xManager->registry['export'] = ExportAnnotation::class;
76
        $this->xManager->registry['before'] = BeforeAnnotation::class;
77
        $this->xManager->registry['after'] = AfterAnnotation::class;
78
        $this->xManager->registry['di'] = ContainerAnnotation::class;
79
        $this->xManager->registry['callback'] = CallbackAnnotation::class;
80
        // Missing standard annotations.
81
        // We need to define this, otherwise they throw an exception, and make the whole processing fail.
82
        $this->xManager->registry['const'] = false;
83
        $this->xManager->registry['inheritDoc'] = false;
84
        $this->xManager->registry['template'] = false;
85
        $this->xManager->registry['param-closure-this'] = false;
86
    }
87
88
    /**
89
     * @return array
90
     */
91
    public function getPropTypes(): array
92
    {
93
        return $this->aPropTypes;
94
    }
95
96
    /**
97
     * @return bool
98
     */
99
    public function annotationIsOnProperty(): bool
100
    {
101
        return $this->sCurrMemberType === AnnotationManager::MEMBER_PROPERTY;
102
    }
103
104
    /**
105
     * @param string $sClass
106
     *
107
     * @return void
108
     * @throws AnnotationException
109
     */
110
    private function readClassAnnotations(string $sClass): void
111
    {
112
        // Only keep the annotations declared in this package.
113
        /** @var array<AbstractAnnotation> */
114
        $aAnnotations = array_filter(
115
            $this->xManager->getClassAnnotations($sClass),
116
            fn($xAnnotation) => is_a($xAnnotation, AbstractAnnotation::class)
117
        );
118
        // First check if the class is excluded.
119
        foreach($aAnnotations as $xAnnotation)
120
        {
121
            if(is_a($xAnnotation, ExcludeAnnotation::class))
122
            {
123
                $xAnnotation->saveValue($this->xMetadata);
124
            }
125
        }
126
        if($this->xMetadata->isExcluded())
127
        {
128
            return;
129
        }
130
131
        foreach($aAnnotations as $xAnnotation)
132
        {
133
            if(!is_a($xAnnotation, ExcludeAnnotation::class))
134
            {
135
                $xAnnotation->saveValue($this->xMetadata);
136
            }
137
        }
138
    }
139
140
    /**
141
     * @param string $sClass
142
     * @param string $sProperty
143
     *
144
     * @return void
145
     * @throws AnnotationException
146
     */
147
    private function readPropertyAnnotations(string $sClass, string $sProperty): void
148
    {
149
        /** @var array<ContainerAnnotation> */
150
        // Only keep the annotations declared in this package.
151
        $aAnnotations = array_filter(
152
            $this->xManager->getPropertyAnnotations($sClass, $sProperty),
153
            function($xAnnotation) use($sProperty) {
154
                // Save the property type
155
                if(is_a($xAnnotation, VarAnnotation::class))
156
                {
157
                    $this->aPropTypes[$sProperty] = $xAnnotation->type;
158
                }
159
                // Only container annotations are allowed on properties
160
                return is_a($xAnnotation, ContainerAnnotation::class);
161
            }
162
        );
163
        if(count($aAnnotations) > 1)
164
        {
165
            throw new AnnotationException('Only one @di annotation is allowed on a property');
166
        }
167
168
        foreach($aAnnotations as $xAnnotation)
169
        {
170
            $xAnnotation->setAttr($sProperty);
171
            $xAnnotation->saveValue($this->xMetadata);
172
        }
173
    }
174
175
    /**
176
     * @param string $sClass
177
     * @param string $sMethod
178
     *
179
     * @return void
180
     * @throws AnnotationException
181
     */
182
    private function readMethodAnnotations(string $sClass, string $sMethod): void
183
    {
184
        // Only keep the annotations declared in this package.
185
        /** @var array<AbstractAnnotation> */
186
        $aAnnotations = array_filter(
187
            $this->xManager->getMethodAnnotations($sClass, $sMethod),
188
            fn($xAnnotation) => is_a($xAnnotation, AbstractAnnotation::class)
189
        );
190
        foreach($aAnnotations as $xAnnotation)
191
        {
192
            $xAnnotation->saveValue($this->xMetadata, $sMethod);
193
        }
194
    }
195
196
    /**
197
     * @throws SetupException
198
     */
199
    public function getAttributes(InputData $xInput): Metadata
200
    {
201
        ContainerAnnotation::$xReader = $this;
202
        $this->aPropTypes = [];
203
        $this->xMetadata = new Metadata();
204
        $sClass = $xInput->getReflectionClass()->getName();
205
206
        try
207
        {
208
            // Processing class annotations
209
            $this->sCurrMemberType = AnnotationManager::MEMBER_CLASS;
210
211
            $this->readClassAnnotations($sClass);
212
213
            // Processing properties annotations
214
            $this->sCurrMemberType = AnnotationManager::MEMBER_PROPERTY;
215
216
            // Properties annotations
217
            foreach($xInput->getProperties() as $sProperty)
218
            {
219
                $this->readPropertyAnnotations($sClass, $sProperty);
220
            }
221
222
            // The methods annotations are not taken for excluded classes.
223
            if($this->xMetadata->isExcluded())
224
            {
225
                return $this->xMetadata;
226
            }
227
228
            // Processing methods annotations
229
            $this->sCurrMemberType = AnnotationManager::MEMBER_METHOD;
230
231
            foreach($xInput->getMethods() as $sMethod)
232
            {
233
                $this->readMethodAnnotations($sClass, $sMethod);
234
            }
235
236
            return $this->xMetadata;
237
        }
238
        catch(AnnotationException $e)
239
        {
240
            throw new SetupException($e->getMessage());
241
        }
242
    }
243
}
244