Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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