Passed
Push — main ( f5b6ef...c06968 )
by Thierry
16:33 queued 05:42
created

AttributeReader::getMethodAttrs()   A

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 &&
205
            in_array($xParentClass->getName(), $this->aJaxonClasses) ?
206
            $xParentClass : null;
207
    }
208
209
    /**
210
     * @inheritDoc
211
     * @throws SetupException
212
     */
213
    public function getAttributes(InputData $xInput): ?Metadata
214
    {
215
        try
216
        {
217
            $this->xMetadata = new Metadata();
0 ignored issues
show
Bug introduced by
The call to Jaxon\App\Metadata\Metadata::__construct() has too few arguments starting with bIsExcluded. ( Ignorable by Annotation )

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

217
            $this->xMetadata = /** @scrutinizer ignore-call */ new Metadata();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
218
219
            $xClass = $xInput->getReflectionClass();
220
            // First check if the class is exluded.
221
            $this->getClassExcludeAttr($xClass);
222
            if($this->xMetadata->isExcluded())
223
            {
224
                return $this->xMetadata;
225
            }
226
227
            $aClasses = [$xClass];
228
            while(($xClass = $this->getParentClass($xClass)) !== null)
229
            {
230
                $aClasses[] = $xClass;
231
            }
232
            $aClasses = array_reverse($aClasses);
233
234
            foreach($aClasses as $xClass)
235
            {
236
                // Processing class attributes
237
                $this->getClassAttrs($xClass);
238
239
                // Processing properties attributes
240
                foreach($xInput->getProperties() as $sProperty)
241
                {
242
                    $this->getPropertyAttrs($xClass, $sProperty);
243
                }
244
245
                // Processing methods attributes
246
                foreach($xInput->getMethods() as $sMethod)
247
                {
248
                    $this->getMethodAttrs($xClass, $sMethod);
249
                }
250
            }
251
252
            return $this->xMetadata;
253
        }
254
        catch(Exception|Error $e)
255
        {
256
            throw new SetupException($e->getMessage());
257
        }
258
    }
259
}
260