Passed
Push — main ( 813e95...426dc3 )
by Thierry
07:49
created

ContainerAnnotation::validateClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
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
use function rtrim;
29
30
/**
31
 * Specifies attributes to inject into a callable object.
32
 *
33
 * @usage('class' => true, 'method'=>true, 'property'=>true, 'multiple'=>true, 'inherited'=>true)
34
 */
35
class ContainerAnnotation extends AbstractAnnotation implements IAnnotationFileAware
36
{
37
    /**
38
     * The attribute name
39
     *
40
     * @var string
41
     */
42
    protected $sAttr = '';
43
44
    /**
45
     * The attribute class
46
     *
47
     * @var string
48
     */
49
    protected $sClass = '';
50
51
    /**
52
     * @var AnnotationFile
53
     */
54
    protected $xClassFile;
55
56
    /**
57
     * @var string
58
     */
59
    public static $sMemberType;
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 initAnnotation() method.
94
        // So we just return raw data in a custom format here.
95
        return ['__raw' => $value];
96
    }
97
98
    /**
99
     * @param string $value
100
     *
101
     * @return array
102
     * @throws AnnotationException
103
     */
104
    protected function parseValue(string $value): array
105
    {
106
        $aParams = preg_split("/[\s]+/", $value, 3);
107
        $nParamCount = count($aParams);
108
        if($nParamCount === 1)
109
        {
110
            // For a property, the only parameter is the class. Otherwise, it is the attribute.
111
            if(self::$sMemberType === AnnotationManager::MEMBER_PROPERTY)
112
            {
113
                $sClass = rtrim($aParams[0]);
114
                if(substr($sClass, 0, 1) === '$')
115
                {
116
                    throw new AnnotationException('The only property of the @di annotation must be a class name');
117
                }
118
                return ['class' => $sClass];
119
            }
120
            $sAttr = rtrim($aParams[0]);
121
            if(substr($sAttr, 0, 1) !== '$')
122
            {
123
                throw new AnnotationException('The only property of the @di annotation must be a var name');
124
            }
125
            return ['attr' => substr($sAttr,1)];
126
        }
127
        if($nParamCount === 2)
128
        {
129
            if(self::$sMemberType === AnnotationManager::MEMBER_PROPERTY)
130
            {
131
                throw new AnnotationException('The @di annotation accepts only one property on a class attribute');
132
            }
133
            $sAttr = rtrim($aParams[0]);
134
            if(substr($sAttr, 0, 1) !== '$')
135
            {
136
                throw new AnnotationException('The only property of the @di annotation must be a var name');
137
            }
138
            $sClass = rtrim($aParams[1]);
139
            if(substr($sClass, 0, 1) === '$')
140
            {
141
                throw new AnnotationException('The first property of the @di annotation must be a class name');
142
            }
143
            // For a property, having 2 parameters is not allowed.
144
            return ['attr' => substr($sAttr,1), 'class' => $sClass];
145
        }
146
147
        throw new AnnotationException('The @di annotation only accepts one or two properties');
148
    }
149
150
    /**
151
     * @inheritDoc
152
     * @throws AnnotationException
153
     */
154
    public function initAnnotation(array $properties)
155
    {
156
        if(isset($properties['__raw']))
157
        {
158
            $properties = $this->parseValue($properties['__raw']);
159
        }
160
        $nCount = count($properties);
161
        if($nCount > 2 ||
162
            ($nCount === 2 && !(isset($properties['attr']) && isset($properties['class']))) ||
163
            ($nCount === 1 && !(isset($properties['attr']) || isset($properties['class']))))
164
        {
165
            throw new AnnotationException('The @di annotation accepts only "attr" or "class" as properties');
166
        }
167
168
        if(isset($properties['attr']))
169
        {
170
            if(self::$sMemberType === AnnotationManager::MEMBER_PROPERTY)
171
            {
172
                throw new AnnotationException('The @di annotation does not allow the "attr" property on class attributes');
173
            }
174
            if(!is_string($properties['attr']))
175
            {
176
                throw new AnnotationException('The @di annotation requires a property "attr" of type string');
177
            }
178
            $this->sAttr = $properties['attr'];
179
        }
180
        if(isset($properties['class']))
181
        {
182
            if(!is_string($properties['class']))
183
            {
184
                throw new AnnotationException('The @di annotation requires a property "class" of type string');
185
            }
186
            $this->sClass = ltrim($this->xClassFile->resolveType($properties['class']), '\\');
187
        }
188
    }
189
190
    /**
191
     * @param string $sClassName
192
     *
193
     * @return bool
194
     */
195
    protected function validateClassName(string $sClassName): bool
196
    {
197
        return preg_match('/^(\\\)?([a-zA-Z][a-zA-Z0-9_]*)(\\\[a-zA-Z][a-zA-Z0-9_]*)*$/', $sClassName) > 0;
198
    }
199
200
    /**
201
     * @param string $sAttrName
202
     *
203
     * @return bool
204
     */
205
    protected function validateAttrName(string $sAttrName): bool
206
    {
207
        return preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $sAttrName) > 0;
208
    }
209
210
    /**
211
     * @inheritDoc
212
     * @throws AnnotationException
213
     */
214
    public function getValue()
215
    {
216
        // The type in the @di annotations can be set from the values in the @var annotations
217
        $aPropTypes = $this->xReader->getPropTypes();
218
        if($this->sClass === '' && isset($aPropTypes[$this->sAttr]))
219
        {
220
            $this->sClass = ltrim($aPropTypes[$this->sAttr], '\\');
221
        }
222
223
        if(!$this->validateAttrName($this->sAttr))
224
        {
225
            throw new AnnotationException($this->sAttr . ' is not a valid "attr" value for the @di annotation');
226
        }
227
        if(!$this->validateClassName($this->sClass))
228
        {
229
            throw new AnnotationException($this->sClass . ' is not a valid "class" value for the @di annotation');
230
        }
231
        if(is_array($this->xPrevValue))
232
        {
233
            $this->xPrevValue[$this->sAttr] = $this->sClass; // Append the current value to the array
234
            return $this->xPrevValue;
235
        }
236
        return [$this->sAttr => $this->sClass]; // Return the current value in an array
237
    }
238
}
239