ContainerAnnotation::saveValue()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 6
nop 2
dl 0
loc 17
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 Jaxon\Annotations\AnnotationReader;
18
use Jaxon\App\Metadata\Metadata;
19
use mindplay\annotations\AnnotationException;
20
use mindplay\annotations\AnnotationFile;
21
use mindplay\annotations\IAnnotationFileAware;
22
23
use function count;
24
use function is_string;
25
use function ltrim;
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
     * @var AnnotationReader
37
     */
38
    public static $xReader;
39
40
    /**
41
     * The annotation properties
42
     *
43
     * @var array
44
     */
45
    protected $properties = [];
46
47
    /**
48
     * The attribute name
49
     *
50
     * @var string
51
     */
52
    protected $sAttr = '';
53
54
    /**
55
     * The attribute class
56
     *
57
     * @var string
58
     */
59
    protected $sClass = '';
60
61
    /**
62
     * @var AnnotationFile
63
     */
64
    protected $xClassFile;
65
66
    /**
67
     * @param string $sAttr
68
     *
69
     * @return void
70
     */
71
    public function setAttr(string $sAttr): void
72
    {
73
        $this->sAttr = $sAttr;
74
    }
75
76
    /**
77
     * @inheritDoc
78
     */
79
    public function setAnnotationFile(AnnotationFile $file)
80
    {
81
        $this->xClassFile = $file;
82
    }
83
84
    /**
85
     * @inheritDoc
86
     */
87
    public static function parseAnnotation($value)
88
    {
89
        // We need to know which type of class member the annotation is attached to (attribute,
90
        // method or class), which is possible only when calling the getValue() method.
91
        // So we just return raw data in a custom format here.
92
        return ['__value__' => $value];
93
    }
94
95
    /**
96
     * @inheritDoc
97
     */
98
    public function initAnnotation(array $properties)
99
    {
100
        // We need to know which type of class member the annotation is attached to (attribute,
101
        // method or class), which is possible only when calling the getValue() method.
102
        // So we just save the properties locally here.
103
        $this->properties = $properties;
104
    }
105
106
    /**
107
     * @param string $sClassName
108
     *
109
     * @return string
110
     */
111
    private function getFullClassName(string $sClassName): string
112
    {
113
        return ltrim($this->xClassFile->resolveType($sClassName), '\\');
114
    }
115
116
    /**
117
     * @return void
118
     * @throws AnnotationException
119
     */
120
    private function parseValue()
121
    {
122
        $value = $this->properties['__value__'];
123
        $aParams = preg_split('/[\s]+/', $value, 3);
124
        $nParamCount = count($aParams);
125
        if($nParamCount === 1)
126
        {
127
            // For a property, the only parameter is the class. Otherwise, it is the attribute.
128
            if(self::$xReader->annotationIsOnProperty())
129
            {
130
                if(substr($aParams[0], 0, 1) === '$')
131
                {
132
                    throw new AnnotationException('The only property of the @di annotation must be a class name');
133
                }
134
                $this->sClass = $this->getFullClassName($aParams[0]);
135
                return;
136
            }
137
            if(substr($aParams[0], 0, 1) !== '$')
138
            {
139
                throw new AnnotationException('The only property of the @di annotation must be a var name');
140
            }
141
            $this->sAttr = substr($aParams[0], 1);
142
            return;
143
        }
144
        if($nParamCount === 2)
145
        {
146
            // For a property, having 2 parameters is not allowed.
147
            if(self::$xReader->annotationIsOnProperty())
148
            {
149
                throw new AnnotationException('The @di annotation accepts only one property on a class attribute');
150
            }
151
152
            if(substr($aParams[0], 0, 1) !== '$')
153
            {
154
                throw new AnnotationException('The only property of the @di annotation must be a var name');
155
            }
156
            if(substr($aParams[1], 0, 1) === '$')
157
            {
158
                throw new AnnotationException('The first property of the @di annotation must be a class name');
159
            }
160
            $this->sAttr = substr($aParams[0], 1);
161
            $this->sClass = $this->getFullClassName($aParams[1]);
162
            return;
163
        }
164
165
        throw new AnnotationException('The @di annotation only accepts one or two properties');
166
    }
167
168
    /**
169
     * @return bool
170
     */
171
    private function checkPropertiesNames(): bool
172
    {
173
        return match(count($this->properties))
174
        {
175
            0 => true,
176
            1 => isset($this->properties['attr']) || isset($this->properties['class']),
177
            2 => isset($this->properties['attr']) && isset($this->properties['class']),
178
            default => false,
179
        };
180
    }
181
182
    /**
183
     * @return void
184
     * @throws AnnotationException
185
     */
186
    private function parseProperties()
187
    {
188
        if(!$this->checkPropertiesNames())
189
        {
190
            throw new AnnotationException('The @di annotation accepts only "attr" or "class" as properties');
191
        }
192
193
        if(isset($this->properties['attr']))
194
        {
195
            if(self::$xReader->annotationIsOnProperty())
196
            {
197
                throw new AnnotationException('The @di annotation does not allow the "attr" property on class attributes');
198
            }
199
            if(!is_string($this->properties['attr']))
200
            {
201
                throw new AnnotationException('The @di annotation requires a property "attr" of type string');
202
            }
203
            $this->sAttr = $this->properties['attr'];
204
        }
205
        if(isset($this->properties['class']))
206
        {
207
            if(!is_string($this->properties['class']))
208
            {
209
                throw new AnnotationException('The @di annotation requires a property "class" of type string');
210
            }
211
            $this->sClass = $this->getFullClassName($this->properties['class']);
212
        }
213
    }
214
215
    /**
216
     * @inheritDoc
217
     */
218
    public function saveValue(Metadata $xMetadata, string $sMethod = '*'): void
219
    {
220
        isset($this->properties['__value__']) ? $this->parseValue() : $this->parseProperties();
221
222
        // The type in the @di annotations can be set from the values in the @var annotations
223
        $aPropTypes = self::$xReader->getPropTypes();
224
        if($this->sClass === '')
225
        {
226
            if(!isset($aPropTypes[$this->sAttr]))
227
            {
228
                throw new AnnotationException('No class defined for @di on attribute "' .
229
                    $this->sAttr . '".');
230
            }
231
            $this->sClass = ltrim($aPropTypes[$this->sAttr], '\\');
232
        }
233
234
        $xMetadata->container($sMethod)->addValue($this->sAttr, $this->sClass);
235
    }
236
}
237