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

CombinedRegexp   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 92
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
eloc 28
c 6
b 3
f 0
dl 0
loc 92
ccs 31
cts 31
cp 1
rs 10
wmc 9

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getCompiledPattern() 0 3 1
A matches() 0 3 1
A __construct() 0 9 2
A compilePatterns() 0 18 2
A getMatchingPatternPosition() 0 14 2
A getMatchingPattern() 0 3 1
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