Passed
Push — main ( ad0cf2...5ca15e )
by Thierry
01:57
created

AnnotationReader::getAttributes()   B

Complexity

Conditions 10
Paths 136

Size

Total Lines 62
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 29
nc 136
nop 3
dl 0
loc 62
rs 7.3666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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