Passed
Pull Request — 3.x (#196)
by
unknown
01:29
created

Generator::buildUrl()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 4
nop 0
dl 0
loc 16
ccs 5
cts 5
cp 1
crap 4
rs 9.9332
c 0
b 0
f 0
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
10
namespace Aura\Router;
11
12
/**
13
 *
14
 * Generates URL paths from routes.
15
 *
16
 * @package Aura.Router
17
 *
18
 */
19
class Generator
20
{
21
    const REGEX = '#{\s*([a-zA-Z_][a-zA-Z0-9_-]*)\s*:*\s*([^{}]*{*[^{}]*}*[^{}]*)\s*}#';
22
    const OPT_REGEX = '#{\s*/\s*([a-z][a-zA-Z0-9_-]*\s*:*\s*[^/]*{*[^/]*}*[^/]*,*)}#';
23
    const EXPLODE_REGEX = '#\s*([a-zA-Z_][a-zA-Z0-9_-]*)\s*(?::*\s*([^,]*[{\d+,}]*[^,\w\s])[^}]?)?#';
24
25
    /**
26
     *
27
     * The map of all routes.
28
     *
29
     * @var Map
30
     *
31
     */
32
    protected $map;
33
34
    /**
35
     *
36
     * The route from which the URL is being generated.
37
     *
38
     * @var Route
39
     *
40
     */
41
    protected $route;
42
43
    /**
44
     *
45
     * The URL being generated.
46
     *
47
     * @var string
48
     *
49
     */
50
    protected $url;
51
52
    /**
53
     *
54
     * Data being interpolated into the URL.
55
     *
56
     * @var array
57
     *
58
     */
59
    protected $data;
60
61
    /**
62
     *
63
     * Replacement data.
64
     *
65
     * @var array
66
     *
67
     */
68
    protected $repl;
69
70
    /**
71
     *
72
     * Leave values raw?
73
     *
74
     * @var bool
75
     *
76
     */
77
    protected $raw;
78
79
    /**
80
     *
81
     * The basepath to prefix to generated paths.
82
     *
83
     * @var string|null
84
     *
85
     */
86
    protected $basepath;
87
88
    /**
89
     *
90
     * Constructor.
91
     *
92 14
     * @param Map $map A route collection object.
93
     *
94 14
     * @param string $basepath The basepath to prefix to generated paths.
95 14
     *
96 14
     */
97
    public function __construct(Map $map, $basepath = null)
98
    {
99
        $this->map = $map;
100
        $this->basepath = $basepath;
101
    }
102
103
    /**
104
     *
105
     * Looks up a route by name, and interpolates data into it to return
106
     * a URI path.
107
     *
108
     * @param string $name The route name to look up.
109
     *
110
     * @param array $data The data to interpolate into the URI; data keys
111
     * map to attribute tokens in the path.
112
     *
113
     * @return string A URI path string if the route name is found
114 12
     *
115
     * @throws Exception\RouteNotFound
116 12
     *
117
     */
118
    public function generate($name, array $data = [])
119
    {
120
        return $this->build($name, $data, false);
121
    }
122
123
    /**
124
     *
125
     * Gets the URL for a Route.
126
     *
127
     * @param string $name The route name to look up.
128
     *
129
     * @param array $data An array of key-value pairs to interpolate into the
130
     * attribute tokens in the path for the Route.
131
     *
132
     * @param bool $raw Leave the data unencoded?
133
     *
134 2
     * @return string
135
     *
136 2
     * @throws Exception\RouteNotFound
137
     *
138
     */
139
    protected function build($name, array $data, $raw)
140
    {
141
        $this->raw = $raw;
142
        $this->route = $this->map->getRoute($name);
143
        $this->buildUrl();
144
        $this->repl = [];
145
        $this->data = array_merge($this->route->defaults, $data);
146
147
        $this->buildTokenReplacements();
148
        $this->buildOptionalReplacements();
149
        $this->url = strtr($this->url, $this->repl);
150
        $this->buildWildcardReplacement();
151
152
        return $this->url;
153 14
    }
154
155 14
    /**
156 14
     *
157 13
     * Builds the URL property.
158 13
     *
159 13
     * @return void
160
     *
161 13
     */
162 13
    protected function buildUrl()
163 13
    {
164 13
        $this->url = $this->basepath . $this->route->path;
165
166 13
        $host = $this->route->host;
167
        if (!$host) {
168
            return;
169
        }
170
        $this->url = '//' . $host . $this->url;
171
172
        $secure = $this->route->secure;
173
        if ($secure === null) {
0 ignored issues
show
introduced by
The condition $secure === null is always false.
Loading history...
174
            return;
175
        }
176 13
        $protocol = $secure ? 'https:' : 'http:';
177
        $this->url = $protocol . $this->url;
178 13
    }
179
180 13
    /**
181 13
     *
182 10
     * Builds urlencoded data for token replacements.
183
     *
184 3
     * @return void
185
     *
186 3
     */
187 3
    protected function buildTokenReplacements()
188 1
    {
189
        preg_match_all(self::REGEX, $this->url, $matches, PREG_SET_ORDER);
190 2
        foreach ($matches as $match) {
191 2
            $name = $match[1];
192 2
            foreach ($this->data as $key => $val) {
193
                if ($key === $name) {
194
                    $token = isset($match[2]) ? $match[2] : null;
195
                    if (isset($this->route->tokens[$name]) && is_string($this->route->tokens[$name])) {
196
                        // if $token is null use route token
197
                        $token = $token ?: $this->route->tokens[$name];
198
                    }
199
                    if ($token) {
200
                        if (!preg_match('~^' . $token . '$~', (string)$val)) {
201 13
                            throw new \RuntimeException(sprintf(
202
                                'Parameter value for [%s] did not match the regex `%s`',
203 13
                                $name,
204 13
                                $token
205
                            ));
206 13
                        }
207
                    }
208
                    $this->repl[$match[0]] = $this->encode($val);
209
                }
210
            }
211
        }
212
    }
213
214
    /**
215 13
     *
216
     * Encodes values, or leaves them raw.
217
     *
218 13
     * @param string $val The value to encode or leave raw.
219 13
     *
220 12
     * @return mixed
221
     *
222
     */
223
    protected function encode($val)
224 1
    {
225
        if ($this->raw) {
226
            return $val;
227 1
        }
228
229
        return is_scalar($val) ? rawurlencode($val) : null;
0 ignored issues
show
introduced by
The condition is_scalar($val) is always true.
Loading history...
230 1
    }
231 1
232
    /**
233
     *
234
     * Builds replacements for attributes in the generated path.
235
     *
236
     * @return void
237
     *
238
     */
239
    protected function buildOptionalReplacements()
240
    {
241
        // replacements for optional attributes, if any
242 1
        preg_match(self::OPT_REGEX, $this->url, $matches);
243
        if (!$matches) {
244 1
            return;
245 1
        }
246
247 1
        // the optional attribute names in the token
248
        $names = [];
249
        preg_match_all(self::EXPLODE_REGEX, $matches[1], $exMatches, PREG_SET_ORDER);
250 1
        foreach ($exMatches as $match) {
251
            $name = $match[1];
252
            $token = isset($match[2]) ? $match[2] : null;
253 1
            if (isset($this->route->tokens[$name]) && is_string($this->route->tokens[$name])) {
254
                // if $token is null use route token
255 1
                $token = $token ?: $this->route->tokens[$name];
256
            }
257
            $names[] = $token ? [$name, $token] : $name;
258
        }
259
260
        // this is the full token to replace in the path
261
        $key = $matches[0];
262
263
        // build the replacement string
264
        $this->repl[$key] = $this->buildOptionalReplacement($names);
265 13
    }
266
267 13
    /**
268 13
     *
269 1
     * Builds the optional replacement for attribute names.
270 1
     *
271 1
     * @param array $names The optional replacement names.
272
     *
273
     * @return string
274 13
     *
275
     */
276
    protected function buildOptionalReplacement($names)
277
    {
278
        $repl = '';
279
        foreach ($names as $name) {
280
            $token = null;
281
            if (is_array($name)) {
282
                $token = $name[1];
283
                $name = $name[0];
284
            }
285 13
            // is there data for this optional attribute?
286
            if (!isset($this->data[$name])) {
287 13
                // options are *sequentially* optional, so if one is
288 2
                // missing, we're done
289
                return $repl;
290
            }
291 11
292
            $val = $this->data[$name];
293
294
            // Check val matching token
295
            if ($token) {
296
                if (!preg_match('~^' . $token . '$~', (string)$val)) {
297
                    throw new \RuntimeException(sprintf(
298
                        'Parameter value for [%s] did not match the regex `%s`',
299
                        $name,
300
                        $token
301
                    ));
302
                }
303
            }
304
            // encode the optional value
305
            $repl .= '/' . $this->encode($val);
306
        }
307
        return $repl;
308
    }
309
310
    /**
311
     *
312
     * Builds a wildcard replacement in the generated path.
313
     *
314
     * @return void
315
     *
316
     */
317
    protected function buildWildcardReplacement()
318
    {
319
        $wildcard = $this->route->wildcard;
320
        if ($wildcard && isset($this->data[$wildcard])) {
321
            $this->url = rtrim($this->url, '/');
322
            foreach ($this->data[$wildcard] as $val) {
323
                $this->url .= '/' . $this->encode($val);
324
            }
325
        }
326
    }
327
328
    /**
329
     *
330
     * Generate the route without url encoding.
331
     *
332
     * @param string $name The route name to look up.
333
     *
334
     * @param array $data The data to interpolate into the URI; data keys
335
     * map to attribute tokens in the path.
336
     *
337
     * @return string A URI path string
338
     *
339
     * @throws Exception\RouteNotFound
340
     *
341
     */
342
    public function generateRaw($name, array $data = [])
343
    {
344
        return $this->build($name, $data, true);
345
    }
346
}
347