Completed
Push — master ( 413e31...6028d7 )
by Changwan
06:35
created

Pattern::parseEach()   C

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 28
    public function __construct(string $pattern)
44
    {
45 28
        $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 28
    }
47
48
    /**
49
     * @return array
50
     */
51 28
    public function parse() {
52 28
        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 28
        if (!preg_match_all(
56 28
            '~' . 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 28
            PREG_OFFSET_CAPTURE | PREG_SET_ORDER
58
        )) {
59 22
            return $this->parsedPattern = [
60 22
                [$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 11
        $parsedPattern = [];
65 11
        $this->getOptional($matches);
66 11
        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 1
            foreach ($this->subset($this->optional) as $optional) {
68 1
                $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 10
            $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 11
        return $this->parsedPattern = $parsedPattern;
74
    }
75
76
    /**
77
     * @param array $arguments
78
     * @return string
79
     */
80 6
    public function path(array $arguments = [])
81
    {
82 6
        $parsedPattern = $this->parse();
83 6
        foreach ($this->required as $required) {
84 5
            if (!array_key_exists($required, $arguments)) {
85 5
                throw new CannotGetPathException($this->required);
86
            }
87
        }
88 6
        $patternIndex = 0;
89 6
        foreach ($this->optional as $index => $optional) {
90 1
            if (array_key_exists($optional, $arguments)) {
91 1
                $patternIndex += (1 >> $index);
92
            }
93
        }
94 6
        $path = '';
95 6
        foreach ($parsedPattern[$patternIndex] as $item) {
96 6
            if (is_array($item)) {
97 6
                if ($item[0]) {
98 5
                    $path .= rawurlencode($arguments[$item[0]]);
99 5
                    unset($arguments[$item[0]]);
100 3
                } elseif (strpos($item[1], '(?:\/') === 0) {
101 6
                    $path .= '/';
102
                }
103
            } else {
104 6
                $path .= $item;
105
            }
106
        }
107 6
        $queries = [];
108 6
        foreach ($arguments as $key => $value) {
109 2
            $value = rawurlencode($value);
110 2
            $queries[] = "{$key}={$value}";
111
        }
112 6
        return rtrim($path . (count($queries) ? '?' . implode('&', $queries) : ''), '/');
113
    }
114
    
115 11
    private function parseEach($route, $matches, array $optional = [])
116
    {
117 11
        $offset = 0;
118 11
        $routeData = [];
119 11
        foreach ($matches as $set) {
120 11
            if ($set[0][1] > $offset) {
121 7
                $routeData[] = substr($route, $offset, $set[0][1] - $offset);
122
            }
123 11
            $prefix = $set[2][0] ?? '';
124 11
            $name = $set[3][0] ?? '';
125 11
            $capture = $set[4][0] ?? '';
126 11
            $group = $set[5][0] ?? '';
127 11
            $modifier = $set[6][0] ?? '';
128 11
            $asterisk = $set[7][0] ?? '';
129
130
            // group
131 11
            if ($group) {
132 2
                if ($prefix) {
133 2
                    $routeData[] = ['', "(?:\\/{$group}){$modifier}",];
134
                } else {
135 2
                    $routeData[] = ['', "(?:{$group}){$modifier}",];
136
                }
137 11
            } elseif ($asterisk) {
138 1
                if ($prefix) {
139 1
                    $routeData[] = ['', '\/.*'];
140
                } else {
141 1
                    $routeData[] = ['', '.*'];
142
                }
143 10
            } elseif (in_array($name, $optional) || $modifier !== '?') {
144 10
                if ($prefix) {
145 9
                    $routeData[] = $prefix;
146
                }
147 10
                $routeData[] = [$name, $capture ?: static::DEFAULT_DISPATCH_REGEX];
148
            }
149 11
            $offset = $set[0][1] + strlen($set[0][0]);
150
        }
151 11
        if ($offset != strlen($route)) {
152 1
            $routeData[] = substr($route, $offset);
153
        }
154 11
        return $routeData;
155
    }
156
    
157 11
    private function getOptional($matches)
158
    {
159 11
        $optional = [];
160 11
        $required = [];
161 11
        foreach ($matches as $set) {
162 11
            $name = $set[3][0] ?? '';
163 11
            $modifier = $set[6][0] ?? '';
164 11
            $group = $set[5][0] ?? '';
165 11
            $asterisk = $set[7][0] ?? '';
166
167 11
            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 10
            } elseif ($modifier === '?') {
169 1
                $optional[] = $name;
170
            } else {
171 11
                $required[] = $name;
172
            }
173
        }
174 11
        $this->optional = $optional;
175 11
        $this->required = $required;
176 11
    }
177
    
178 1
    private function subset($elems) {
179 1
        $count = count($elems);
180 1
        if ($count === 0) return;
181 1
        $subsetCount = pow(2, $count);
182 1
        for ($i = 0; $i < $subsetCount; $i++) {
183 1
            $out = [];
184 1
            for ($j = 0; $j < $count; $j++) {
185 1
                if ($i & (1 << $j)) $out[] = $elems[$j];
186
            }
187 1
            yield $out;
188
        }
189 1
    }
190
}
191