Passed
Pull Request — master (#117)
by Viktor
03:34
created

WildcardPattern::isDynamic()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 1
b 0
f 0
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
    private ?string $patternPrepared = null;
30
31
    /**
32
     * @param string $pattern The shell wildcard pattern to match against.
33
     * @param string[] $delimiters Delimiters to consider for "*" (`/` and `\` by default).
34
     */
35 59
    public function __construct(
36
        private string $pattern,
37
        private array $delimiters = ['\\\\', '/'],
38
    ) {
39 59
    }
40
41
    /**
42
     * Checks if the passed string would match the given shell wildcard pattern.
43
     *
44
     * @param string $string The tested string.
45
     *
46
     * @return bool Whether the string matches pattern or not.
47
     */
48 58
    public function match(string $string): bool
49
    {
50 58
        if ($this->pattern === '**') {
51 1
            return true;
52
        }
53
54 57
        return preg_match($this->getPatternPrepared(), $string) === 1;
55
    }
56
57
    /**
58
     * Make pattern case insensitive.
59
     */
60 3
    public function ignoreCase(bool $flag = true): self
61
    {
62 3
        $new = clone $this;
63 3
        $new->patternPrepared = null;
64 3
        $new->ignoreCase = $flag;
65
66 3
        return $new;
67
    }
68
69
    /**
70
     * Returns whether the pattern contains a dynamic part i.e.
71
     * has unescaped "*",  "{", "?", or "[" character.
72
     *
73
     * @param string $pattern The pattern to check.
74
     *
75
     * @return bool Whether the pattern contains a dynamic part.
76
     */
77 5
    public static function isDynamic(string $pattern): bool
78
    {
79 5
        $pattern = preg_replace('/\\\\./', '', $pattern);
80 5
        return preg_match('/[*{?\[]/', $pattern) === 1;
81
    }
82
83
    /**
84
     * Escapes pattern characters in a string.
85
     *
86
     * @param string $string Source string.
87
     *
88
     * @return string String with pattern characters escaped.
89
     */
90 2
    public static function quote(string $string): string
91
    {
92 2
        return preg_replace('#([\\\\?*\\[\\]])#', '\\\\$1', $string);
93
    }
94
95 57
    private function getPatternPrepared(): string
96
    {
97 57
        if ($this->patternPrepared === null) {
98 57
            $replacements = [
99 57
                '\*\*' => '.*',
100 57
                '\\\\\\\\' => '\\\\',
101 57
                '\\\\\\*' => '[*]',
102 57
                '\\\\\\?' => '[?]',
103 57
                '\\\\\\[' => '[\[]',
104 57
                '\\\\\\]' => '[\]]',
105 57
            ];
106
107 57
            if ($this->delimiters === []) {
108 1
                $replacements += [
109 1
                    '\*' => '.*',
110 1
                    '\?' => '?',
111 1
                ];
112
            } else {
113 56
                $notDelimiters = '[^' . preg_quote(implode('', $this->delimiters), '#') . ']';
114 56
                $replacements += [
115 56
                    '\*' => "$notDelimiters*",
116 56
                    '\?' => $notDelimiters,
117 56
                ];
118
            }
119
120 57
            $replacements += [
121 57
                '\[\!' => '[^',
122 57
                '\[' => '[',
123 57
                '\]' => ']',
124 57
                '\-' => '-',
125 57
            ];
126
127 57
            $pattern = strtr(preg_quote($this->pattern, '#'), $replacements);
128 57
            $pattern = '#^' . $pattern . '$#us';
129
130 57
            if ($this->ignoreCase) {
131 1
                $pattern .= 'i';
132
            }
133
134 57
            $this->patternPrepared = $pattern;
135
        }
136
137 57
        return $this->patternPrepared;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->patternPrepared could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
138
    }
139
}
140