Passed
Pull Request — master (#22)
by Sergei
14:23
created

PathMatcher::makePathPatterns()   B

Complexity

Conditions 9
Paths 50

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 19
nc 50
nop 1
dl 0
loc 35
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Files\PathMatch;
6
7
use Yiisoft\Strings\StringHelper;
8
9
final class PathMatcher implements PathMatcherInterface
10
{
11
    /**
12
     * @var PathPattern[]|null
13
     */
14
    private ?array $only = null;
15
16
    /**
17
     * @var PathPattern[]|null
18
     */
19
    private ?array $except = null;
20
21
    /**
22
     * @var callable[]|null
23
     */
24
    private ?array $callbacks = null;
25
26
    private bool $caseSensitive = false;
27
    private bool $matchFullPath = false;
28
    private bool $matchSlashesExactly = true;
29
    private bool $checkFilesystem = true;
30
31
    /**
32
     * Make string patterns case sensitive.
33
     * @return self
34
     */
35
    public function caseSensitive(): self
36
    {
37
        $new = clone $this;
38
        $new->caseSensitive = true;
39
        return $new;
40
    }
41
42
    /**
43
     * Match string patterns as full path, not just ending of path.
44
     * @return self
45
     */
46
    public function withFullPath(): self
47
    {
48
        $new = clone $this;
49
        $new->matchFullPath = true;
50
        return $new;
51
    }
52
53
    /**
54
     * Match `/` character with wildcards in string patterns.
55
     * @return self
56
     */
57
    public function withNotExactSlashes(): self
58
    {
59
        $new = clone $this;
60
        $new->matchSlashesExactly = false;
61
        return $new;
62
    }
63
64
    public function notCheckFilesystem(): self
65
    {
66
        $new = clone $this;
67
        $new->checkFilesystem = false;
68
        return $new;
69
    }
70
71
    /**
72
     * Set list of patterns that the files or directories should match.
73
     * @param string|PathPattern ...$patterns
74
     * @return self
75
     */
76
    public function only(...$patterns): self
77
    {
78
        $new = clone $this;
79
        $new->only = $this->makePathPatterns($patterns);
80
        return $new;
81
    }
82
83
    /**
84
     * Set list of patterns that the files or directories should not match.
85
     * @param string|PathPattern ...$patterns
86
     * @return self
87
     */
88
    public function except(...$patterns): self
89
    {
90
        $new = clone $this;
91
        $new->except = $this->makePathPatterns($patterns);
92
        return $new;
93
    }
94
95
    /**
96
     * Set list of PHP callback that is called for each path.
97
     *
98
     * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
99
     * The callback should return `true` if the passed path would match and `false` if it doesn't.
100
     *
101
     * @param callable ...$callbacks
102
     * @return self
103
     */
104
    public function callback(callable ...$callbacks): self
105
    {
106
        $new = clone $this;
107
        $new->callbacks = $callbacks;
108
        return $new;
109
    }
110
111
    /**
112
     * Checks if the passed path would match specified conditions.
113
     *
114
     * @param string $path The tested path.
115
     * @return bool|null Whether the path matches conditions or not.
116
     */
117
    public function match(string $path): ?bool
118
    {
119
        $path = str_replace('\\', '/', $path);
120
121
        if (!$this->matchOnly($path)) {
122
            return false;
123
        }
124
125
        if ($this->matchExcept($path)) {
126
            return false;
127
        }
128
129
        if ($this->callbacks !== null) {
130
            foreach ($this->callbacks as $callback) {
131
                if (!$callback($path)) {
132
                    return false;
133
                }
134
            }
135
        }
136
137
        return true;
138
    }
139
140
    /**
141
     * @param string $path
142
     * @return bool
143
     */
144
    private function matchOnly(string $path): bool
145
    {
146
        if ($this->only === null) {
147
            return true;
148
        }
149
150
        $hasFalse = false;
151
        $hasNull = false;
152
153
        foreach ($this->only as $pattern) {
154
            if ($pattern->match($path) === true) {
155
                return true;
156
            }
157
            if ($pattern->match($path) === false) {
158
                $hasFalse = true;
159
            }
160
            if ($pattern->match($path) === null) {
161
                $hasNull = true;
162
            }
163
        }
164
165
        if ($this->checkFilesystem) {
166
            if (is_file($path)) {
167
                return $hasFalse ? false : true;
168
            }
169
            if (is_dir($path)) {
170
                return $hasNull ? true : false;
171
            }
172
        }
173
174
        return false;
175
    }
176
177
    /**
178
     * @param string $path
179
     * @return bool
180
     */
181
    private function matchExcept(string $path): bool
182
    {
183
        if ($this->except === null) {
184
            return false;
185
        }
186
187
        foreach ($this->except as $pattern) {
188
            if ($pattern->match($path) === true) {
189
                return true;
190
            }
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * @param string[]|PathPattern[] $patterns
198
     * @return PathPattern[]
199
     */
200
    private function makePathPatterns(array $patterns): array
201
    {
202
        $pathPatterns = [];
203
        foreach ($patterns as $pattern) {
204
            if ($pattern instanceof PathPattern) {
205
                $pathPatterns[] = $pattern;
206
                continue;
207
            }
208
209
            $isDirectory = StringHelper::endsWith($pattern, '/');
210
            if ($isDirectory) {
211
                $pattern = StringHelper::substring($pattern, 0, -1);
212
            }
213
214
            $pathPattern = new PathPattern($pattern);
215
216
            if ($this->caseSensitive) {
217
                $pathPattern = $pathPattern->caseSensitive();
218
            }
219
220
            if ($this->matchFullPath) {
221
                $pathPattern = $pathPattern->withFullPath();
222
            }
223
224
            if (!$this->matchSlashesExactly) {
225
                $pathPattern = $pathPattern->withNotExactSlashes();
226
            }
227
228
            if ($this->checkFilesystem) {
229
                $pathPattern = $isDirectory ? $pathPattern->onlyDirectories() : $pathPattern->onlyFiles();
230
            }
231
232
            $pathPatterns[] = $pathPattern;
233
        }
234
        return $pathPatterns;
235
    }
236
}
237