Passed
Push — master ( 65484c...fedbc6 )
by Satoshi
02:08
created

isMatchWithFQSEN()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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