ContainerAnnotation::parseProperties()   B
last analyzed

Complexity

Conditions 7
Paths 9

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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