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 */
9namespace Aura\Router;
10
11/**
12 *
13 * An individual route with a name, path, attributes, defaults, etc.
14 *
15 * @package Aura.Router
16 *
17 * @property-read string $name The route name.
18 *
19 * @property-read string $path The route path.
20 *
21 * @property-read string $namePrefix
22 *
23 * @property-read string $pathPrefix
24 *
25 * @property-read string $host
26 *
27 * @property-read array $defaults Default values for attributes.
28 *
29 * @property-read array $attributes Attribute values added by the rules.
30 *
31 * @property-read array $tokens Placeholder token names and regexes.
32 *
33 * @property-read string $wildcard The name of the wildcard token.
34 *
35 * @property-read array $accept
36 *
37 * @property-read mixed $auth The auth value.
38 *
39 * @property-read array $extras
40 *
41 * @property-read bool $secure
42 *
43 * @property-read array $allows
44 *
45 * @property-read bool $isRoutable
46 *
47 * @property-read callable $special
48 *
49 * @property-read string $failedRule
50 *
51 * @property-read mixed $handler
52 *
53 */
54class Route
55{
56    /**
57     *
58     * Accepts these content types.
59     *
60     * @var array
61     *
62     */
63    protected $accepts = [];
64
65    /**
66     *
67     * Allows these HTTP methods.
68     *
69     * @var array
70     *
71     */
72    protected $allows = [];
73
74    /**
75     *
76     * Attribute values added by the rules.
77     *
78     * @var array
79     *
80     */
81    protected $attributes = [];
82
83    /**
84     *
85     * Authentication/authorization values.
86     *
87     * @var mixed
88     *
89     */
90    protected $auth;
91
92    /**
93     *
94     * Default attribute values.
95     *
96     * @var array
97     *
98     */
99    protected $defaults = [];
100
101    /**
102     *
103     * Extra key-value pairs to attach to the route; intended for use by
104     * custom matching rules.
105     *
106     * @var array
107     *
108     */
109    protected $extras = [];
110
111    /**
112     *
113     * The rule that failed, if any, during matching.
114     *
115     * @var string
116     *
117     */
118    protected $failedRule;
119
120    /**
121     *
122     * The action, controller, callable, closure, etc. this route points to.
123     *
124     * @var mixed
125     *
126     */
127    protected $handler;
128
129    /**
130     *
131     * The host string this route responds to.
132     *
133     * @var string
134     *
135     */
136    protected $host;
137
138    /**
139     *
140     * The name for this route.
141     *
142     * @var string
143     *
144     */
145    protected $name;
146
147    /**
148     *
149     * Prefix the route name with this string.
150     *
151     * @var string
152     *
153     */
154    protected $namePrefix;
155
156    /**
157     *
158     * The path for this route.
159     *
160     * @var string
161     *
162     */
163    protected $path;
164
165    /**
166     *
167     * Prefix the route path with this string.
168     *
169     * @var string
170     *
171     */
172    protected $pathPrefix;
173
174    /**
175     *
176     * Should this route be used for matching?
177     *
178     * @var bool
179     *
180     */
181    protected $isRoutable = true;
182
183    /**
184     *
185     * Should this route respond on a secure protocol?
186     *
187     * @var bool
188     *
189     */
190    protected $secure = null;
191
192    /**
193     *
194     * A callable to use for special matching logic on this individual Route.
195     *
196     * @var callable
197     *
198     */
199    protected $special;
200
201    /**
202     *
203     * Placeholder token names and regexes.
204     *
205     * @var array
206     *
207     */
208    protected $tokens = [];
209
210    /**
211     *
212     * Wildcard token name, if any.
213     *
214     * @var string
215     *
216     */
217    protected $wildcard = null;
218
219    /**
220     *
221     * When cloning the Route, reset the `$attributes` to an empty array, and
222     * clear the `$failedRule`.
223     *
224     */
225    public function __clone()
226    {
227        // $this is the cloned instance, not the original
228        $this->attributes = $this->defaults;
229        $this->failedRule = null;
230    }
231
232    /**
233     *
234     * Magic read-only for all properties.
235     *
236     * @param string $key The property to read from.
237     *
238     * @return mixed
239     *
240     */
241    public function __get($key)
242    {
243        return $this->$key;
244    }
245
246    /**
247     *
248     * Merges with the existing content types.
249     *
250     * @param string|array $accepts The content types.
251     *
252     * @return $this
253     *
254     */
255    public function accepts($accepts)
256    {
257        $this->accepts = array_merge($this->accepts, (array) $accepts);
258        return $this;
259    }
260
261    /**
262     *
263     * Merges with the existing allowed methods.
264     *
265     * @param string|array $allows The allowed HTTP methods.
266     *
267     * @return $this
268     *
269     */
270    public function allows($allows)
271    {
272        $this->allows = array_merge($this->allows, (array) $allows);
273        return $this;
274    }
275
276    /**
277     *
278     * Merges with the existing attributes.
279     *
280     * @param array $attributes The attributes to add.
281     *
282     * @return $this
283     *
284     */
285    public function attributes(array $attributes)
286    {
287        $this->attributes = array_merge($this->attributes, $attributes);
288        return $this;
289    }
290
291    /**
292     *
293     * Sets the auth value.
294     *
295     * @param mixed $auth The auth value to set.
296     *
297     * @return $this
298     *
299     */
300    public function auth($auth)
301    {
302        $this->auth = $auth;
303        return $this;
304    }
305
306    /**
307     *
308     * Merges with the existing default values for attributes.
309     *
310     * @param array $defaults Default values for attributes.
311     *
312     * @return $this
313     *
314     */
315    public function defaults(array $defaults)
316    {
317        $this->defaults = array_merge($this->defaults, $defaults);
318        return $this;
319    }
320
321    /**
322     *
323     * Merges with the existing extra key-value pairs; this merge is recursive,
324     * so the values can be arbitrarily deep.
325     *
326     * @param array $extras The extra key-value pairs.
327     *
328     * @return $this
329     *
330     */
331    public function extras(array $extras)
332    {
333        $this->extras = array_merge_recursive($this->extras, $extras);
334        return $this;
335    }
336
337    /**
338     *
339     * Sets the failed rule.
340     *
341     * @param mixed $failedRule The failed rule.
342     *
343     * @return $this
344     *
345     */
346    public function failedRule($failedRule)
347    {
348        $this->failedRule = $failedRule;
349        return $this;
350    }
351
352    /**
353     *
354     * The route leads to this handler.
355     *
356     * @param mixed $handler The handler for this route; if null, uses the
357     * route name.
358     *
359     * @return $this
360     *
361     */
362    public function handler($handler)
363    {
364        if ($handler === null) {
365            $handler = $this->name;
366        }
367        $this->handler = $handler;
368        return $this;
369    }
370
371    /**
372     *
373     * Sets the host.
374     *
375     * @param mixed $host The host.
376     *
377     * @return $this
378     *
379     */
380    public function host($host)
381    {
382        $this->host = $host;
383        return $this;
384    }
385
386    /**
387     *
388     * Sets whether or not this route should be used for matching.
389     *
390     * @param bool $isRoutable If true, this route can be matched; if not, it
391     * can be used only to generate a path.
392     *
393     * @return $this
394     *
395     */
396    public function isRoutable($isRoutable = true)
397    {
398        $this->isRoutable = (bool) $isRoutable;
399        return $this;
400    }
401
402    /**
403     *
404     * Sets the route name; immutable once set.
405     *
406     * @param string $name The route name.
407     *
408     * @return $this
409     *
410     * @throws Exception\ImmutableProperty when the name has already been set.
411     *
412     */
413    public function name($name)
414    {
415        if ($this->name !== null) {
416            $message = __CLASS__ . '::$name is immutable once set';
417            throw new Exception\ImmutableProperty($message);
418        }
419        $this->name = $this->namePrefix . $name;
420        return $this;
421    }
422
423    /**
424     *
425     * Appends to the existing name prefix; immutable once $name is set.
426     *
427     * @param string $namePrefix The name prefix to append.
428     *
429     * @return $this
430     *
431     * @throws Exception\ImmutableProperty when the name has already been set.
432     *
433     */
434    public function namePrefix($namePrefix)
435    {
436        if ($this->name !== null) {
437            $message = __CLASS__ . '::$namePrefix is immutable once $name is set';
438            throw new Exception\ImmutableProperty($message);
439        }
440        $this->namePrefix = $namePrefix;
441        return $this;
442    }
443
444    /**
445     *
446     * Sets the route path; immutable once set.
447     *
448     * @param string $path The route path.
449     *
450     * @return $this
451     *
452     * @throws Exception\ImmutableProperty when the name has already been set.
453     *
454     */
455    public function path($path)
456    {
457        if ($this->path !== null) {
458            $message = __CLASS__ . '::$path is immutable once set';
459            throw new Exception\ImmutableProperty($message);
460        }
461        $this->path = $this->pathPrefix . $path;
462        return $this;
463    }
464
465    /**
466     *
467     * Appends to the existing path prefix; immutable once $path is set.
468     *
469     * @param string $pathPrefix The path prefix to append.
470     *
471     * @return $this
472     *
473     * @throws Exception\ImmutableProperty when the path has already been set.
474     *
475     */
476    public function pathPrefix($pathPrefix)
477    {
478        if ($this->path !== null) {
479            $message = __CLASS__ . '::$pathPrefix is immutable once $path is set';
480            throw new Exception\ImmutableProperty($message);
481        }
482        $this->pathPrefix = $pathPrefix;
483        return $this;
484    }
485
486    /**
487     *
488     * Sets whether or not the route must be secure.
489     *
490     * @param bool|null $secure If true, the server must indicate an HTTPS request;
491     * if false, it must *not* be HTTPS; if null, it doesn't matter.
492     *
493     * @return $this
494     *
495     */
496    public function secure($secure = true)
497    {
498        $this->secure = ($secure === null) ? null : (bool) $secure;
499        return $this;
500    }
501
502    /**
503     *
504     * A callable to use for special matching logic on this individual Route.
505     *
506     * @param callable|null $special A callable to invoke for special matching
507     * logic on this individiual route. The callable should have the signature
508     * `function ($request, $route) : bool`. (Use null or another empty value
509     * to indicate there is no special matching logic.)
510     *
511     * @return $this
512     *
513     */
514    public function special($special)
515    {
516        $this->special = $special;
517        return $this;
518    }
519
520    /**
521     *
522     * Merges with the existing tokens.
523     *
524     * @param array $tokens The tokens.
525     *
526     * @return $this
527     *
528     */
529    public function tokens(array $tokens)
530    {
531        $this->tokens = array_merge($this->tokens, $tokens);
532        return $this;
533    }
534
535    /**
536     *
537     * Sets the name of the wildcard token, if any.
538     *
539     * @param string $wildcard The name of the wildcard token, if any.
540     *
541     * @return $this
542     *
543     */
544    public function wildcard($wildcard)
545    {
546        $this->wildcard = $wildcard;
547        return $this;
548    }
549}
550