Passed
Pull Request — master (#103)
by Dmitriy
12:16 queued 16s
created

CombinedRegexp::getFlags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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