Passed
Push — master ( d3cc39...f2a84f )
by Melech
03:59
created

Attributes::getInstances()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 45
rs 8.8657
c 0
b 0
f 0
cc 6
nc 5
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Attribute;
15
16
use Closure;
17
use ReflectionAttribute;
18
use ReflectionClass;
19
use ReflectionClassConstant;
20
use ReflectionException;
21
use ReflectionFunction;
22
use ReflectionMethod;
23
use ReflectionParameter;
24
use ReflectionProperty;
25
use Reflector;
26
use Valkyrja\Attribute\Contract\Attributes as Contract;
27
use Valkyrja\Dispatcher\Data\CallableDispatch;
28
use Valkyrja\Dispatcher\Data\ClassDispatch;
29
use Valkyrja\Dispatcher\Data\ConstantDispatch;
30
use Valkyrja\Dispatcher\Data\MethodDispatch;
31
use Valkyrja\Dispatcher\Data\PropertyDispatch;
32
use Valkyrja\Reflection\Contract\Reflection;
33
34
/**
35
 * Class Attributes.
36
 *
37
 * @author Melech Mizrachi
38
 */
39
class Attributes implements Contract
40
{
41
    /**
42
     * Default flags for the getAttributes() method.
43
     *
44
     * @var int
45
     */
46
    protected static int $defaultFlags = ReflectionAttribute::IS_INSTANCEOF;
47
48
    /**
49
     * Attributes constructor.
50
     */
51
    public function __construct(
52
        protected Reflection $reflection = new \Valkyrja\Reflection\Reflection(),
53
    ) {
54
    }
55
56
    /**
57
     * @inheritDoc
58
     *
59
     * @throws ReflectionException
60
     */
61
    public function forClass(string $class, string|null $attribute = null, int|null $flags = null): array
62
    {
63
        $reflection = $this->reflection->forClass($class);
64
65
        return $this->getInstances(
66
            $reflection,
67
            ...$reflection->getAttributes($attribute, $flags ?? static::$defaultFlags)
68
        );
69
    }
70
71
    /**
72
     * @inheritDoc
73
     *
74
     * @throws ReflectionException
75
     */
76
    public function forClassMembers(string $class, string|null $attribute = null, int|null $flags = null): array
77
    {
78
        return [
79
            ...$this->forConstants($class, $attribute, $flags),
80
            ...$this->forProperties($class, $attribute, $flags),
81
            ...$this->forMethods($class, $attribute, $flags),
82
        ];
83
    }
84
85
    /**
86
     * @inheritDoc
87
     *
88
     * @throws ReflectionException
89
     */
90
    public function forClassAndMembers(string $class, string|null $attribute = null, int|null $flags = null): array
91
    {
92
        return [
93
            ...$this->forClass($class, $attribute, $flags),
94
            ...$this->forClassMembers($class, $attribute, $flags),
95
        ];
96
    }
97
98
    /**
99
     * @inheritDoc
100
     *
101
     * @throws ReflectionException
102
     */
103
    public function forConstant(
104
        string $class,
105
        string $constant,
106
        string|null $attribute = null,
107
        int|null $flags = null
108
    ): array {
109
        return $this->forClassMember($attribute, $flags, $this->reflection->forClassConstant($class, $constant));
110
    }
111
112
    /**
113
     * @inheritDoc
114
     *
115
     * @throws ReflectionException
116
     */
117
    public function forConstants(string $class, string|null $attribute = null, int|null $flags = null): array
118
    {
119
        return $this->forClassMember(
120
            $attribute,
121
            $flags,
122
            ...$this->reflection->forClass($class)->getReflectionConstants()
123
        );
124
    }
125
126
    /**
127
     * @inheritDoc
128
     *
129
     * @throws ReflectionException
130
     */
131
    public function forProperty(
132
        string $class,
133
        string $property,
134
        string|null $attribute = null,
135
        int|null $flags = null
136
    ): array {
137
        return $this->forClassMember($attribute, $flags, $this->reflection->forClassProperty($class, $property));
138
    }
139
140
    /**
141
     * @inheritDoc
142
     *
143
     * @throws ReflectionException
144
     */
145
    public function forProperties(string $class, string|null $attribute = null, int|null $flags = null): array
146
    {
147
        return $this->forClassMember(
148
            $attribute,
149
            $flags,
150
            ...$this->reflection->forClass($class)->getProperties()
151
        );
152
    }
153
154
    /**
155
     * @inheritDoc
156
     *
157
     * @throws ReflectionException
158
     */
159
    public function forMethod(
160
        string $class,
161
        string $method,
162
        string|null $attribute = null,
163
        int|null $flags = null
164
    ): array {
165
        return $this->forClassMember($attribute, $flags, $this->reflection->forClassMethod($class, $method));
166
    }
167
168
    /**
169
     * @inheritDoc
170
     *
171
     * @throws ReflectionException
172
     */
173
    public function forMethodParameters(
174
        string $class,
175
        string $method,
176
        string|null $attribute = null,
177
        int|null $flags = null
178
    ): array {
179
        return $this->forParameter(
180
            $attribute,
181
            $flags,
182
            ...$this->reflection->forClassMethod($class, $method)->getParameters()
183
        );
184
    }
185
186
    /**
187
     * @inheritDoc
188
     *
189
     * @throws ReflectionException
190
     */
191
    public function forMethodParameter(
192
        string $class,
193
        string $method,
194
        string $parameter,
195
        string|null $attribute = null,
196
        int|null $flags = null
197
    ): array {
198
        $parameters = $this->reflection->forClassMethod($class, $method)->getParameters();
199
200
        foreach ($parameters as $reflectionParameter) {
201
            if ($reflectionParameter->getName() === $parameter) {
202
                return $this->forParameter(
203
                    $attribute,
204
                    $flags,
205
                    $reflectionParameter
206
                );
207
            }
208
        }
209
210
        return [];
211
    }
212
213
    /**
214
     * @inheritDoc
215
     *
216
     * @throws ReflectionException
217
     */
218
    public function forMethods(string $class, string|null $attribute = null, int|null $flags = null): array
219
    {
220
        return $this->forClassMember(
221
            $attribute,
222
            $flags,
223
            ...$this->reflection->forClass($class)->getMethods()
224
        );
225
    }
226
227
    /**
228
     * @inheritDoc
229
     *
230
     * @throws ReflectionException
231
     */
232
    public function forFunction(string $function, string|null $attribute = null, int|null $flags = null): array
233
    {
234
        $reflection = $this->reflection->forFunction($function);
235
236
        return $this->getInstances(
237
            $reflection,
238
            ...$reflection->getAttributes($attribute, $flags ?? static::$defaultFlags)
239
        );
240
    }
241
242
    /**
243
     * @inheritDoc
244
     *
245
     * @throws ReflectionException
246
     */
247
    public function forFunctionParameters(string $function, string|null $attribute = null, int|null $flags = null): array
248
    {
249
        return $this->forParameter(
250
            $attribute,
251
            $flags,
252
            ...$this->reflection->forFunction($function)->getParameters()
253
        );
254
    }
255
256
    /**
257
     * @inheritDoc
258
     *
259
     * @throws ReflectionException
260
     */
261
    public function forClosure(Closure $closure, string|null $attribute = null, int|null $flags = null): array
262
    {
263
        $reflection = $this->reflection->forClosure($closure);
264
265
        return $this->getInstances(
266
            $reflection,
267
            ...$reflection->getAttributes($attribute, $flags ?? static::$defaultFlags)
268
        );
269
    }
270
271
    /**
272
     * @inheritDoc
273
     *
274
     * @throws ReflectionException
275
     */
276
    public function forClosureParameters(Closure $closure, string|null $attribute = null, int|null $flags = null): array
277
    {
278
        return $this->forParameter(
279
            $attribute,
280
            $flags,
281
            ...$this->reflection->forClosure($closure)->getParameters()
282
        );
283
    }
284
285
    /**
286
     * Get a class' members' attributes.
287
     *
288
     * @param class-string|null                                           $attribute  [optional] The attribute to return
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
289
     * @param ReflectionClassConstant|ReflectionMethod|ReflectionProperty ...$members [optional] The members
290
     *
291
     * @return object[]
292
     */
293
    protected function forClassMember(
294
        string|null $attribute = null,
295
        int|null $flags = null,
296
        ReflectionClassConstant|ReflectionMethod|ReflectionProperty ...$members
297
    ): array {
298
        $instances = [];
299
300
        foreach ($members as $member) {
301
            /** @psalm-suppress PossiblyInvalidArgument $member is absolutely a Reflector */
302
            $instances = [
303
                ...$instances,
304
                ...$this->getInstances(
305
                    $member,
306
                    ...$member->getAttributes($attribute, $flags ?? static::$defaultFlags)
307
                ),
308
            ];
309
        }
310
311
        return $instances;
312
    }
313
314
    /**
315
     * Get a parameter' attributes.
316
     *
317
     * @param class-string|null   $attribute     [optional] The attribute to return
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
318
     * @param ReflectionParameter ...$parameters [optional] The parameters
319
     *
320
     * @return object[]
321
     */
322
    protected function forParameter(
323
        string|null $attribute = null,
324
        int|null $flags = null,
325
        ReflectionParameter ...$parameters
326
    ): array {
327
        $instances = [];
328
329
        foreach ($parameters as $parameter) {
330
            $instances = [
331
                ...$instances,
332
                ...$this->getInstances(
333
                    $parameter,
334
                    ...$parameter->getAttributes($attribute, $flags ?? static::$defaultFlags)
335
                ),
336
            ];
337
        }
338
339
        return $instances;
340
    }
341
342
    /**
343
     * Set the base annotation model values.
344
     *
345
     * @param ReflectionAttribute ...$reflectionAttributes The reflection attributes
346
     *
347
     * @return object[]
348
     */
349
    protected function getInstances(Reflector $reflection, ReflectionAttribute ...$reflectionAttributes): array
350
    {
351
        $instances = [];
352
353
        foreach ($reflectionAttributes as $reflectionAttribute) {
354
            $instance = $reflectionAttribute->newInstance();
355
356
            if (method_exists($instance, 'setReflection')) {
357
                $instance->setReflection($reflection);
358
            }
359
360
            if (method_exists($instance, 'withDispatch') && method_exists($instance, 'getDispatch')) {
361
                /** @var object $instance */
362
                $instance = $instance->withDispatch(
363
                    match (true) {
364
                        $reflection instanceof ReflectionMethod        => new MethodDispatch(
365
                            class: $reflection->getDeclaringClass()->getName(),
0 ignored issues
show
Bug introduced by
The method getDeclaringClass() does not exist on Reflector. It seems like you code against a sub-type of Reflector such as ReflectionProperty or ReflectionClassConstant or ReflectionParameter or ReflectionMethod. ( Ignorable by Annotation )

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

365
                            class: $reflection->/** @scrutinizer ignore-call */ getDeclaringClass()->getName(),
Loading history...
366
                            method: $reflection->getName(),
367
                            isStatic: $reflection->isStatic()
0 ignored issues
show
Bug introduced by
The method isStatic() does not exist on Reflector. It seems like you code against a sub-type of Reflector such as ReflectionProperty or ReflectionMethod. ( Ignorable by Annotation )

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

367
                            isStatic: $reflection->/** @scrutinizer ignore-call */ isStatic()
Loading history...
368
                        ),
369
                        $reflection instanceof ReflectionProperty      => new PropertyDispatch(
370
                            class: $reflection->getDeclaringClass()->getName(),
371
                            property: $reflection->getName(),
372
                            isStatic: $reflection->isStatic()
373
                        ),
374
                        $reflection instanceof ReflectionClass         => new ClassDispatch(
375
                            class: $reflection->getName(),
376
                        ),
377
                        $reflection instanceof ReflectionClassConstant => new ConstantDispatch(
378
                            constant: $reflection->getName(),
379
                            class: $reflection->getDeclaringClass()->getName()
380
                        ),
381
                        $reflection instanceof ReflectionFunction
382
                        && ! $reflection->isClosure()                  => new CallableDispatch(
383
                            callable: $reflection->getName()
384
                        ),
385
                        default                                        => $instance->getDispatch(),
386
                    }
387
                );
388
            }
389
390
            $instances[] = $instance;
391
        }
392
393
        return $instances;
394
    }
395
}
396