Passed
Push — main ( e8812c...7db43c )
by Thierry
01:57
created

AnnotationReader::getMemberType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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