TargetAttribute   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Test Coverage

Coverage 95.83%

Importance

Changes 0
Metric Value
wmc 26
eloc 48
dl 0
loc 110
ccs 46
cts 48
cp 0.9583
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
F filter() 0 91 25
A __construct() 0 8 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Tokenizer\Attribute;
6
7
use Spiral\Attributes\AttributeReader;
8
use Spiral\Attributes\Factory;
9
use Spiral\Attributes\Internal\Instantiator\NamedArgumentsInstantiator;
10
use Spiral\Attributes\NamedArgumentConstructor;
11
use Spiral\Tokenizer\TokenizationListenerInterface;
12
13
/**
14
 * When applied to {@see TokenizationListenerInterface}, this attribute will instruct the tokenizer to listen for
15
 * classes that use attributes of the given class.
16
 */
17
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor]
18
final class TargetAttribute extends AbstractTarget
19
{
20
    /**
21
     * @param class-string $attribute
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
22
     * @param non-empty-string|null $scope Class locator scope
23
     * @param bool $namedArguments Whether to use named arguments when reading attributes
24
     * @param bool $scanParents Whether to scan parent classes/interfaces for attributes target of which is class
25
     */
26 397
    public function __construct(
27
        private readonly string $attribute,
28
        ?string $scope = null,
29
        private readonly bool $useAnnotations = false,
30
        private readonly bool $namedArguments = true,
31
        private readonly bool $scanParents = false,
32
    ) {
33 397
        parent::__construct($scope);
34
    }
35
36 396
    public function filter(array $classes): \Generator
37
    {
38 396
        $target = new \ReflectionClass($this->attribute);
39 396
        $attribute = $target->getAttributes(\Attribute::class)[0] ?? null;
40
41
        // If annotations are used, we need to use the annotation reader also
42
        // It will slow down the process a bit, but it will allow us to use annotations
43
        /** @psalm-suppress InternalClass */
44 396
        $reader = $this->useAnnotations
45 5
            ? (new Factory())->create()
46 391
            : new AttributeReader($this->namedArguments ? new NamedArgumentsInstantiator() : null);
47
48 396
        if ($attribute === null) {
49
            return;
50
        }
51
52 396
        $attribute = $attribute->newInstance();
53
54 396
        foreach ($classes as $class) {
55
            // If attribute is defined on class level and class has target attribute
56
            // then we can add it to the list of classes
57 396
            if ($attribute->flags & \Attribute::TARGET_CLASS) {
58 385
                if ($reader->firstClassMetadata($class, $target->getName())) {
59 364
                    yield $class->getName();
60 364
                    continue;
61
                }
62
63 381
                if ($this->scanParents) {
64
                    // Interfaces
65 2
                    foreach ($class->getInterfaces() as $interface) {
66 1
                        if ($reader->firstClassMetadata($interface, $target->getName())) {
67 1
                            yield $class->getName();
68 1
                            continue 2;
69
                        }
70
                    }
71
72
                    // Parents
73 1
                    $parent = $class->getParentClass();
74 1
                    while ($parent !== false) {
75 1
                        if ($reader->firstClassMetadata($parent, $target->getName())) {
76 1
                            yield $class->getName();
77 1
                            continue 2;
78
                        }
79
                        $parent = $parent->getParentClass();
80
                    }
81
                }
82
            }
83
84
            // If attribute is defined on method level and class methods has target attribute
85
            // then we can add it to the list of classes
86 390
            if ($attribute->flags & \Attribute::TARGET_METHOD) {
87 6
                foreach ($class->getMethods() as $method) {
88 6
                    if ($reader->firstFunctionMetadata($method, $target->getName())) {
89 6
                        yield $class->getName();
90 6
                        continue 2;
91
                    }
92
                }
93
            }
94
95
            // If attribute is defined on property level and class properties has target attribute
96
            // then we can add it to the list of classes
97 385
            if ($attribute->flags & \Attribute::TARGET_PROPERTY) {
98 1
                foreach ($class->getProperties() as $property) {
99 1
                    if ($reader->firstPropertyMetadata($property, $target->getName())) {
100 1
                        yield $class->getName();
101 1
                        continue 2;
102
                    }
103
                }
104
            }
105
106
107
            // If attribute is defined on constant level and class constants has target attribute
108
            // then we can add it to the list of classes
109 385
            if ($attribute->flags & \Attribute::TARGET_CLASS_CONSTANT) {
110 2
                foreach ($class->getReflectionConstants() as $constant) {
111 2
                    if ($reader->firstConstantMetadata($constant, $target->getName())) {
112 2
                        yield $class->getName();
113 2
                        continue 2;
114
                    }
115
                }
116
            }
117
118
119
            // If attribute is defined on method parameters level and class method parameter has target attribute
120
            // then we can add it to the list of classes
121 385
            if ($attribute->flags & \Attribute::TARGET_PARAMETER) {
122 2
                foreach ($class->getMethods() as $method) {
123 2
                    foreach ($method->getParameters() as $parameter) {
124 2
                        if ($reader->firstParameterMetadata($parameter, $target->getName())) {
125 2
                            yield $class->getName();
126 2
                            continue 3;
127
                        }
128
                    }
129
                }
130
            }
131
        }
132
    }
133
}
134