Completed
Push — master ( 86f7cc...7c1ca4 )
by Basil
07:22
created

CompositionResolver::trailingPathInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace luya\web;
4
5
use yii\base\BaseObject;
6
use luya\web\Request;
7
use luya\helpers\Url;
8
use luya\helpers\StringHelper;
9
10
/**
11
 * Resolve composition values from a given path and pattern.
12
 * 
13
 * @property string $resolvedPath
14
 * @property array $resolvedValues
15
 * @property array $resolvedKeys
16
 * 
17
 * @author Basil Suter <[email protected]>
18
 * @since 1.0.5
19
 */
20
class CompositionResolver extends BaseObject
21
{
22
    /**
23
     * @var string The Regular-Expression matching the var finder inside the url parts
24
     */
25
    const VAR_MATCH_REGEX = '/<(\w+):?([^>]+)?>/';
26
    
27
    /**
28
     * @var Request
29
     */
30
    protected $request;
31
    
32
    /**
33
     * @var string Url matching prefix, which is used for all the modules (e.g. an e-store requireds a language
34
     * as the cms needs this informations too). After proccessing this informations, they will be removed
35
     * from the url for further proccessing.
36
     *
37
     * Examples of how to use patterns:
38
     *
39
     * ```php
40
     * 'pattern' => '<langShortCode:[a-z]{2}>.<countryShortCode:[a-z]{2}>', // de-ch; fr-ch
41
     * ```
42
     */
43
    public $pattern;
44
    
45
    /**
46
     * @var array Default value if there is no composition provided in the url. The default value must match the url.
47
     */
48
    public $defaultValues = [];
49
    
50
    /**
51
     * Constructor ensures given Request component.
52
     * 
53
     * @param Request $request
54
     * @param array $config
55
     */
56
    public function __construct(Request $request, array $config = [])
57
    {
58
        $this->request = $request;
59
        parent::__construct($config);
60
    }
61
    
62
    /**
63
     * Get the resolved path.
64
     *
65
     * @return string|array
66
     */
67
    public function getResolvedPath()
68
    {
69
        return $this->getInternalResolverArray()['route'];
70
    }
71
    
72
    /**
73
     * Get resolved composition values as array.
74
     * 
75
     * @return array
76
     */
77
    public function getResolvedValues()
78
    {
79
        return $this->getInternalResolverArray()['values'];
80
    }
81
    
82
    /**
83
     * Get only the resolved composition keys from pattern.
84
     * @return array
85
     */
86
    public function getResolvedKeys()
87
    {
88
        return array_keys($this->getResolvedValues());
89
    }
90
    
91
    /**
92
     * Get a value for a given resolved pattern key.
93
     * 
94
     * @param string $key
95
     * @return boolean|mixed
96
     */
97
    public function getResolvedKeyValue($key)
98
    {
99
        $keys = $this->resolvedValues;
100
        
101
        return isset($keys[$key]) ? $keys[$key] : false;
102
    }
103
    
104
    /**
105
     * Add trailing slash to the request pathinfo.
106
     * 
107
     * @return string
108
     */
109
    protected function trailingPathInfo()
110
    {
111
        return Url::trailing($this->request->pathInfo);
112
    }
113
    
114
    /**
115
     * Generate the regex pattern based on the pattern.
116
     *
117
     * @return string
118
     */
119
    protected function buildRegexPattern()
120
    {
121
        return "@^{$this->pattern}\/@";
122
    }
123
    
124
    private $_resolved;
125
    
126
    /**
127
     * Resolve the current data.
128
     * 
129
     * @return array
130
     */
131
    protected function getInternalResolverArray()
132
    {
133
        if ($this->_resolved === null) {
134
            
135
            $requestPathInfo = $this->trailingPathInfo();
136
            $newRegex = $this->buildRegexPattern();
137
            
138
            // extract the rules from the regex pattern, this means you get array with keys for every rule inside the pattern string
139
            // example pattern: <langShortCode:[a-z]{2}>-<countryShortCode:[a-z]{2}>
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
140
            /* [0]=>
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% 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...
141
                 array(3) {
142
                     [0]=> string(24) "<langShortCode:[a-z]{2}>"
143
                     [1]=> string(13) "langShortCode"
144
                     [2]=> string(8) "[a-z]{2}"
145
                 }
146
               [1]=>
147
                 array(3) {
148
                     [0]=> string(27) "<countryShortCode:[a-z]{2}>"
149
                     [1]=> string(16) "countryShortCode"
150
                     [2]=> string(8) "[a-z]{2}"
151
                 }
152
             */
153
            preg_match_all(static::VAR_MATCH_REGEX, $this->pattern, $patternDefinitions, PREG_SET_ORDER);
154
            
155
            foreach($patternDefinitions as $definition) {
0 ignored issues
show
Bug introduced by
The expression $patternDefinitions of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
156
                $newRegex = str_replace($definition[0], "(".$definition[2].")", $newRegex);
157
            }
158
            
159
            preg_match_all($newRegex, $requestPathInfo, $matches, PREG_SET_ORDER);
160
            
161
            if (isset($matches[0]) && !empty($matches[0])) {
162
                $keys = [];
163
                $matches = $matches[0];
164
                
165
                $compositionPrefix = $matches[0];
166
                unset($matches[0]);
167
                $matches = array_values($matches);
168
                
169
                foreach ($matches as $k => $v) {
170
                    $keys[$patternDefinitions[$k][1]] = $v;
171
                }
172
                
173
                $route = StringHelper::replaceFirst($compositionPrefix, '', $requestPathInfo);
174
                
175
            } else {
176
                $matches = [];
0 ignored issues
show
Unused Code introduced by
$matches 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...
177
                $keys = $this->defaultValues;
178
                $route = $requestPathInfo;
179
            }
180
            
181
            $this->_resolved = [
182
                'route' => rtrim($route, '/'),
183
                'values' => $keys,
184
            ];
185
        }
186
        
187
        return $this->_resolved;
188
    }
189
}