yiisoft /
strings
| 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 |
||
| 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
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 147 | } |
||
| 148 | } |
||
| 149 |