1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Yiisoft\Strings; |
||
6 | |||
7 | use function implode; |
||
8 | use function preg_match; |
||
9 | use function preg_quote; |
||
10 | use function preg_replace; |
||
11 | use function strtr; |
||
12 | |||
13 | /** |
||
14 | * A wildcard pattern to match strings against. |
||
15 | * |
||
16 | * - `\` escapes other special characters if usage of escape character is not turned off. |
||
17 | * - `*` matches any string including the empty string except it has a delimiter (`/` and `\` by default). |
||
18 | * - `**` matches any string including the empty string and delimiters. |
||
19 | * - `?` matches any single character. |
||
20 | * - `[seq]` matches any character in seq. |
||
21 | * - `[a-z]` matches any character from a to z. |
||
22 | * - `[!seq]` matches any character not in seq. |
||
23 | * - `[[:alnum:]]` matches POSIX style character classes, |
||
24 | * see {@see https://www.php.net/manual/en/regexp.reference.character-classes.php}. |
||
25 | */ |
||
26 | final class WildcardPattern |
||
27 | { |
||
28 | private bool $ignoreCase = false; |
||
29 | |||
30 | /** |
||
31 | * @psalm-var non-empty-string|null |
||
32 | */ |
||
33 | private ?string $patternPrepared = null; |
||
34 | |||
35 | /** |
||
36 | * @param string $pattern The shell wildcard pattern to match against. |
||
37 | * @param string[] $delimiters Delimiters to consider for "*" (`/` and `\` by default). |
||
38 | */ |
||
39 | 59 | public function __construct( |
|
40 | private string $pattern, |
||
41 | private array $delimiters = ['\\\\', '/'], |
||
42 | ) { |
||
43 | 59 | } |
|
44 | |||
45 | /** |
||
46 | * Checks if the passed string would match the given shell wildcard pattern. |
||
47 | * |
||
48 | * @param string $string The tested string. |
||
49 | * |
||
50 | * @return bool Whether the string matches pattern or not. |
||
51 | */ |
||
52 | 58 | public function match(string $string): bool |
|
53 | { |
||
54 | 58 | if ($this->pattern === '**') { |
|
55 | 1 | return true; |
|
56 | } |
||
57 | |||
58 | 57 | return preg_match($this->getPatternPrepared(), $string) === 1; |
|
59 | } |
||
60 | |||
61 | /** |
||
62 | * Make pattern case insensitive. |
||
63 | */ |
||
64 | 3 | public function ignoreCase(bool $flag = true): self |
|
65 | { |
||
66 | 3 | $new = clone $this; |
|
67 | 3 | $new->patternPrepared = null; |
|
68 | 3 | $new->ignoreCase = $flag; |
|
69 | |||
70 | 3 | return $new; |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * Returns whether the pattern contains a dynamic part i.e. |
||
75 | * has unescaped "*", "{", "?", or "[" character. |
||
76 | * |
||
77 | * @param string $pattern The pattern to check. |
||
78 | * |
||
79 | * @return bool Whether the pattern contains a dynamic part. |
||
80 | */ |
||
81 | 5 | public static function isDynamic(string $pattern): bool |
|
82 | { |
||
83 | 5 | $pattern = preg_replace('/\\\\./', '', $pattern); |
|
84 | 5 | return preg_match('/[*{?\[]/', $pattern) === 1; |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * Escapes pattern characters in a string. |
||
89 | * |
||
90 | * @param string $string Source string. |
||
91 | * |
||
92 | * @return string String with pattern characters escaped. |
||
93 | */ |
||
94 | 2 | public static function quote(string $string): string |
|
95 | { |
||
96 | 2 | return preg_replace('#([\\\\?*\\[\\]])#', '\\\\$1', $string); |
|
97 | } |
||
98 | |||
99 | /** |
||
100 | * @return non-empty-string |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
101 | */ |
||
102 | 57 | private function getPatternPrepared(): string |
|
103 | { |
||
104 | 57 | if ($this->patternPrepared !== null) { |
|
105 | return $this->patternPrepared; |
||
106 | } |
||
107 | |||
108 | 57 | $replacements = [ |
|
109 | 57 | '\*\*' => '.*', |
|
110 | 57 | '\\\\\\\\' => '\\\\', |
|
111 | 57 | '\\\\\\*' => '[*]', |
|
112 | 57 | '\\\\\\?' => '[?]', |
|
113 | 57 | '\\\\\\[' => '[\[]', |
|
114 | 57 | '\\\\\\]' => '[\]]', |
|
115 | 57 | ]; |
|
116 | |||
117 | 57 | if ($this->delimiters === []) { |
|
118 | 1 | $replacements += [ |
|
119 | 1 | '\*' => '.*', |
|
120 | 1 | '\?' => '?', |
|
121 | 1 | ]; |
|
122 | } else { |
||
123 | 56 | $notDelimiters = '[^' . preg_quote(implode('', $this->delimiters), '#') . ']'; |
|
124 | 56 | $replacements += [ |
|
125 | 56 | '\*' => "$notDelimiters*", |
|
126 | 56 | '\?' => $notDelimiters, |
|
127 | 56 | ]; |
|
128 | } |
||
129 | |||
130 | 57 | $replacements += [ |
|
131 | 57 | '\[\!' => '[^', |
|
132 | 57 | '\[' => '[', |
|
133 | 57 | '\]' => ']', |
|
134 | 57 | '\-' => '-', |
|
135 | 57 | ]; |
|
136 | |||
137 | 57 | $pattern = strtr(preg_quote($this->pattern, '#'), $replacements); |
|
138 | 57 | $pattern = '#^' . $pattern . '$#us'; |
|
139 | |||
140 | 57 | if ($this->ignoreCase) { |
|
141 | 1 | $pattern .= 'i'; |
|
142 | } |
||
143 | |||
144 | 57 | $this->patternPrepared = $pattern; |
|
145 | |||
146 | 57 | return $this->patternPrepared; |
|
0 ignored issues
–
show
|
|||
147 | } |
||
148 | } |
||
149 |