1<?php
2
3namespace Illuminate\Routing;
4
5use Closure;
6use Illuminate\Container\Container;
7use Illuminate\Http\Exceptions\HttpResponseException;
8use Illuminate\Http\Request;
9use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;
10use Illuminate\Routing\Matching\HostValidator;
11use Illuminate\Routing\Matching\MethodValidator;
12use Illuminate\Routing\Matching\SchemeValidator;
13use Illuminate\Routing\Matching\UriValidator;
14use Illuminate\Support\Arr;
15use Illuminate\Support\Str;
16use Illuminate\Support\Traits\Macroable;
17use LogicException;
18use Opis\Closure\SerializableClosure;
19use ReflectionFunction;
20use Symfony\Component\Routing\Route as SymfonyRoute;
21
22class Route
23{
24    use CreatesRegularExpressionRouteConstraints, Macroable, RouteDependencyResolverTrait;
25
26    /**
27     * The URI pattern the route responds to.
28     *
29     * @var string
30     */
31    public $uri;
32
33    /**
34     * The HTTP methods the route responds to.
35     *
36     * @var array
37     */
38    public $methods;
39
40    /**
41     * The route action array.
42     *
43     * @var array
44     */
45    public $action;
46
47    /**
48     * Indicates whether the route is a fallback route.
49     *
50     * @var bool
51     */
52    public $isFallback = false;
53
54    /**
55     * The controller instance.
56     *
57     * @var mixed
58     */
59    public $controller;
60
61    /**
62     * The default values for the route.
63     *
64     * @var array
65     */
66    public $defaults = [];
67
68    /**
69     * The regular expression requirements.
70     *
71     * @var array
72     */
73    public $wheres = [];
74
75    /**
76     * The array of matched parameters.
77     *
78     * @var array|null
79     */
80    public $parameters;
81
82    /**
83     * The parameter names for the route.
84     *
85     * @var array|null
86     */
87    public $parameterNames;
88
89    /**
90     * The array of the matched parameters' original values.
91     *
92     * @var array
93     */
94    protected $originalParameters;
95
96    /**
97     * Indicates the maximum number of seconds the route should acquire a session lock for.
98     *
99     * @var int|null
100     */
101    protected $lockSeconds;
102
103    /**
104     * Indicates the maximum number of seconds the route should wait while attempting to acquire a session lock.
105     *
106     * @var int|null
107     */
108    protected $waitSeconds;
109
110    /**
111     * The computed gathered middleware.
112     *
113     * @var array|null
114     */
115    public $computedMiddleware;
116
117    /**
118     * The compiled version of the route.
119     *
120     * @var \Symfony\Component\Routing\CompiledRoute
121     */
122    public $compiled;
123
124    /**
125     * The router instance used by the route.
126     *
127     * @var \Illuminate\Routing\Router
128     */
129    protected $router;
130
131    /**
132     * The container instance used by the route.
133     *
134     * @var \Illuminate\Container\Container
135     */
136    protected $container;
137
138    /**
139     * The fields that implicit binding should use for a given parameter.
140     *
141     * @var array
142     */
143    protected $bindingFields = [];
144
145    /**
146     * The validators used by the routes.
147     *
148     * @var array
149     */
150    public static $validators;
151
152    /**
153     * Create a new Route instance.
154     *
155     * @param  array|string  $methods
156     * @param  string  $uri
157     * @param  \Closure|array  $action
158     * @return void
159     */
160    public function __construct($methods, $uri, $action)
161    {
162        $this->uri = $uri;
163        $this->methods = (array) $methods;
164        $this->action = Arr::except($this->parseAction($action), ['prefix']);
165
166        if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) {
167            $this->methods[] = 'HEAD';
168        }
169
170        $this->prefix(is_array($action) ? Arr::get($action, 'prefix') : '');
171    }
172
173    /**
174     * Parse the route action into a standard array.
175     *
176     * @param  callable|array|null  $action
177     * @return array
178     *
179     * @throws \UnexpectedValueException
180     */
181    protected function parseAction($action)
182    {
183        return RouteAction::parse($this->uri, $action);
184    }
185
186    /**
187     * Run the route action and return the response.
188     *
189     * @return mixed
190     */
191    public function run()
192    {
193        $this->container = $this->container ?: new Container;
194
195        try {
196            if ($this->isControllerAction()) {
197                return $this->runController();
198            }
199
200            return $this->runCallable();
201        } catch (HttpResponseException $e) {
202            return $e->getResponse();
203        }
204    }
205
206    /**
207     * Checks whether the route's action is a controller.
208     *
209     * @return bool
210     */
211    protected function isControllerAction()
212    {
213        return is_string($this->action['uses']) && ! $this->isSerializedClosure();
214    }
215
216    /**
217     * Run the route action and return the response.
218     *
219     * @return mixed
220     */
221    protected function runCallable()
222    {
223        $callable = $this->action['uses'];
224
225        if ($this->isSerializedClosure()) {
226            $callable = unserialize($this->action['uses'])->getClosure();
227        }
228
229        return $callable(...array_values($this->resolveMethodDependencies(
230            $this->parametersWithoutNulls(), new ReflectionFunction($callable)
231        )));
232    }
233
234    /**
235     * Determine if the route action is a serialized Closure.
236     *
237     * @return bool
238     */
239    protected function isSerializedClosure()
240    {
241        return RouteAction::containsSerializedClosure($this->action);
242    }
243
244    /**
245     * Run the route action and return the response.
246     *
247     * @return mixed
248     *
249     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
250     */
251    protected function runController()
252    {
253        return $this->controllerDispatcher()->dispatch(
254            $this, $this->getController(), $this->getControllerMethod()
255        );
256    }
257
258    /**
259     * Get the controller instance for the route.
260     *
261     * @return mixed
262     */
263    public function getController()
264    {
265        if (! $this->controller) {
266            $class = $this->parseControllerCallback()[0];
267
268            $this->controller = $this->container->make(ltrim($class, '\\'));
269        }
270
271        return $this->controller;
272    }
273
274    /**
275     * Get the controller method used for the route.
276     *
277     * @return string
278     */
279    protected function getControllerMethod()
280    {
281        return $this->parseControllerCallback()[1];
282    }
283
284    /**
285     * Parse the controller.
286     *
287     * @return array
288     */
289    protected function parseControllerCallback()
290    {
291        return Str::parseCallback($this->action['uses']);
292    }
293
294    /**
295     * Determine if the route matches a given request.
296     *
297     * @param  \Illuminate\Http\Request  $request
298     * @param  bool  $includingMethod
299     * @return bool
300     */
301    public function matches(Request $request, $includingMethod = true)
302    {
303        $this->compileRoute();
304
305        foreach (self::getValidators() as $validator) {
306            if (! $includingMethod && $validator instanceof MethodValidator) {
307                continue;
308            }
309
310            if (! $validator->matches($this, $request)) {
311                return false;
312            }
313        }
314
315        return true;
316    }
317
318    /**
319     * Compile the route into a Symfony CompiledRoute instance.
320     *
321     * @return \Symfony\Component\Routing\CompiledRoute
322     */
323    protected function compileRoute()
324    {
325        if (! $this->compiled) {
326            $this->compiled = $this->toSymfonyRoute()->compile();
327        }
328
329        return $this->compiled;
330    }
331
332    /**
333     * Bind the route to a given request for execution.
334     *
335     * @param  \Illuminate\Http\Request  $request
336     * @return $this
337     */
338    public function bind(Request $request)
339    {
340        $this->compileRoute();
341
342        $this->parameters = (new RouteParameterBinder($this))
343                        ->parameters($request);
344
345        $this->originalParameters = $this->parameters;
346
347        return $this;
348    }
349
350    /**
351     * Determine if the route has parameters.
352     *
353     * @return bool
354     */
355    public function hasParameters()
356    {
357        return isset($this->parameters);
358    }
359
360    /**
361     * Determine a given parameter exists from the route.
362     *
363     * @param  string  $name
364     * @return bool
365     */
366    public function hasParameter($name)
367    {
368        if ($this->hasParameters()) {
369            return array_key_exists($name, $this->parameters());
370        }
371
372        return false;
373    }
374
375    /**
376     * Get a given parameter from the route.
377     *
378     * @param  string  $name
379     * @param  string|object|null  $default
380     * @return string|object|null
381     */
382    public function parameter($name, $default = null)
383    {
384        return Arr::get($this->parameters(), $name, $default);
385    }
386
387    /**
388     * Get original value of a given parameter from the route.
389     *
390     * @param  string  $name
391     * @param  string|null  $default
392     * @return string|null
393     */
394    public function originalParameter($name, $default = null)
395    {
396        return Arr::get($this->originalParameters(), $name, $default);
397    }
398
399    /**
400     * Set a parameter to the given value.
401     *
402     * @param  string  $name
403     * @param  string|object|null  $value
404     * @return void
405     */
406    public function setParameter($name, $value)
407    {
408        $this->parameters();
409
410        $this->parameters[$name] = $value;
411    }
412
413    /**
414     * Unset a parameter on the route if it is set.
415     *
416     * @param  string  $name
417     * @return void
418     */
419    public function forgetParameter($name)
420    {
421        $this->parameters();
422
423        unset($this->parameters[$name]);
424    }
425
426    /**
427     * Get the key / value list of parameters for the route.
428     *
429     * @return array
430     *
431     * @throws \LogicException
432     */
433    public function parameters()
434    {
435        if (isset($this->parameters)) {
436            return $this->parameters;
437        }
438
439        throw new LogicException('Route is not bound.');
440    }
441
442    /**
443     * Get the key / value list of original parameters for the route.
444     *
445     * @return array
446     *
447     * @throws \LogicException
448     */
449    public function originalParameters()
450    {
451        if (isset($this->originalParameters)) {
452            return $this->originalParameters;
453        }
454
455        throw new LogicException('Route is not bound.');
456    }
457
458    /**
459     * Get the key / value list of parameters without null values.
460     *
461     * @return array
462     */
463    public function parametersWithoutNulls()
464    {
465        return array_filter($this->parameters(), function ($p) {
466            return ! is_null($p);
467        });
468    }
469
470    /**
471     * Get all of the parameter names for the route.
472     *
473     * @return array
474     */
475    public function parameterNames()
476    {
477        if (isset($this->parameterNames)) {
478            return $this->parameterNames;
479        }
480
481        return $this->parameterNames = $this->compileParameterNames();
482    }
483
484    /**
485     * Get the parameter names for the route.
486     *
487     * @return array
488     */
489    protected function compileParameterNames()
490    {
491        preg_match_all('/\{(.*?)\}/', $this->getDomain().$this->uri, $matches);
492
493        return array_map(function ($m) {
494            return trim($m, '?');
495        }, $matches[1]);
496    }
497
498    /**
499     * Get the parameters that are listed in the route / controller signature.
500     *
501     * @param  string|null  $subClass
502     * @return array
503     */
504    public function signatureParameters($subClass = null)
505    {
506        return RouteSignatureParameters::fromAction($this->action, $subClass);
507    }
508
509    /**
510     * Get the binding field for the given parameter.
511     *
512     * @param  string|int  $parameter
513     * @return string|null
514     */
515    public function bindingFieldFor($parameter)
516    {
517        $fields = is_int($parameter) ? array_values($this->bindingFields) : $this->bindingFields;
518
519        return $fields[$parameter] ?? null;
520    }
521
522    /**
523     * Get the binding fields for the route.
524     *
525     * @return array
526     */
527    public function bindingFields()
528    {
529        return $this->bindingFields ?? [];
530    }
531
532    /**
533     * Set the binding fields for the route.
534     *
535     * @param  array  $bindingFields
536     * @return $this
537     */
538    public function setBindingFields(array $bindingFields)
539    {
540        $this->bindingFields = $bindingFields;
541
542        return $this;
543    }
544
545    /**
546     * Get the parent parameter of the given parameter.
547     *
548     * @param  string  $parameter
549     * @return string
550     */
551    public function parentOfParameter($parameter)
552    {
553        $key = array_search($parameter, array_keys($this->parameters));
554
555        if ($key === 0) {
556            return;
557        }
558
559        return array_values($this->parameters)[$key - 1];
560    }
561
562    /**
563     * Set a default value for the route.
564     *
565     * @param  string  $key
566     * @param  mixed  $value
567     * @return $this
568     */
569    public function defaults($key, $value)
570    {
571        $this->defaults[$key] = $value;
572
573        return $this;
574    }
575
576    /**
577     * Set the default values for the route.
578     *
579     * @param  array  $defaults
580     * @return $this
581     */
582    public function setDefaults(array $defaults)
583    {
584        $this->defaults = $defaults;
585
586        return $this;
587    }
588
589    /**
590     * Set a regular expression requirement on the route.
591     *
592     * @param  array|string  $name
593     * @param  string|null  $expression
594     * @return $this
595     */
596    public function where($name, $expression = null)
597    {
598        foreach ($this->parseWhere($name, $expression) as $name => $expression) {
599            $this->wheres[$name] = $expression;
600        }
601
602        return $this;
603    }
604
605    /**
606     * Parse arguments to the where method into an array.
607     *
608     * @param  array|string  $name
609     * @param  string  $expression
610     * @return array
611     */
612    protected function parseWhere($name, $expression)
613    {
614        return is_array($name) ? $name : [$name => $expression];
615    }
616
617    /**
618     * Set a list of regular expression requirements on the route.
619     *
620     * @param  array  $wheres
621     * @return $this
622     */
623    public function setWheres(array $wheres)
624    {
625        foreach ($wheres as $name => $expression) {
626            $this->where($name, $expression);
627        }
628
629        return $this;
630    }
631
632    /**
633     * Mark this route as a fallback route.
634     *
635     * @return $this
636     */
637    public function fallback()
638    {
639        $this->isFallback = true;
640
641        return $this;
642    }
643
644    /**
645     * Set the fallback value.
646     *
647     * @param  bool  $isFallback
648     * @return $this
649     */
650    public function setFallback($isFallback)
651    {
652        $this->isFallback = $isFallback;
653
654        return $this;
655    }
656
657    /**
658     * Get the HTTP verbs the route responds to.
659     *
660     * @return array
661     */
662    public function methods()
663    {
664        return $this->methods;
665    }
666
667    /**
668     * Determine if the route only responds to HTTP requests.
669     *
670     * @return bool
671     */
672    public function httpOnly()
673    {
674        return in_array('http', $this->action, true);
675    }
676
677    /**
678     * Determine if the route only responds to HTTPS requests.
679     *
680     * @return bool
681     */
682    public function httpsOnly()
683    {
684        return $this->secure();
685    }
686
687    /**
688     * Determine if the route only responds to HTTPS requests.
689     *
690     * @return bool
691     */
692    public function secure()
693    {
694        return in_array('https', $this->action, true);
695    }
696
697    /**
698     * Get or set the domain for the route.
699     *
700     * @param  string|null  $domain
701     * @return $this|string|null
702     */
703    public function domain($domain = null)
704    {
705        if (is_null($domain)) {
706            return $this->getDomain();
707        }
708
709        $parsed = RouteUri::parse($domain);
710
711        $this->action['domain'] = $parsed->uri;
712
713        $this->bindingFields = array_merge(
714            $this->bindingFields, $parsed->bindingFields
715        );
716
717        return $this;
718    }
719
720    /**
721     * Get the domain defined for the route.
722     *
723     * @return string|null
724     */
725    public function getDomain()
726    {
727        return isset($this->action['domain'])
728                ? str_replace(['http://', 'https://'], '', $this->action['domain']) : null;
729    }
730
731    /**
732     * Get the prefix of the route instance.
733     *
734     * @return string|null
735     */
736    public function getPrefix()
737    {
738        return $this->action['prefix'] ?? null;
739    }
740
741    /**
742     * Add a prefix to the route URI.
743     *
744     * @param  string  $prefix
745     * @return $this
746     */
747    public function prefix($prefix)
748    {
749        $prefix = $prefix ?? '';
750
751        $this->updatePrefixOnAction($prefix);
752
753        $uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/');
754
755        return $this->setUri($uri !== '/' ? trim($uri, '/') : $uri);
756    }
757
758    /**
759     * Update the "prefix" attribute on the action array.
760     *
761     * @param  string  $prefix
762     * @return void
763     */
764    protected function updatePrefixOnAction($prefix)
765    {
766        if (! empty($newPrefix = trim(rtrim($prefix, '/').'/'.ltrim($this->action['prefix'] ?? '', '/'), '/'))) {
767            $this->action['prefix'] = $newPrefix;
768        }
769    }
770
771    /**
772     * Get the URI associated with the route.
773     *
774     * @return string
775     */
776    public function uri()
777    {
778        return $this->uri;
779    }
780
781    /**
782     * Set the URI that the route responds to.
783     *
784     * @param  string  $uri
785     * @return $this
786     */
787    public function setUri($uri)
788    {
789        $this->uri = $this->parseUri($uri);
790
791        return $this;
792    }
793
794    /**
795     * Parse the route URI and normalize / store any implicit binding fields.
796     *
797     * @param  string  $uri
798     * @return string
799     */
800    protected function parseUri($uri)
801    {
802        $this->bindingFields = [];
803
804        return tap(RouteUri::parse($uri), function ($uri) {
805            $this->bindingFields = $uri->bindingFields;
806        })->uri;
807    }
808
809    /**
810     * Get the name of the route instance.
811     *
812     * @return string|null
813     */
814    public function getName()
815    {
816        return $this->action['as'] ?? null;
817    }
818
819    /**
820     * Add or change the route name.
821     *
822     * @param  string  $name
823     * @return $this
824     */
825    public function name($name)
826    {
827        $this->action['as'] = isset($this->action['as']) ? $this->action['as'].$name : $name;
828
829        return $this;
830    }
831
832    /**
833     * Determine whether the route's name matches the given patterns.
834     *
835     * @param  mixed  ...$patterns
836     * @return bool
837     */
838    public function named(...$patterns)
839    {
840        if (is_null($routeName = $this->getName())) {
841            return false;
842        }
843
844        foreach ($patterns as $pattern) {
845            if (Str::is($pattern, $routeName)) {
846                return true;
847            }
848        }
849
850        return false;
851    }
852
853    /**
854     * Set the handler for the route.
855     *
856     * @param  \Closure|array|string  $action
857     * @return $this
858     */
859    public function uses($action)
860    {
861        if (is_array($action)) {
862            $action = $action[0].'@'.$action[1];
863        }
864
865        $action = is_string($action) ? $this->addGroupNamespaceToStringUses($action) : $action;
866
867        return $this->setAction(array_merge($this->action, $this->parseAction([
868            'uses' => $action,
869            'controller' => $action,
870        ])));
871    }
872
873    /**
874     * Parse a string based action for the "uses" fluent method.
875     *
876     * @param  string  $action
877     * @return string
878     */
879    protected function addGroupNamespaceToStringUses($action)
880    {
881        $groupStack = last($this->router->getGroupStack());
882
883        if (isset($groupStack['namespace']) && strpos($action, '\\') !== 0) {
884            return $groupStack['namespace'].'\\'.$action;
885        }
886
887        return $action;
888    }
889
890    /**
891     * Get the action name for the route.
892     *
893     * @return string
894     */
895    public function getActionName()
896    {
897        return $this->action['controller'] ?? 'Closure';
898    }
899
900    /**
901     * Get the method name of the route action.
902     *
903     * @return string
904     */
905    public function getActionMethod()
906    {
907        return Arr::last(explode('@', $this->getActionName()));
908    }
909
910    /**
911     * Get the action array or one of its properties for the route.
912     *
913     * @param  string|null  $key
914     * @return mixed
915     */
916    public function getAction($key = null)
917    {
918        return Arr::get($this->action, $key);
919    }
920
921    /**
922     * Set the action array for the route.
923     *
924     * @param  array  $action
925     * @return $this
926     */
927    public function setAction(array $action)
928    {
929        $this->action = $action;
930
931        if (isset($this->action['domain'])) {
932            $this->domain($this->action['domain']);
933        }
934
935        return $this;
936    }
937
938    /**
939     * Get the value of the action that should be taken on a missing model exception.
940     *
941     * @return \Closure|null
942     */
943    public function getMissing()
944    {
945        $missing = $this->action['missing'] ?? null;
946
947        return is_string($missing) &&
948            Str::startsWith($missing, 'C:32:"Opis\\Closure\\SerializableClosure')
949                ? unserialize($missing)
950                : $missing;
951    }
952
953    /**
954     * Define the callable that should be invoked on a missing model exception.
955     *
956     * @param  \Closure  $missing
957     * @return $this
958     */
959    public function missing($missing)
960    {
961        $this->action['missing'] = $missing;
962
963        return $this;
964    }
965
966    /**
967     * Get all middleware, including the ones from the controller.
968     *
969     * @return array
970     */
971    public function gatherMiddleware()
972    {
973        if (! is_null($this->computedMiddleware)) {
974            return $this->computedMiddleware;
975        }
976
977        $this->computedMiddleware = [];
978
979        return $this->computedMiddleware = Router::uniqueMiddleware(array_merge(
980            $this->middleware(), $this->controllerMiddleware()
981        ));
982    }
983
984    /**
985     * Get or set the middlewares attached to the route.
986     *
987     * @param  array|string|null  $middleware
988     * @return $this|array
989     */
990    public function middleware($middleware = null)
991    {
992        if (is_null($middleware)) {
993            return (array) ($this->action['middleware'] ?? []);
994        }
995
996        if (is_string($middleware)) {
997            $middleware = func_get_args();
998        }
999
1000        $this->action['middleware'] = array_merge(
1001            (array) ($this->action['middleware'] ?? []), $middleware
1002        );
1003
1004        return $this;
1005    }
1006
1007    /**
1008     * Get the middleware for the route's controller.
1009     *
1010     * @return array
1011     */
1012    public function controllerMiddleware()
1013    {
1014        if (! $this->isControllerAction()) {
1015            return [];
1016        }
1017
1018        return $this->controllerDispatcher()->getMiddleware(
1019            $this->getController(), $this->getControllerMethod()
1020        );
1021    }
1022
1023    /**
1024     * Specify middleware that should be removed from the given route.
1025     *
1026     * @param  array|string  $middleware
1027     * @return $this|array
1028     */
1029    public function withoutMiddleware($middleware)
1030    {
1031        $this->action['excluded_middleware'] = array_merge(
1032            (array) ($this->action['excluded_middleware'] ?? []), Arr::wrap($middleware)
1033        );
1034
1035        return $this;
1036    }
1037
1038    /**
1039     * Get the middleware should be removed from the route.
1040     *
1041     * @return array
1042     */
1043    public function excludedMiddleware()
1044    {
1045        return (array) ($this->action['excluded_middleware'] ?? []);
1046    }
1047
1048    /**
1049     * Specify that the route should not allow concurrent requests from the same session.
1050     *
1051     * @param  int|null  $lockSeconds
1052     * @param  int|null  $waitSeconds
1053     * @return $this
1054     */
1055    public function block($lockSeconds = 10, $waitSeconds = 10)
1056    {
1057        $this->lockSeconds = $lockSeconds;
1058        $this->waitSeconds = $waitSeconds;
1059
1060        return $this;
1061    }
1062
1063    /**
1064     * Specify that the route should allow concurrent requests from the same session.
1065     *
1066     * @return $this
1067     */
1068    public function withoutBlocking()
1069    {
1070        return $this->block(null, null);
1071    }
1072
1073    /**
1074     * Get the maximum number of seconds the route's session lock should be held for.
1075     *
1076     * @return int|null
1077     */
1078    public function locksFor()
1079    {
1080        return $this->lockSeconds;
1081    }
1082
1083    /**
1084     * Get the maximum number of seconds to wait while attempting to acquire a session lock.
1085     *
1086     * @return int|null
1087     */
1088    public function waitsFor()
1089    {
1090        return $this->waitSeconds;
1091    }
1092
1093    /**
1094     * Get the dispatcher for the route's controller.
1095     *
1096     * @return \Illuminate\Routing\Contracts\ControllerDispatcher
1097     */
1098    public function controllerDispatcher()
1099    {
1100        if ($this->container->bound(ControllerDispatcherContract::class)) {
1101            return $this->container->make(ControllerDispatcherContract::class);
1102        }
1103
1104        return new ControllerDispatcher($this->container);
1105    }
1106
1107    /**
1108     * Get the route validators for the instance.
1109     *
1110     * @return array
1111     */
1112    public static function getValidators()
1113    {
1114        if (isset(static::$validators)) {
1115            return static::$validators;
1116        }
1117
1118        // To match the route, we will use a chain of responsibility pattern with the
1119        // validator implementations. We will spin through each one making sure it
1120        // passes and then we will know if the route as a whole matches request.
1121        return static::$validators = [
1122            new UriValidator, new MethodValidator,
1123            new SchemeValidator, new HostValidator,
1124        ];
1125    }
1126
1127    /**
1128     * Convert the route to a Symfony route.
1129     *
1130     * @return \Symfony\Component\Routing\Route
1131     */
1132    public function toSymfonyRoute()
1133    {
1134        return new SymfonyRoute(
1135            preg_replace('/\{(\w+?)\?\}/', '{$1}', $this->uri()), $this->getOptionalParameterNames(),
1136            $this->wheres, ['utf8' => true, 'action' => $this->action],
1137            $this->getDomain() ?: '', [], $this->methods
1138        );
1139    }
1140
1141    /**
1142     * Get the optional parameter names for the route.
1143     *
1144     * @return array
1145     */
1146    protected function getOptionalParameterNames()
1147    {
1148        preg_match_all('/\{(\w+?)\?\}/', $this->uri(), $matches);
1149
1150        return isset($matches[1]) ? array_fill_keys($matches[1], null) : [];
1151    }
1152
1153    /**
1154     * Get the compiled version of the route.
1155     *
1156     * @return \Symfony\Component\Routing\CompiledRoute
1157     */
1158    public function getCompiled()
1159    {
1160        return $this->compiled;
1161    }
1162
1163    /**
1164     * Set the router instance on the route.
1165     *
1166     * @param  \Illuminate\Routing\Router  $router
1167     * @return $this
1168     */
1169    public function setRouter(Router $router)
1170    {
1171        $this->router = $router;
1172
1173        return $this;
1174    }
1175
1176    /**
1177     * Set the container instance on the route.
1178     *
1179     * @param  \Illuminate\Container\Container  $container
1180     * @return $this
1181     */
1182    public function setContainer(Container $container)
1183    {
1184        $this->container = $container;
1185
1186        return $this;
1187    }
1188
1189    /**
1190     * Prepare the route instance for serialization.
1191     *
1192     * @return void
1193     *
1194     * @throws \LogicException
1195     */
1196    public function prepareForSerialization()
1197    {
1198        if ($this->action['uses'] instanceof Closure) {
1199            $this->action['uses'] = serialize(new SerializableClosure($this->action['uses']));
1200        }
1201
1202        if (isset($this->action['missing']) && $this->action['missing'] instanceof Closure) {
1203            $this->action['missing'] = serialize(new SerializableClosure($this->action['missing']));
1204        }
1205
1206        $this->compileRoute();
1207
1208        unset($this->router, $this->container);
1209    }
1210
1211    /**
1212     * Dynamically access route parameters.
1213     *
1214     * @param  string  $key
1215     * @return mixed
1216     */
1217    public function __get($key)
1218    {
1219        return $this->parameter($key);
1220    }
1221}
1222