Passed
Push — master ( 056350...65484c )
by Satoshi
05:02
created

StructuralElementPatternMatcher::isMatch()   B

Complexity

Conditions 8
Paths 21

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 16
nc 21
nop 1
dl 0
loc 29
rs 8.4444
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace DependencyAnalyzer\DependencyGraph;
5
6
use DependencyAnalyzer\DependencyGraph\FullyQualifiedStructuralElementName as FQSEN;
7
use DependencyAnalyzer\DependencyGraph\StructuralElementPatternMatcher\FQSENMatcher;
8
use DependencyAnalyzer\DependencyGraph\StructuralElementPatternMatcher\Matchable;
9
use DependencyAnalyzer\DependencyGraph\StructuralElementPatternMatcher\PhpNativeClassesMatcher;
10
use DependencyAnalyzer\Exceptions\InvalidFullyQualifiedStructureElementNameException;
11
use DependencyAnalyzer\Exceptions\InvalidQualifiedNamePatternException;
12
13
class StructuralElementPatternMatcher
14
{
15
    const PHP_NATIVE_CLASSES = '@php_native';
16
    protected static $nativeClasses = [];
17
18
    /**
19
     * @var Matchable[]
20
     */
21
    protected $patterns = [];
22
23
    /**
24
     * @var Matchable[]
25
     */
26
    protected $excludePatterns = [];
27
28
    public function __construct(array $patterns)
29
    {
30
        foreach ($patterns as $pattern) {
31
            if (!$this->verifyPattern($pattern)) {
32
                throw new InvalidQualifiedNamePatternException($pattern);
33
            }
34
35
            $this->addPattern($pattern);
36
        }
37
    }
38
39
    public function addExcludePatterns(array $patterns): self
40
    {
41
        foreach ($patterns as $pattern) {
42
            if (!$this->verifyPattern($pattern)) {
43
                throw new InvalidQualifiedNamePatternException($pattern);
44
            }
45
46
            $this->excludePatterns[] = $this->createPattern($pattern);
47
        }
48
49
        return $this;
50
    }
51
52
    protected function verifyPattern(string $pattern): bool
53
    {
54
        if ($this->isMagicWordPattern($pattern)) {
55
            return true;
56
        }
57
58
        try {
59
            $this->isExcludePattern($pattern) ? FQSEN::createFromString(substr($pattern, 1)) : FQSEN::createFromString($pattern);
60
        } catch (InvalidFullyQualifiedStructureElementNameException $e) {
61
            return false;
62
        }
63
64
        return true;
65
    }
66
67
    protected function addPattern(string $pattern): void
68
    {
69
        if ($this->isExcludePattern($pattern)) {
70
            $this->excludePatterns[] = $this->createPattern(substr($pattern, 1));
71
        } else {
72
            $this->patterns[] = $this->createPattern($pattern);
73
        }
74
    }
75
76
    protected function createPattern(string $pattern): Matchable
77
    {
78
        if ($this->isMagicWordPattern($pattern)) {
79
            return new PhpNativeClassesMatcher(self::$nativeClasses);
80
        }
81
82
        return new FQSENMatcher(FQSEN::createFromString($pattern));
83
    }
84
85
    protected function isExcludePattern(string $pattern): bool
86
    {
87
        return preg_match('/^!/', $pattern) === 1;
88
    }
89
90
    protected function isMagicWordPattern(string $pattern): bool
91
    {
92
        return in_array($pattern, [
93
            self::PHP_NATIVE_CLASSES,
94
            '!' . self::PHP_NATIVE_CLASSES,
95
        ]);
96
    }
97
98
    public function isMatch(string $className): bool
99
    {
100
        try {
101
            if (!substr($className, 0, 1) !== '\\') {
0 ignored issues
show
introduced by
The condition ! substr($className, 0, 1) !== '\' is always true.
Loading history...
102
                $className = '\\' . $className;
103
            }
104
105
            $target = FQSEN::createFromString($className);
106
        } catch (InvalidFullyQualifiedStructureElementNameException $e) {
107
            return false;
108
        }
109
110
        foreach ($this->excludePatterns as $excludePattern) {
111
            if ($excludePattern->isMatch($target)) {
112
                return false;
113
            }
114
        }
115
116
        if (count($this->patterns) === 0) {
117
            return true;
118
        } else {
119
            foreach ($this->patterns as $pattern) {
120
                if ($pattern->isMatch($target)) {
121
                    return true;
122
                }
123
            }
124
        }
125
126
        return false;
127
    }
128
129
    public static function setPhpNativeClasses(array $nativeClasses): void
130
    {
131
        self::$nativeClasses = array_map(function (string $className) {
132
            return substr($className, 0, 1) !== '\\' ? "\\{$className}" : $className;
133
        }, $nativeClasses);
134
    }
135
136
    public function toArray(): array
137
    {
138
        return [
139
            'include' => array_map(function (Matchable $fqsen) {
140
                return $fqsen->getPattern();
141
            }, $this->patterns),
142
            'exclude' => array_map(function (Matchable $fqsen) {
143
                return $fqsen->getPattern();
144
            }, $this->excludePatterns)
145
        ];
146
    }
147
}
148