1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* |
4
|
|
|
* This file is part of Aura for PHP. |
5
|
|
|
* |
6
|
|
|
* @license http://opensource.org/licenses/bsd-license.php BSD |
7
|
|
|
* |
8
|
|
|
*/ |
9
|
|
|
namespace Aura\Router; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* |
13
|
|
|
* Generates URL paths from routes. |
14
|
|
|
* |
15
|
|
|
* @package Aura.Router |
16
|
|
|
* |
17
|
|
|
*/ |
18
|
|
|
class Generator |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* |
22
|
|
|
* The map of all routes. |
23
|
|
|
* |
24
|
|
|
* @var Map |
25
|
|
|
* |
26
|
|
|
*/ |
27
|
|
|
protected $map; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* |
31
|
|
|
* The route from which the URL is being generated. |
32
|
|
|
* |
33
|
|
|
* @var Route |
34
|
|
|
* |
35
|
|
|
*/ |
36
|
|
|
protected $route; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* |
40
|
|
|
* The URL being generated. |
41
|
|
|
* |
42
|
|
|
* @var string |
43
|
|
|
* |
44
|
|
|
*/ |
45
|
|
|
protected $url; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* |
49
|
|
|
* Data being interpolated into the URL. |
50
|
|
|
* |
51
|
|
|
* @var array |
52
|
|
|
* |
53
|
|
|
*/ |
54
|
|
|
protected $data; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* |
58
|
|
|
* Replacement data. |
59
|
|
|
* |
60
|
|
|
* @var array |
61
|
|
|
* |
62
|
|
|
*/ |
63
|
|
|
protected $repl; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* |
67
|
|
|
* Leave values raw? |
68
|
|
|
* |
69
|
|
|
* @var bool |
70
|
|
|
* |
71
|
|
|
*/ |
72
|
|
|
protected $raw; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* |
76
|
|
|
* The basepath to prefix to generated paths. |
77
|
|
|
* |
78
|
|
|
* @var string |
79
|
|
|
* |
80
|
|
|
*/ |
81
|
|
|
protected $basepath; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* |
85
|
|
|
* Constructor. |
86
|
|
|
* |
87
|
|
|
* @param Map $map A route collection object. |
88
|
|
|
* |
89
|
|
|
* @param string $basepath The basepath to prefix to generated paths. |
90
|
|
|
* |
91
|
|
|
*/ |
92
|
12 |
|
public function __construct(Map $map, $basepath = null) |
93
|
|
|
{ |
94
|
12 |
|
$this->map = $map; |
95
|
12 |
|
$this->basepath = $basepath; |
96
|
12 |
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* |
100
|
|
|
* Looks up a route by name, and interpolates data into it to return |
101
|
|
|
* a URI path. |
102
|
|
|
* |
103
|
|
|
* @param string $name The route name to look up. |
104
|
|
|
* |
105
|
|
|
* @param array $data The data to interpolate into the URI; data keys |
106
|
|
|
* map to attribute tokens in the path. |
107
|
|
|
* |
108
|
|
|
* @return string|false A URI path string if the route name is found, or |
109
|
|
|
* boolean false if not. |
110
|
|
|
* |
111
|
|
|
* @throws Exception\RouteNotFound |
112
|
|
|
* |
113
|
|
|
*/ |
114
|
11 |
|
public function generate($name, $data = []) |
115
|
|
|
{ |
116
|
11 |
|
return $this->build($name, $data, false); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* |
121
|
|
|
* Generate the route without url encoding. |
122
|
|
|
* |
123
|
|
|
* @param string $name The route name to look up. |
124
|
|
|
* |
125
|
|
|
* @param array $data The data to interpolate into the URI; data keys |
126
|
|
|
* map to attribute tokens in the path. |
127
|
|
|
* |
128
|
|
|
* @return string|false A URI path string if the route name is found, or |
129
|
|
|
* boolean false if not. |
130
|
|
|
* |
131
|
|
|
* @throws Exception\RouteNotFound |
132
|
|
|
* |
133
|
|
|
*/ |
134
|
1 |
|
public function generateRaw($name, $data = []) |
135
|
|
|
{ |
136
|
1 |
|
return $this->build($name, $data, true); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* |
141
|
|
|
* Gets the URL for a Route. |
142
|
|
|
* |
143
|
|
|
* @param string $name The route name to look up. |
144
|
|
|
* |
145
|
|
|
* @param array $data An array of key-value pairs to interpolate into the |
146
|
|
|
* attribute tokens in the path for the Route. |
147
|
|
|
* |
148
|
|
|
* @param bool $raw Leave the data unencoded? |
149
|
|
|
* |
150
|
|
|
* @return string |
151
|
|
|
* |
152
|
|
|
*/ |
153
|
12 |
|
protected function build($name, $data, $raw) |
154
|
|
|
{ |
155
|
12 |
|
$this->raw = $raw; |
156
|
12 |
|
$this->route = $this->map->getRoute($name); |
157
|
11 |
|
$this->buildUrl(); |
158
|
11 |
|
$this->repl = []; |
159
|
11 |
|
$this->data = array_merge($this->route->defaults, $data); |
160
|
|
|
|
161
|
11 |
|
$this->buildTokenReplacements(); |
162
|
11 |
|
$this->buildOptionalReplacements(); |
163
|
11 |
|
$this->url = strtr($this->url, $this->repl); |
164
|
11 |
|
$this->buildWildcardReplacement(); |
165
|
|
|
|
166
|
11 |
|
return $this->url; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* |
171
|
|
|
* Builds the URL property. |
172
|
|
|
* |
173
|
|
|
* @return null |
174
|
|
|
* |
175
|
|
|
*/ |
176
|
11 |
|
protected function buildUrl() |
177
|
|
|
{ |
178
|
11 |
|
$this->url = $this->basepath . $this->route->path; |
179
|
|
|
|
180
|
11 |
|
$host = $this->route->host; |
181
|
11 |
|
if (! $host) { |
182
|
8 |
|
return; |
183
|
|
|
} |
184
|
3 |
|
$this->url = '//' . $host . $this->url; |
185
|
|
|
|
186
|
3 |
|
$secure = $this->route->secure; |
187
|
3 |
|
if ($secure === null) { |
188
|
1 |
|
return; |
189
|
|
|
} |
190
|
2 |
|
$protocol = $secure ? 'https:' : 'http:'; |
191
|
2 |
|
$this->url = $protocol . $this->url; |
192
|
2 |
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* |
196
|
|
|
* Builds urlencoded data for token replacements. |
197
|
|
|
* |
198
|
|
|
* @return array |
199
|
|
|
* |
200
|
|
|
*/ |
201
|
11 |
|
protected function buildTokenReplacements() |
202
|
|
|
{ |
203
|
11 |
|
foreach ($this->data as $key => $val) { |
204
|
11 |
|
$this->repl["{{$key}}"] = $this->encode($val); |
205
|
11 |
|
} |
206
|
11 |
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* |
210
|
|
|
* Builds replacements for attributes in the generated path. |
211
|
|
|
* |
212
|
|
|
* @return string |
213
|
|
|
* |
214
|
|
|
*/ |
215
|
11 |
|
protected function buildOptionalReplacements() |
216
|
|
|
{ |
217
|
|
|
// replacements for optional attributes, if any |
218
|
11 |
|
preg_match('#{/([a-z][a-zA-Z0-9_,]*)}#', $this->url, $matches); |
219
|
11 |
|
if (! $matches) { |
|
|
|
|
220
|
10 |
|
return; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// the optional attribute names in the token |
224
|
1 |
|
$names = explode(',', $matches[1]); |
225
|
|
|
|
226
|
|
|
// this is the full token to replace in the path |
227
|
1 |
|
$key = $matches[0]; |
228
|
|
|
|
229
|
|
|
// build the replacement string |
230
|
1 |
|
$this->repl[$key] = $this->buildOptionalReplacement($names); |
231
|
1 |
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* |
235
|
|
|
* Builds the optional replacement for attribute names. |
236
|
|
|
* |
237
|
|
|
* @param array $names The optional replacement names. |
238
|
|
|
* |
239
|
|
|
* @return string |
240
|
|
|
* |
241
|
|
|
*/ |
242
|
1 |
|
protected function buildOptionalReplacement($names) |
243
|
|
|
{ |
244
|
1 |
|
$repl = ''; |
245
|
1 |
|
foreach ($names as $name) { |
246
|
|
|
// is there data for this optional attribute? |
247
|
1 |
|
if (! isset($this->data[$name])) { |
248
|
|
|
// options are *sequentially* optional, so if one is |
249
|
|
|
// missing, we're done |
250
|
1 |
|
return $repl; |
251
|
|
|
} |
252
|
|
|
// encode the optional value |
253
|
1 |
|
$repl .= '/' . $this->encode($this->data[$name]); |
254
|
1 |
|
} |
255
|
1 |
|
return $repl; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* |
260
|
|
|
* Builds a wildcard replacement in the generated path. |
261
|
|
|
* |
262
|
|
|
* @return string |
263
|
|
|
* |
264
|
|
|
*/ |
265
|
11 |
|
protected function buildWildcardReplacement() |
266
|
|
|
{ |
267
|
11 |
|
$wildcard = $this->route->wildcard; |
268
|
11 |
|
if ($wildcard && isset($this->data[$wildcard])) { |
269
|
1 |
|
$this->url = rtrim($this->url, '/'); |
270
|
1 |
|
foreach ($this->data[$wildcard] as $val) { |
271
|
1 |
|
$this->url .= '/' . $this->encode($val); |
272
|
1 |
|
} |
273
|
1 |
|
} |
274
|
11 |
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* |
278
|
|
|
* Encodes values, or leaves them raw. |
279
|
|
|
* |
280
|
|
|
* @param string $val The value to encode or leave raw. |
281
|
|
|
* |
282
|
|
|
* @return mixed |
283
|
|
|
* |
284
|
|
|
*/ |
285
|
11 |
|
protected function encode($val) |
286
|
|
|
{ |
287
|
11 |
|
if ($this->raw) { |
288
|
1 |
|
return $val; |
289
|
|
|
} |
290
|
|
|
|
291
|
10 |
|
return is_scalar($val) ? rawurlencode($val) : null; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
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.