Passed
Pull Request — 3.x (#196)
by
unknown
02:26
created

Generator::generate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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