Passed
Push — main ( 32548f...0f6191 )
by Thierry
09:46
created

ContainerAnnotation::checkPropertiesNames()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 6
nop 0
dl 0
loc 13
rs 9.2222
c 0
b 0
f 0
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 getValue() 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 getValue() 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->annotationIsOnProperty())
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->annotationIsOnProperty())
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 bool
194
     */
195
    private function checkPropertiesNames(): bool
196
    {
197
        $nCount = count($this->properties);
198
        switch($nCount)
199
        {
200
        case 0:
201
            return true;
202
        case 1:
203
            return isset($this->properties['attr']) || isset($this->properties['class']);
204
        case 2:
205
            return isset($this->properties['attr']) && isset($this->properties['class']);
206
        default:
207
            return false;
208
        }
209
    }
210
211
    /**
212
     * @return void
213
     * @throws AnnotationException
214
     */
215
    private function parseProperties()
216
    {
217
        if(!$this->checkPropertiesNames())
218
        {
219
            throw new AnnotationException('The @di annotation accepts only "attr" or "class" as properties');
220
        }
221
222
        if(isset($this->properties['attr']))
223
        {
224
            if($this->xReader->annotationIsOnProperty())
225
            {
226
                throw new AnnotationException('The @di annotation does not allow the "attr" property on class attributes');
227
            }
228
            if(!is_string($this->properties['attr']))
229
            {
230
                throw new AnnotationException('The @di annotation requires a property "attr" of type string');
231
            }
232
            $this->sAttr = $this->properties['attr'];
233
        }
234
        if(isset($this->properties['class']))
235
        {
236
            if(!is_string($this->properties['class']))
237
            {
238
                throw new AnnotationException('The @di annotation requires a property "class" of type string');
239
            }
240
            $this->sClass = $this->getFullClassName($this->properties['class']);
241
        }
242
    }
243
244
    /**
245
     * @inheritDoc
246
     * @throws AnnotationException
247
     */
248
    public function getValue()
249
    {
250
        isset($this->properties['__value__']) ? $this->parseValue() : $this->parseProperties();
251
252
        // The type in the @di annotations can be set from the values in the @var annotations
253
        $aPropTypes = $this->xReader->getPropTypes();
254
        if($this->sClass === '' && isset($aPropTypes[$this->sAttr]))
255
        {
256
            $this->sClass = ltrim($aPropTypes[$this->sAttr], '\\');
257
        }
258
259
        if($this->xReader->annotationIsOnProperty() && $this->xPrevValue !== null)
260
        {
261
            throw new AnnotationException('Only one @di annotation is allowed on a property');
262
        }
263
        if(!$this->validateAttrName($this->sAttr))
264
        {
265
            throw new AnnotationException($this->sAttr . ' is not a valid "attr" value for the @di annotation');
266
        }
267
        if(!$this->validateClassName($this->sClass))
268
        {
269
            throw new AnnotationException($this->sClass . ' is not a valid "class" value for the @di annotation');
270
        }
271
272
        if(is_array($this->xPrevValue))
273
        {
274
            $this->xPrevValue[$this->sAttr] = $this->sClass; // Append the current value to the array
275
            return $this->xPrevValue;
276
        }
277
        return [$this->sAttr => $this->sClass]; // Return the current value in an array
278
    }
279
}
280