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}> |
|
|
|
|
140
|
|
|
/* [0]=> |
|
|
|
|
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) { |
|
|
|
|
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 = []; |
|
|
|
|
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
|
|
|
} |
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.