Passed
Push — master ( 574a80...97126d )
by Dmitriy
02:56 queued 02:14
created

CombinedRegexp::getMatchingPatternPosition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 14
ccs 11
cts 11
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
/**
8
 * `CombinedRegexp` optimizes matching of multiple regular expressions.
9
 * Read more about the concept in
10
 * {@see https://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html}.
11
 */
12
final class CombinedRegexp
13
{
14
    private const REGEXP_DELIMITER = '/';
15
    private const QUOTE_REPLACER = '\\/';
16
17
    /**
18
     * @var string[]
19
     */
20
    private array $patterns;
21
    private string $compiledPattern;
22
23
    /**
24
     * @param string[] $patterns Regular expressions to combine.
25
     * @param string $flags Flags to apply to all regular expressions.
26
     */
27 19
    public function __construct(
28
        array $patterns,
29
        string $flags = ''
30
    ) {
31 19
        if (count($patterns) === 0) {
32 1
            throw new \InvalidArgumentException('At least one pattern should be specified.');
33
        }
34 18
        $this->patterns = $patterns;
35 18
        $this->compiledPattern = $this->compilePatterns($patterns) . $flags;
36
    }
37
38
    /**
39
     * @return string The compiled pattern.
40
     */
41 18
    public function getCompiledPattern(): string
42
    {
43 18
        return $this->compiledPattern;
44
    }
45
46
    /**
47
     * Returns `true` whether the given string matches any of the patterns, `false` - otherwise.
48
     */
49 12
    public function matches(string $string): bool
50
    {
51 12
        return preg_match($this->compiledPattern, $string) === 1;
52
    }
53
54
    /**
55
     * Returns pattern that matches the given string.
56
     * @throws \Exception if the string does not match any of the patterns.
57
     */
58 5
    public function getMatchingPattern(string $string): string
59
    {
60 5
        return $this->patterns[$this->getMatchingPatternPosition($string)];
61
    }
62
63
    /**
64
     * Returns position of the pattern that matches the given string.
65
     * @throws \Exception if the string does not match any of the patterns.
66
     */
67 9
    public function getMatchingPatternPosition(string $string): int
68
    {
69 9
        $match = preg_match($this->compiledPattern, $string, $matches);
70 9
        if ($match !== 1) {
71 1
            throw new \Exception(
72 1
                sprintf(
73 1
                    'Failed to match pattern "%s" with string "%s".',
74 1
                    $this->getCompiledPattern(),
75 1
                    $string,
76 1
                )
77 1
            );
78
        }
79
80 8
        return count($matches) - 1;
81
    }
82
83
    /**
84
     * @param string[] $patterns
85
     */
86 18
    private function compilePatterns(array $patterns): string
87
    {
88 18
        $quotedPatterns = [];
89
90
        /**
91
         * Possible mutant escaping, but it's ok for our case.
92
         * It doesn't matter where to place `()` in the pattern:
93
         * https://regex101.com/r/lE1Q1S/1, https://regex101.com/r/rWg7Fj/1
94
         */
95 18
        for ($i = 0; $i < count($patterns); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
96 18
            $quotedPatterns[] = $patterns[$i] . str_repeat('()', $i);
97
        }
98 18
        $combinedRegexps = '(?|' . strtr(
99 18
            implode('|', $quotedPatterns),
100 18
            [self::REGEXP_DELIMITER => self::QUOTE_REPLACER]
101 18
        ) . ')';
102
103 18
        return self::REGEXP_DELIMITER . $combinedRegexps . self::REGEXP_DELIMITER;
104
    }
105
}
106