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

ContainerAnnotation::parseProperties()   C

Complexity

Conditions 13
Paths 9

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 15
nc 9
nop 0
dl 0
loc 29
rs 6.6166
c 0
b 0
f 0

How to fix   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
 * ContainerAnnotation.php
5
 *
6
 * Jaxon annotation for DI injection.
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\Annotation;
16
17
use mindplay\annotations\AnnotationException;
18
use mindplay\annotations\AnnotationFile;
19
use mindplay\annotations\AnnotationManager;
20
use mindplay\annotations\IAnnotationFileAware;
21
22
use function count;
23
use function is_array;
24
use function is_string;
25
use function ltrim;
26
use function preg_match;
27
use function preg_split;
28
29
/**
30
 * Specifies attributes to inject into a callable object.
31
 *
32
 * @usage('class' => true, 'method'=>true, 'property'=>true, 'multiple'=>true, 'inherited'=>true)
33
 */
34
class ContainerAnnotation extends AbstractAnnotation implements IAnnotationFileAware
35
{
36
    /**
37
     * The annotation properties
38
     *
39
     * @var array
40
     */
41
    protected $properties = [];
42
43
    /**
44
     * The attribute name
45
     *
46
     * @var string
47
     */
48
    protected $sAttr = '';
49
50
    /**
51
     * The attribute class
52
     *
53
     * @var string
54
     */
55
    protected $sClass = '';
56
57
    /**
58
     * @var AnnotationFile
59
     */
60
    protected $xClassFile;
61
62
    /**
63
     * @inheritDoc
64
     */
65
    public function getName(): string
66
    {
67
        return '__di';
68
    }
69
70
    /**
71
     * @param string $sAttr
72
     *
73
     * @return void
74
     */
75
    public function setAttr(string $sAttr): void
76
    {
77
        $this->sAttr = $sAttr;
78
    }
79
80
    /**
81
     * @inheritDoc
82
     */
83
    public function setAnnotationFile(AnnotationFile $file)
84
    {
85
        $this->xClassFile = $file;
86
    }
87
88
    /**
89
     * @inheritDoc
90
     */
91
    public static function parseAnnotation($value)
92
    {
93
        // We need to know which type of class member the annotation is attached to (attribute,
94
        // method or class), which is possible only when calling the initAnnotation() method.
95
        // So we just return raw data in a custom format here.
96
        return ['__value__' => $value];
97
    }
98
99
    /**
100
     * @inheritDoc
101
     */
102
    public function initAnnotation(array $properties)
103
    {
104
        // We need to know which type of class member the annotation is attached to (attribute,
105
        // method or class), which is possible only when calling the initAnnotation() method.
106
        // So we just save the properties locally here.
107
        $this->properties = $properties;
108
    }
109
110
    /**
111
     * @param string $sClassName
112
     *
113
     * @return bool
114
     */
115
    protected function validateClassName(string $sClassName): bool
116
    {
117
        return preg_match('/^(\\\)?([a-zA-Z][a-zA-Z0-9_]*)(\\\[a-zA-Z][a-zA-Z0-9_]*)*$/', $sClassName) > 0;
118
    }
119
120
    /**
121
     * @param string $sAttrName
122
     *
123
     * @return bool
124
     */
125
    protected function validateAttrName(string $sAttrName): bool
126
    {
127
        return preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $sAttrName) > 0;
128
    }
129
130
    /**
131
     * @param string $sClassName
132
     *
133
     * @return string
134
     */
135
    private function getFullClassName(string $sClassName): string
136
    {
137
        return ltrim($this->xClassFile->resolveType($sClassName), '\\');;
138
    }
139
140
    /**
141
     * @return void
142
     * @throws AnnotationException
143
     */
144
    private function parseValue()
