Passed
Push — main ( c6b105...31af8b )
by Thierry
07:02
created

AttributeReader::getPropertyAttrs()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 2
dl 0
loc 20
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * AttributeReader.php
5
 *
6
 * Jaxon attribute reader.
7
 *
8
 * @package jaxon-attributes
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2024 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-core
13
 */
14
15
namespace Jaxon\Attributes;
16
17
use Jaxon\App;
18
use Jaxon\App\Metadata\InputDataInterface;
19
use Jaxon\App\Metadata\Metadata;
20
use Jaxon\App\Metadata\MetadataReaderInterface;
21
use Jaxon\Attributes\Attribute\AbstractAttribute;
22
use Jaxon\Attributes\Attribute\Inject as InjectAttribute;
23
use Jaxon\Attributes\Attribute\Exclude as ExcludeAttribute;
24
use Jaxon\Exception\SetupException;
25
use Error;
26
use Exception;
27
use ReflectionAttribute;
28
use ReflectionClass;
29
use ReflectionNamedType;
30
use ReflectionProperty;
31
32
use function array_filter;
33
use function array_reverse;
34
use function count;
35
use function in_array;
36
use function is_a;
37
38
class AttributeReader implements MetadataReaderInterface
39
{
40
    /**
41
     * @var Metadata
42
     */
43
    protected Metadata $xMetadata;
44
45
    /**
46
     * @var array
47
     */
48
    protected array $aTypes;
49
50
    /**
51
     * @var array
52
     */
53
    private $aJaxonClasses = [
54
        App\Component::class,
55
        App\NodeComponent::class,
56
        App\FuncComponent::class,
57
        App\PageComponent::class,
58
        App\CallableClass::class,
59
    ];
60
61
    /**
62
     * Read the property types
63
     *
64
     * @param ReflectionClass $xClass
65
     *
66
     * @return void
67
     */
68
    private function readTypes(ReflectionClass $xClass)
69
    {
70
        $sClass = $xClass->getName();
71
        if(isset($this->aTypes[$sClass]))
72
        {
73
            return;
74
        }
75
76
        $this->aTypes[$sClass] = [];
77
        $nFilter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED;
78
        $aProperties = $xClass->getProperties($nFilter);
79
        foreach($aProperties as $xReflectionProperty)
80
        {
81
            $xType = $xReflectionProperty->getType();
82
            $sType = $xType?->getName() ?? '';
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

82
            $sType = $xType?->/** @scrutinizer ignore-call */ getName() ?? '';
Loading history...
83
            // Check that the property has a valid type defined
84
            if($sType !== '' && is_a($xType, ReflectionNamedType::class))
85
            {
86
                $this->aTypes[$sClass][$xReflectionProperty->getName()] = $sType;
87
            }
88
        }
89
    }
90
91
    /**
92
     * @param AbstractAttribute $xAttribute
93
     * @param ReflectionClass $xClass
94
     * @param ReflectionAttribute $xReflectionAttribute
95
     *
96
     * @return void
97
     */
98
    private function initAttribute(AbstractAttribute $xAttribute,
99
        ReflectionClass $xClass, ReflectionAttribute $xReflectionAttribute): void
100
    {
101
        if(is_a($xAttribute, InjectAttribute::class))
102
        {
103
            $this->readTypes($xClass);
104
105
            $xAttribute->setTarget($xReflectionAttribute->getTarget());
106
            $xAttribute->setNamespace($xClass->getNamespaceName());
107
            $xAttribute->setTypes($this->aTypes[$xClass->getName()]);
108
        }
109
    }
110
111
    /**
112
     * @param ReflectionClass $xClass
113
     *
114
     * @return void
115
     */
116
    private function getClassExcludeAttr(ReflectionClass $xClass): void
117
    {
118
        $aClassAttributes = $xClass->getAttributes();
119
        $aAttributes = array_filter($aClassAttributes, fn($xAttribute) =>
120
            is_a($xAttribute->getName(), ExcludeAttribute::class, true));
121
        foreach($aAttributes as $xReflectionAttribute)
122
        {
123
            $xReflectionAttribute->newInstance()->saveValue($this->xMetadata);
124
        }
125
    }
126
127
    /**
128
     * @param ReflectionClass $xClass
129
     *
130
     * @return void
131
     */
132
    private function getClassAttrs(ReflectionClass $xClass): void
133
    {
134
        $aClassAttributes = $xClass->getAttributes();
135
        $aAttributes = array_filter($aClassAttributes, fn($xAttribute) =>
136
            is_a($xAttribute->getName(), AbstractAttribute::class, true) &&
137
            !is_a($xAttribute->getName(), ExcludeAttribute::class, true));
138
        foreach($aAttributes as $xReflectionAttribute)
139
        {
140
            $xAttribute = $xReflectionAttribute->newInstance();
141
            $this->initAttribute($xAttribute, $xClass, $xReflectionAttribute);
142
143
            $xAttribute->saveValue($this->xMetadata);
144
        }
145
    }
146
147
    /**
148
     * @param ReflectionClass $xClass
149
     * @param string $sProperty
150
     *
151
     * @return void
152
     * @throws SetupException
153
     */
154
    private function getPropertyAttrs(ReflectionClass $xClass, string $sProperty): void
155
    {
156
        // Only Inject attributes are allowed on properties
157
        $aAttributes = !$xClass->hasProperty($sProperty) ? [] :
158
            $xClass->getProperty($sProperty)->getAttributes();
159
        $aAttributes = array_filter($aAttributes, fn($xAttribute) =>
160
            is_a($xAttribute->getName(), InjectAttribute::class, true));
161
        if(count($aAttributes) > 1)
162
        {
163
            throw new SetupException('Only one Inject attribute is allowed on a property');
164
        }
165
166
        foreach($aAttributes as $xReflectionAttribute)
167
        {
168
            /** @var InjectAttribute */
169
            $xAttribute = $xReflectionAttribute->newInstance();
170
            $xAttribute->setAttr($sProperty);
171
            $this->initAttribute($xAttribute, $xClass, $xReflectionAttribute);
172
173
            $xAttribute->saveValue($this->xMetadata);
174
        }
175
    }
176
177
    /**
178
     * @param ReflectionClass $xClass
179
     * @param string $sMethod
180
     *
181
     * @return void
182
     */
183
    private function getMethodAttrs(ReflectionClass $xClass, string $sMethod): void
184
    {
185
        $aAttributes = !$xClass->hasMethod($sMethod) ? [] :
186
            $xClass->getMethod($sMethod)->getAttributes();
187
        $aAttributes = array_filter($aAttributes, fn($xAttribute) =>
188
            is_a($xAttribute->getName(), AbstractAttribute::class, true));
189
        foreach($aAttributes as $xReflectionAttribute)
190
        {
191
            $xAttribute = $xReflectionAttribute->newInstance();
192
            $this->initAttribute($xAttribute, $xClass, $xReflectionAttribute);
193
194
            $xAttribute->saveValue($this->xMetadata, $sMethod);
195
        }
196
    }
197
198
    /**
199
     * @param ReflectionClass $xClass
200
     *
201
     * @return ReflectionClass|null
202
     */
203
    private function getParentClass(ReflectionClass $xClass): ReflectionClass|null
204
    {
205
        $xParentClass = $xClass->getParentClass();
206
        return $xParentClass !== false &&
207
            in_array($xParentClass->getName(), $this->aJaxonClasses) ?
208
            $xParentClass : null;
209
    }
210
211
    /**
212
     * @inheritDoc
213
     * @throws SetupException
214
     */
215
    public function getAttributes(InputDataInterface $xInput): ?Metadata
216
    {
217
        try
218
        {
219
            $this->xMetadata = new Metadata();
220
221
            $xClass = $xInput->getReflectionClass();
222
            // First check if the class is exluded.
223
            $this->getClassExcludeAttr($xClass);
224
            if($this->xMetadata->isExcluded())
225
            {
226
                return $this->xMetadata;
227
            }
228
229
            $aClasses = [$xClass];
230
            while(($xClass = $this->getParentClass($xClass)) !== null)
231
            {
232
                $aClasses[] = $xClass;
233
            }
234
            $aClasses = array_reverse($aClasses);
235
236
            foreach($aClasses as $xClass)
237
            {
238
                // Processing class attributes
239
                $this->getClassAttrs($xClass);
240
241
                // Processing properties attributes
242
                foreach($xInput->getProperties() as $sProperty)
243
                {
244
                    $this->getPropertyAttrs($xClass, $sProperty);
245
                }
246
247
                // Processing methods attributes
248
                foreach($xInput->getMethods() as $sMethod)
249
                {
250
                    $this->getMethodAttrs($xClass, $sMethod);
251
                }
252
            }
253
254
            return $this->xMetadata;
255
        }
256
        catch(Exception|Error $e)
257
        {
258
            throw new SetupException($e->getMessage());
259
        }
260
    }
261
}
262