Passed
Push — master ( 5beeba...c57dde )
by Satoshi
02:49
created

ClassNameMatcher::formatPattern()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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