145
    {
146
        $value = $this->properties['__value__'];
147
        $aParams = preg_split("/[\s]+/", $value, 3);
148
        $nParamCount = count($aParams);
149
        if($nParamCount === 1)
150
        {
151
            // For a property, the only parameter is the class. Otherwise, it is the attribute.
152
            if($this->xReader->getMemberType() === AnnotationManager::MEMBER_PROPERTY)
153
            {
154
                if(substr($aParams[0], 0, 1) === '$')
155
                {
156
                    throw new AnnotationException('The only property of the @di annotation must be a class name');
157
                }
158
                $this->sClass = $this->getFullClassName($aParams[0]);
159
                return;
160
            }
161
            if(substr($aParams[0], 0, 1) !== '$')
162
            {
163
                throw new AnnotationException('The only property of the @di annotation must be a var name');
164
            }
165
            $this->sAttr = substr($aParams[0], 1);
166
            return;
167
        }
168
        if($nParamCount === 2)
169
        {
170
            // For a property, having 2 parameters is not allowed.
171
            if($this->xReader->getMemberType() === AnnotationManager::MEMBER_PROPERTY)
172
            {
173
                throw new AnnotationException('The @di annotation accepts only one property on a class attribute');
174
            }
175
176
            if(substr($aParams[0], 0, 1) !== '$')
177
            {
178
                throw new AnnotationException('The only property of the @di annotation must be a var name');
179
            }
180
            if(substr($aParams[1], 0, 1) === '$')
181
            {
182
                throw new AnnotationException('The first property of the @di annotation must be a class name');
183
            }
184
            $this->sAttr = substr($aParams[0], 1);
185
            $this->sClass = $this->getFullClassName($aParams[1]);
186
            return;
187
        }
188
189
        throw new AnnotationException('The @di annotation only accepts one or two properties');
190
    }
191
192
    /**
193
     * @return void
194
     * @throws AnnotationException
195
     */
196
    private function parseProperties()
197
    {
198
        $nCount = count($this->properties);
199
        if($nCount > 2 ||
200
            ($nCount === 2 && !(isset($this->properties['attr']) && isset($this->properties['class']))) ||
201
            ($nCount === 1 && !(isset($this->properties['attr']) || isset($this->properties['class']))))
202
        {
203
            throw new AnnotationException('The @di annotation accepts only "attr" or "class" as properties');
204
        }
205
206
        if(isset($this->properties['attr']))
207
        {
208
            if($this->xReader->getMemberType() === AnnotationManager::MEMBER_PROPERTY)
209
            {
210
                throw new AnnotationException('The @di annotation does not allow the "attr" property on class attributes');
211
            }
212
            if(!is_string($this->properties['attr']))
213
            {
214
                throw new AnnotationException('The @di annotation requires a property "attr" of type string');
215
            }
216
            $this->sAttr = $this->properties['attr'];
217
        }
218
        if(isset($this->properties['class']))
219
        {
220
            if(!is_string($this->properties['class']))
221
            {
222
                throw new AnnotationException('The @di annotation requires a property "class" of type string');
223
            }
224
            $this->sClass = $this->getFullClassName($this->properties['class']);
225
        }
226
    }
227
228
    /**
229
     * @inheritDoc
230
     * @throws AnnotationException
231
     */
232
    public function getValue()
233
    {
234
        isset($this->properties['__value__']) ? $this->parseValue() : $this->parseProperties();
235
236
        // The type in the @di annotations can be set from the values in the @var annotations
237
        $aPropTypes = $this->xReader->getPropTypes();
238
        if($this->sClass === '' && isset($aPropTypes[$this->sAttr]))
239
        {
240
            $this->sClass = ltrim($aPropTypes[$this->sAttr], '\\');
241
        }
242
243
        if(!$this->validateAttrName($this->sAttr))
244
        {
245
            throw new AnnotationException($this->sAttr . ' is not a valid "attr" value for the @di annotation');
246
        }
247
        if(!$this->validateClassName($this->sClass))
248
        {
249
            throw new AnnotationException($this->sClass . ' is not a valid "class" value for the @di annotation');
250
        }
251
252
        if(is_array($this->xPrevValue))
253
        {
254
            $this->xPrevValue[$this->sAttr] = $this->sClass; // Append the current value to the array
255
            return $this->xPrevValue;
256
        }
257
        return [$this->sAttr => $this->sClass]; // Return the current value in an array
258
    }
259
}
260