Passed
Push — main ( 813e95...426dc3 )
by Thierry
07:49
created

AnnotationReader::propAnnotations()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 3
nop 2
dl 0
loc 27
rs 9.8333
c 0
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\Annotations\Annotation\AbstractAnnotation;
18
use Jaxon\Annotations\Annotation\AfterAnnotation;
19
use Jaxon\Annotations\Annotation\BeforeAnnotation;
20
use Jaxon\Annotations\Annotation\DataBagAnnotation;
21
use Jaxon\Annotations\Annotation\ExcludeAnnotation;
22
use Jaxon\Annotations\Annotation\UploadAnnotation;
23
use Jaxon\Annotations\Annotation\ContainerAnnotation;
24
use Jaxon\Di\Container;
25
use Jaxon\Exception\SetupException;
26
use Jaxon\Plugin\AnnotationReaderInterface;
27
use mindplay\annotations\AnnotationCache;
28
use mindplay\annotations\AnnotationException;
29
use mindplay\annotations\AnnotationManager;
30
use mindplay\annotations\standard\VarAnnotation;
31
32
use function array_filter;
33
use function array_merge;
34
use function count;
35
use function is_a;
36
37
class AnnotationReader
38
{
39
    /**
40
     * @var AnnotationManager
41
     */
42
    protected $xManager;
43
44
    /**
45
     * Properties types, read from the "@var annotations"
0 ignored issues
show
Documentation Bug introduced by
The doc comment annotations" at position 0 could not be parsed: Unknown type name 'annotations"' at position 0 in annotations".
Loading history...
46
     *
47
     * @var array
48
     */
49
    protected $aPropTypes;
50
51
    /**
52
     * The constructor
53
     *
54
     * @param AnnotationManager $xManager
55
     */
56
    public function __construct(AnnotationManager $xManager)
57
    {
58
        $this->xManager = $xManager;
59
        $this->xManager->registry['upload'] = UploadAnnotation::class;
60
        $this->xManager->registry['databag'] = DataBagAnnotation::class;
61
        $this->xManager->registry['exclude'] = ExcludeAnnotation::class;
62
        $this->xManager->registry['before'] = BeforeAnnotation::class;
63
        $this->xManager->registry['after'] = AfterAnnotation::class;
64
        $this->xManager->registry['di'] = ContainerAnnotation::class;
65
        // Missing standard annotations.
66
        // We need to define this, otherwise they throw an exception, and make the whole processing fail.
67
        $this->xManager->registry['const'] = false;
68
        $this->xManager->registry['inheritDoc'] = false;
69
    }
70
71
    /**
72
     * Register the annotation reader into the Jaxon DI container
73
     *
74
     * @param bool $bForce Force registration
75
     * @param Container $di
76
     *
77
     * @return void
78
     */
79
    public static function register(Container $di, bool $bForce = false)
80
    {
81
        if(!$bForce && $di->h(AnnotationReader::class))
82
        {
83
            return;
84
        }
85
86
        $di->val('jaxon_annotations_cache_dir', sys_get_temp_dir());
87
        $di->set(AnnotationReader::class, function($c) {
88
            $xAnnotationManager = new AnnotationManager();
89
            $xAnnotationManager->cache = new AnnotationCache($c->g('jaxon_annotations_cache_dir'));
90
            return new AnnotationReader($xAnnotationManager);
91
        });
92
        $di->alias(AnnotationReaderInterface::class, AnnotationReader::class);
93
    }
94
95
    /**
96
     * @return array
97
     */
98
    public function getPropTypes(): array
99
    {
100
        return $this->aPropTypes;
101
    }
102
103
    /**
104
     * @param array $aAnnotations
105
     *
106
     * @return AbstractAnnotation[]
107
     * @throws AnnotationException
108
     */
109
    private function filterAnnotations(array $aAnnotations): array
110
    {
111
        // Only keep the annotations declared in this package.
112
        $aAnnotations = array_filter($aAnnotations, function($xAnnotation) {
113
            return is_a($xAnnotation, AbstractAnnotation::class);
114
        });
115
116
        $aAttributes = [];
117
        foreach($aAnnotations as $xAnnotation)
118
        {
119
            $xAnnotation->setReader($this);
120
            $sName = $xAnnotation->getName();
121
            $xAnnotation->setPrevValue($aAttributes[$sName] ?? null);
122
            $xValue = $xAnnotation->getValue();
123
            if($sName === 'protected' && !$xValue)
124
            {
125
                continue; // Ignore annotation @exclude with value false
126
            }
127
            $aAttributes[$sName] = $xValue;
128
        }
129
        return $aAttributes;
130
    }
131
132
    /**
133
     * @param string $sProperty
134
     * @param array $aAnnotations
135
     *
136
     * @return array
137
     * @throws AnnotationException
138
     */
139
    private function propAnnotations(string $sProperty, array $aAnnotations): array
140
    {
141
        // Only keep the annotations declared in this package.
142
        $aAnnotations = array_filter($aAnnotations, function($xAnnotation) use($sProperty) {
143
            // Save the property type
144
            if(is_a($xAnnotation, VarAnnotation::class))
145
            {
146
                $this->aPropTypes[$sProperty] = $xAnnotation->type;
147
            }
148
            // Only container annotations are allowed on properties
149
            return is_a($xAnnotation, ContainerAnnotation::class);
150
        });
151
152
        $nCount = count($aAnnotations);
153
        if($nCount === 0)
154
        {
155
            return ['', null];
156
        }
157
        if($nCount > 1)
158
        {
159
            throw new AnnotationException('Only one @di annotation is allowed on a property');
160
        }
161
162
        $xAnnotation = $aAnnotations[0];
163
        $xAnnotation->setReader($this);
164
        $xAnnotation->setAttr($sProperty);
165
        return [$xAnnotation->getName(), $xAnnotation->getValue()];
166
    }
167
168
    /**
169
     * Get the class attributes from its annotations
170
     *
171
     * @param string $sClass
172
     * @param array $aMethods
173
     * @param array $aProperties
174
     *
175
     * @return array
176
     * @throws SetupException
177
     */
178
    public function getAttributes(string $sClass, array $aMethods, array $aProperties = []): array
179
    {
180
        try
181
        {
182
            // Processing properties annotations
183
            ContainerAnnotation::$sMemberType = AnnotationManager::MEMBER_PROPERTY;
184
185
            $this->aPropTypes = [];
186
            $aPropAttrs = [];
187
            // Properties annotations
188
            foreach($aProperties as $sProperty)
189
            {
190
                [$sName, $xValue] = $this->propAnnotations($sProperty, $this->xManager->getPropertyAnnotations($sClass, $sProperty));
191
                if(!$xValue)
192
                {
193
                    continue;
194
                }
195
                $aPropAttrs[$sName] = array_merge($aPropAttrs[$sName] ?? [], $xValue);
196
            }
197
198
            // Processing class annotations
199
            ContainerAnnotation::$sMemberType = AnnotationManager::MEMBER_CLASS;
200
201
            $aClassAttrs = $this->filterAnnotations($this->xManager->getClassAnnotations($sClass));
202
            if(isset($aClassAttrs['protected']))
203
            {
204
                return [true, [], []]; // The entire class is not to be exported.
205
            }
206
207
            // Merge attributes and class annotations
208
            foreach($aPropAttrs as $sName => $xValue)
209
            {
210
                $aClassAttrs[$sName] = array_merge($aClassAttrs[$sName] ?? [], $xValue);
211
            }
212
213
            $aAttributes = [];
214
            if(count($aClassAttrs) > 0)
215
            {
216
                $aAttributes['*'] = $aClassAttrs;
217
            }
218
219
            // Processing methods annotations
220
            ContainerAnnotation::$sMemberType = AnnotationManager::MEMBER_METHOD;
221
222
            $aProtected = [];
223
            foreach($aMethods as $sMethod)
224
            {
225
                $aMethodAttrs = $this->filterAnnotations($this->xManager->getMethodAnnotations($sClass, $sMethod));
226
                if(isset($aMethodAttrs['protected']))
227
                {
228
                    $aProtected[] = $sMethod; // The method is not to be exported.
229
                }
230
                elseif(count($aMethodAttrs) > 0)
231
                {
232
                    $aAttributes[$sMethod] = $aMethodAttrs;
233
                }
234
            }
235
            return [false, $aAttributes, $aProtected];
236
        }
237
        catch(AnnotationException $e)
238
        {
239
            throw new SetupException($e->getMessage());
240
        }
241
    }
242
}
243