CombinedRegexp::getCompiledPattern()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
use Exception;
8
use InvalidArgumentException;
9
10
use function array_values;
11
use function count;
12
use function implode;
13
use function preg_match;
14
use function str_repeat;
15
use function strtr;
16
17
/**
18
 * `CombinedRegexp` optimizes matching of multiple regular expressions.
19
 * Read more about the concept in
20
 * {@see https://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html}.
21
 */
22
final class CombinedRegexp extends AbstractCombinedRegexp
23
{
24
    /**
25
     * @var string[]
26
     */
27
    private array $patterns;
28
    /**
29
     * @psalm-var non-empty-string
30
     */
31
    private string $compiledPattern;
32
33
    /**
34
     * @param string[] $patterns Regular expressions to combine.
35
     * @param string $flags Flags to apply to all regular expressions.
36
     */
37 40
    public function __construct(
38
        array $patterns,
39
        private string $flags = ''
40
    ) {
41 40
        if (empty($patterns)) {
42 2
            throw new InvalidArgumentException('At least one pattern should be specified.');
43
        }
44
45 38
        $this->patterns = array_values($patterns);
46 38
        $this->compiledPattern = $this->compilePatterns($this->patterns) . $this->flags;
47
    }
48
49
    /**
50
     * @return string The compiled pattern.
51
     */
52 36
    public function getCompiledPattern(): string
53
    {
54 36
        return $this->compiledPattern;
55
    }
56
57
    /**
58
     * Returns `true` whether the given string matches any of the patterns, `false` - otherwise.
59
     */
60 12
    public function matches(string $string): bool
61
    {
62 12
        return preg_match($this->compiledPattern, $string) === 1;
63
    }
64
65
    /**
66
     * Returns pattern that matches the given string.
67
     * @throws Exception if the string does not match any of the patterns.
68
     */
69 5
    public function getMatchingPattern(string $string): string
70
    {
71 5
        return $this->patterns[$this->getMatchingPatternPosition($string)];
72
    }
73
74
    /**
75
     * Returns position of the pattern that matches the given string.
76
     * @throws Exception if the string does not match any of the patterns.
77
     */
78 27
    public function getMatchingPatternPosition(string $string): int
79
    {
80 27
        $match = preg_match($this->compiledPattern, $string, $matches);
81 27
        if ($match !== 1) {
82 3
            $this->throwFailedMatchException($string);
83
        }
84
85 24
        return count($matches) - 1;
86
    }
87
88
    /**
89
     * @param string[] $patterns
90
     *
91
     * @psalm-param list<string> $patterns
92
     * @psalm-return non-empty-string
93
     */
94 38
    private function compilePatterns(array $patterns): string
95
    {
96 38
        $quotedPatterns = [];
97
98
        /**
99
         * Possible mutant escaping, but it's ok for our case.
100
         * It doesn't matter where to place `()` in the pattern:
101
         * https://regex101.com/r/lE1Q1S/1, https://regex101.com/r/rWg7Fj/1
102
         */
103 38
        foreach ($patterns as $i => $pattern) {
104 38
            $quotedPatterns[] = $pattern . str_repeat('()', $i);
105
        }
106
107 38
        $combinedRegexps = '(?|' . strtr(
108 38
            implode('|', $quotedPatterns),
109 38
            [self::REGEXP_DELIMITER => self::QUOTE_REPLACER]
110 38
        ) . ')';
111
112 38
        return self::REGEXP_DELIMITER . $combinedRegexps . self::REGEXP_DELIMITER;
113
    }
114
115 5
    public function getPatterns(): array
116
    {
117 5
        return $this->patterns;
118
    }
119
120 2
    public function getFlags(): string
121
    {
122 2
        return $this->flags;
123
    }
124
}
125