AttributeReader::getMethodAttrs()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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