Pattern::parseEach()   C
last analyzed

Complexity

Conditions 12
Paths 38

Size

Total Lines 41
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 12

Importance

Changes 0
Metric Value
cc 12
eloc 30
nc 38
nop 3
dl 0
loc 41
ccs 28
cts 28
cp 1
crap 12
rs 5.1612
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Wandu\Router\Path;
3
4
use Wandu\Router\Exception\CannotGetPathException;
5
6
class Pattern
7
{
8
    const VARIABLE_REGEX = <<<'REGEX'
9
(\\.)
10
|
11
([\/.])?
12
(?:
13
    (?:
14
        \:(\w+)
15
        (?:
16
            \(
17
            ( (?:\\. | [^\\()])+ )
18
            \)
19
        )?
20
     |  \(
21
        ( (?:\\. | [^\\()])+ )
22
        \)
23
        
24
    )
25
    ([?])?
26
    |(\*)
27
)
28
REGEX;
29
    // ([?])?
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
30
    // ([+*?])?
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
31
    
32
    const DEFAULT_DISPATCH_REGEX = '[^/]+';
33
34
    /** @var array */
35
    protected $parsedPattern;
36
37
    /** @var array */
38
    protected $required = [];
39
40
    /** @var array */
41
    protected $optional = [];
42
    
43 21
    public function __construct(string $pattern)
44
    {
45 21
        $this->pattern = $pattern;
0 ignored issues
show
Bug introduced by
The property pattern does not seem to exist. Did you mean parsedPattern?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
46 21
    }
47
48
    /**
49
     * @return array
50
     */
51 21
    public function parse() {
52 21
        if ($this->parsedPattern) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parsedPattern of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
53 5
            return $this->parsedPattern;
54
        }
55 21
        if (!preg_match_all(
56 21
            '~' . self::VARIABLE_REGEX . '~x', $this->pattern, $matches,
0 ignored issues
show
Bug introduced by
The property pattern does not seem to exist. Did you mean parsedPattern?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
57 21
            PREG_OFFSET_CAPTURE | PREG_SET_ORDER
58
        )) {
59 13
            return $this->parsedPattern = [
60 13
                [$this->pattern]
0 ignored issues
show
Bug introduced by
The property pattern does not seem to exist. Did you mean parsedPattern?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
61
            ];
62
        }
63
        
64 12
        $parsedPattern = [];
65 12
        $this->getOptional($matches);
66 12
        if ($countOptionals = count($this->optional)) {
0 ignored issues
show
Unused Code introduced by
$countOptionals is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
67 3
            foreach ($this->subset($this->optional) as $optional) {
68 3
                $parsedPattern[] = $this->parseEach($this->pattern, $matches, $optional);
0 ignored issues
show
Bug introduced by
The property pattern does not seem to exist. Did you mean parsedPattern?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
69
            }
70
        } else {
71 11
            $parsedPattern[] = $this->parseEach($this->pattern, $matches);
0 ignored issues
show
Bug introduced by
The property pattern does not seem to exist. Did you mean parsedPattern?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
72
        }
73 12
        return $this->parsedPattern = $parsedPattern;
74
    }
75
76
    /**
77
     * @param array $arguments
78
     * @return string
79
     */
80 5
    public function path(array $arguments = [])
81
    {
82 5
        $parsedPattern = $this->parse();
83 5
        foreach ($this->required as $required) {
84 4
            if (!array_key_exists($required, $arguments)) {
85 4
                throw new CannotGetPathException($this->required);
86
            }
87
        }
88 5
        $patternIndex = 0;
89 5
        foreach ($this->optional as $index => $optional) {
90 1
            if (array_key_exists($optional, $arguments)) {
91 1
                $patternIndex += (1 >> $index);
92
            }
93
        }
94 5
        $path = '';
95 5
        foreach ($parsedPattern[$patternIndex] as $item) {
96 5
            if (is_array($item)) {
97 5
                if ($item[0]) {
98 4
                    $path .= rawurlencode($arguments[$item[0]]);
99 4
                    unset($arguments[$item[0]]);
100 3
                } elseif (strpos($item[1], '(?:\/') === 0) {
101 5
                    $path .= '/';
102
                }
103
            } else {
104 5
                $path .= $item;
105
            }
106
        }
107 5
        $queries = [];
108 5
        foreach ($arguments as $key => $value) {
109 1
            $value = rawurlencode($value);
110 1
            $queries[] = "{$key}={$value}";
111
        }
112 5
        return rtrim($path . (count($queries) ? '?' . implode('&', $queries) : ''), '/');
113
    }
114
    
115 12
    private function parseEach($route, $matches, array $optional = [])
116
    {
117 12
        $offset = 0;
118 12
        $routeData = [];
119 12
        foreach ($matches as $set) {
120 12
            if ($set[0][1] > $offset) {
121 7
                $routeData[] = substr($route, $offset, $set[0][1] - $offset);
122
            }
123 12
            $prefix = $set[2][0] ?? '';
124 12
            $name = $set[3][0] ?? '';
125 12
            $capture = $set[4][0] ?? '';
126 12
            $group = $set[5][0] ?? '';
127 12
            $modifier = $set[6][0] ?? '';
128 12
            $asterisk = $set[7][0] ?? '';
129
130
            // group
131 12
            if ($group) {
132 2
                if ($prefix) {
133 2
                    $routeData[] = ['', "(?:\\/{$group}){$modifier}",];
134
                } else {
135 2
                    $routeData[] = ['', "(?:{$group}){$modifier}",];
136
                }
137 12
            } elseif ($asterisk) {
138 1
                if ($prefix) {
139 1
                    $routeData[] = ['', '\/.*'];
140
                } else {
141 1
                    $routeData[] = ['', '.*'];
142
                }
143 11
            } elseif (in_array($name, $optional) || $modifier !== '?') {
144 11
                if ($prefix) {
145 10
                    $routeData[] = $prefix;
146
                }
147 11
                $routeData[] = [$name, $capture ?: static::DEFAULT_DISPATCH_REGEX];
148
            }
149 12
            $offset = $set[0][1] + strlen($set[0][0]);
150
        }
151 12
        if ($offset != strlen($route)) {
152 1
            $routeData[] = substr($route, $offset);
153
        }
154 12
        return $routeData;
155
    }
156
    
157 12
    private function getOptional($matches)
158
    {
159 12
        $optional = [];
160 12
        $required = [];
161 12
        foreach ($matches as $set) {
162 12
            $name = $set[3][0] ?? '';
163 12
            $modifier = $set[6][0] ?? '';
164 12
            $group = $set[5][0] ?? '';
165 12
            $asterisk = $set[7][0] ?? '';
166
167 12
            if ($group || $asterisk) { // 
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
168 11
            } elseif ($modifier === '?') {
169 3
                $optional[] = $name;
170
            } else {
171 12
                $required[] = $name;
172
            }
173
        }
174 12
        $this->optional = $optional;
175 12
        $this->required = $required;
176 12
    }
177
    
178 3
    private function subset($elems) {
179 3
        $count = count($elems);
180 3
        if ($count === 0) return;
181 3
        $subsetCount = pow(2, $count);
182 3
        for ($i = 0; $i < $subsetCount; $i++) {
183 3
            $out = [];
184 3
            for ($j = 0; $j < $count; $j++) {
185 3
                if ($i & (1 << $j)) $out[] = $elems[$j];
186
            }
187 3
            yield $out;
188
        }
189 3
    }
190
}
191