Passed
Push — main ( 4cd58e...3399b6 )
by Dimitri
03:02
created

RouteCollection::setAutoRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Router;
13
14
use BlitzPHP\Autoloader\Locator;
15
use BlitzPHP\Contracts\Router\RouteCollectionInterface;
16
use BlitzPHP\Exceptions\RouterException;
17
use BlitzPHP\Loader\Services;
18
use Closure;
19
use InvalidArgumentException;
20
21
class RouteCollection implements RouteCollectionInterface
22
{
23
    /**
24
     * L'espace de noms à ajouter à tous les contrôleurs.
25
     * Par défaut, les espaces de noms globaux (\)
26
     */
27
    protected string $defaultNamespace = '\\';
28
29
    /**
30
     * Le nom du contrôleur par défaut à utiliser
31
     * lorsqu'aucun autre contrôleur n'est spécifié.
32
     *
33
     * Non utilisé ici. Valeur d'intercommunication pour la classe Routeur.
34
     */
35
    protected string $defaultController = 'Home';
36
37
    /**
38
     * Le nom de la méthode par défaut à utiliser
39
     * lorsqu'aucune autre méthode n'a été spécifiée.
40
     *
41
     * Non utilisé ici. Valeur d'intercommunication pour la classe Routeur.
42
     */
43
    protected string $defaultMethod = 'index';
44
45
    /**
46
     * L'espace réservé utilisé lors du routage des "ressources"
47
     * lorsqu'aucun autre espace réservé n'a été spécifié.
48
     */
49
    protected string $defaultPlaceholder = 'any';
50
51
    /**
52
     * S'il faut convertir les tirets en traits de soulignement dans l'URI.
53
     *
54
     * Non utilisé ici. Valeur d'intercommunication pour la classe Routeur.
55
     */
56
    protected bool $translateURIDashes = true;
57
58
    /**
59
     * S'il faut faire correspondre l'URI aux contrôleurs
60
     * lorsqu'il ne correspond pas aux itinéraires définis.
61
     *
62
     * Non utilisé ici. Valeur d'intercommunication pour la classe Routeur.
63
     */
64
    protected bool $autoRoute = true;
65
66
    /**
67
     * Un appelable qui sera affiché
68
     * lorsque la route ne peut pas être matchée.
69
     *
70
     * @var Closure|string
71
     */
72
    protected $override404;
73
74
    /**
75
     * Espaces réservés définis pouvant être utilisés.
76
     */
77
    protected array $placeholders = [
78
        'any'      => '.*',
79
        'segment'  => '[^/]+',
80
        'alphanum' => '[a-zA-Z0-9]+',
81
        'num'      => '[0-9]+',
82
        'alpha'    => '[a-zA-Z]+',
83
        'hash'     => '[^/]+',
84
        'slug'     => '[a-z0-9-]+',
85
    ];
86
87
    /**
88
     * Tableau de toutes les routes et leurs mappages.
89
     * 
90
     * @example 
91
     * ```php 
92
     * [
93
     *     verb => [
94
     *         routeName => [
95
     *             'route' => [
96
     *                 routeKey(regex) => handler,
97
     *             ],
98
     *             'redirect' => statusCode,
99
     *         ]
100
     *     ],
101
     * ]
102
     * ```
103
     */
104
    protected array $routes = [
105
        '*'       => [],
106
        'options' => [],
107
        'get'     => [],
108
        'head'    => [],
109
        'post'    => [],
110
        'put'     => [],
111
        'delete'  => [],
112
        'trace'   => [],
113
        'connect' => [],
114
        'cli'     => [],
115
    ];
116
117
    /**
118
     * Tableaux des options des routes.
119
     * 
120
     * @example 
121
     * ```php
122
     * [
123
     *     verb => [
124
     *         routeKey(regex) => [
125
     *             key => value,
126
     *         ]
127
     *     ],
128
     * ]
129
     * ```
130
     */
131
    protected array $routesOptions = [];
132
133
    /**
134
     * La méthode actuelle par laquelle le script est appelé.
135
     */
136
    protected string $HTTPVerb = '*';
137
138
    /**
139
     * La liste par défaut des méthodes HTTP (et CLI pour l'utilisation de la ligne de commande)
140
     * qui est autorisé si aucune autre méthode n'est fournie.
141
     */
142
    protected array $defaultHTTPMethods = [
143
        'options',
144
        'get',
145
        'head',
146
        'post',
147
        'put',
148
        'delete',
149
        'trace',
150
        'connect',
151
        'cli',
152
    ];
153
154
    /**
155
     * Le nom du groupe de route courant
156
     *
157
     * @var string|null
158
     */
159
    protected $group;
160
161
    /**
162
     * Le sous domaine courant
163
     *
164
     * @var string|null
165
     */
166
    protected $currentSubdomain;
167
168
    /**
169
     * Stocke une copie des options actuelles en cours appliqué lors de la création.
170
     *
171
     * @var array|null
172
     */
173
    protected $currentOptions;
174
175
    /**
176
     * Un petit booster de performances.
177
     */
178
    protected bool $didDiscover = false;
179
180
    /**
181
     * Descripteur du localisateur de fichiers à utiliser.
182
     *
183
     * @var Locator
184
     */
185
    protected $locator;
186
187
    /**
188
     * Drapeau pour trier les routes par priorité.
189
     */
190
    protected bool $prioritize = false;
191
192
    /**
193
     * Indicateur de détection de priorité de route.
194
     */
195
    protected bool $prioritizeDetected = false;
196
197
    /**
198
     * Drapeau pour limiter ou non les routes avec l'espace réservé {locale} vers App::$supportedLocales
199
     */
200
    protected bool $useSupportedLocalesOnly = false;
201
202
    /**
203
     * Le nom d'hôte actuel de $_SERVER['HTTP_HOST']
204
     */
205
    private ?string $httpHost = null;
206
207
    /**
208
     * Constructor
209
     */
210
    public function __construct()
211
    {
212
        $this->locator  = Services::locator();
213
        $this->httpHost = Services::request()->getEnv('HTTP_HOST');
214
    }
215
216
    /**
217
     * Charge le fichier des routes principales et découvre les routes.
218
     *
219
     * Charge une seule fois sauf réinitialisation.
220
     */
221
    public function loadRoutes(string $routesFile = CONFIG_PATH . 'routes.php'): self
222
    {
223
        if ($this->didDiscover) {
224
            return $this;
225
        }
226
227
        // Nous avons besoin de cette variable dans la portée locale pour que les fichiers de route puissent y accéder.
228
        $routes = $this;
229
230
        require $routesFile;
231
232
        $this->discoverRoutes();
233
234
        return $this;
235
    }
236
237
    /**
238
     * Réinitialisez les routes, afin qu'un cas de test puisse fournir le
239
     * ceux explicites nécessaires pour cela.
240
     */
241
    public function resetRoutes()
242
    {
243
        $this->routes = ['*' => []];
244
245
        foreach ($this->defaultHTTPMethods as $verb) {
246
            $this->routes[$verb] = [];
247
        }
248
249
        $this->prioritizeDetected = false;
250
        $this->didDiscover        = false;
251
    }
252
    
253
    /**
254
     * {@inheritDoc}
255
     */
256
    public function addPlaceholder($placeholder, ?string $pattern = null): self
257
    {
258
        if (! is_array($placeholder)) {
259
            $placeholder = [$placeholder => $pattern];
260
        }
261
262
        $this->placeholders = array_merge($this->placeholders, $placeholder);
263
264
        return $this;
265
    }
266
267
    /**
268
     * {@inheritDoc}
269
     */
270
    public function setDefaultNamespace(string $value): self
271
    {
272
        $this->defaultNamespace = esc(strip_tags($value));
273
        $this->defaultNamespace = rtrim($this->defaultNamespace, '\\') . '\\';
274
275
        return $this;
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281
    public function setDefaultController(string $value): self
282
    {
283
        $this->defaultController = esc(strip_tags($value));
284
285
        return $this;
286
    }
287
288
    /**
289
     * {@inheritDoc}
290
     */
291
    public function setDefaultMethod(string $value): self
292
    {
293
        $this->defaultMethod = esc(strip_tags($value));
294
295
        return $this;
296
    }
297
298
    /**
299
     * {@inheritDoc}
300
     */
301
    public function setTranslateURIDashes(bool $value): self
302
    {
303
        $this->translateURIDashes = $value;
304
305
        return $this;
306
    }
307
308
    /**
309
     * {@inheritDoc}
310
     */
311
    public function setAutoRoute(bool $value): self
312
    {
313
        $this->autoRoute = $value;
314
315
        return $this;
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321
    public function set404Override($callable = null): self
322
    {
323
        $this->override404 = $callable;
324
325
        return $this;
326
    }
327
328
    /**
329
     * {@inheritDoc}
330
     */
331
    public function get404Override()
332
    {
333
        return $this->override404;
334
    }
335
336
    /**
337
     * Tentera de découvrir d'éventuelles routes supplémentaires, soit par
338
     * les espaces de noms PSR4 locaux ou via des packages Composer sélectionnés.
339
     */
340
    protected function discoverRoutes()
341
    {
342
        if ($this->didDiscover) {
343
            return;
344
        }
345
346
        // Nous avons besoin de cette variable dans la portée locale pour que les fichiers de route puissent y accéder.
347
        $routes = $this;
0 ignored issues
show
Unused Code introduced by
The assignment to $routes is dead and can be removed.
Loading history...
348
349
        $files    = $this->locator->search('Config/routes.php');
350
        $excludes = [
351
            APP_PATH  . 'Config' . DS . 'routes.php',
352
            SYST_PATH . 'Config' . DS . 'routes.php',
353
        ];
354
355
        foreach ($files as $file) {
356
            // N'incluez plus notre fichier principal...
357
            if (in_array($file, $excludes, true)) {
358
                continue;
359
            }
360
361
            include_once $file;
362
        }
363
364
        $this->didDiscover = true;
365
    }
366
367
    /**
368
     * Définit la contrainte par défaut à utiliser dans le système. Typiquement
369
     * à utiliser avec la méthode 'ressource'.
370
     */
371
    public function setDefaultConstraint(string $placeholder): self
372
    {
373
        if (array_key_exists($placeholder, $this->placeholders)) {
374
            $this->defaultPlaceholder = $placeholder;
375
        }
376
377
        return $this;
378
    }
379
380
    /**
381
     * {@inheritDoc}
382
     */
383
    public function getDefaultController(): string
384
    {
385
        return preg_replace('#Controller$#i', '', $this->defaultController) . 'Controller';
386
    }
387
388
    /**
389
     * {@inheritDoc}
390
     */
391
    public function getDefaultMethod(): string
392
    {
393
        return $this->defaultMethod;
394
    }
395
396
    /**
397
     * {@inheritDoc}
398
     */
399
    public function getDefaultNamespace(): string
400
    {
401
        return $this->defaultNamespace;
402
    }
403
404
    public function getPlaceholders(): array
405
    {
406
        return $this->placeholders;
407
    }
408
409
    /**
410
     *{@inheritDoc}
411
     */
412
    public function shouldTranslateURIDashes(): bool
413
    {
414
        return $this->translateURIDashes;
415
    }
416
417
    /**
418
     * {@inheritDoc}
419
     */
420
    public function shouldAutoRoute(): bool
421
    {
422
        return $this->autoRoute;
423
    }
424
425
    /**
426
     * Activer ou désactiver le tri des routes par priorité
427
     */
428
    public function setPrioritize(bool $enabled = true): self
429
    {
430
        $this->prioritize = $enabled;
431
432
        return $this;
433
    }
434
435
    /**
436
     * Définissez le drapeau qui limite ou non les routes avec l'espace réservé {locale} à App::$supportedLocales
437
     */
438
    public function useSupportedLocalesOnly(bool $useOnly): self
439
    {
440
        $this->useSupportedLocalesOnly = $useOnly;
441
442
        return $this;
443
    }
444
445
    /**
446
     * Obtenez le drapeau qui limite ou non les routes avec l'espace réservé {locale} vers App::$supportedLocales
447
     */
448
    public function shouldUseSupportedLocalesOnly(): bool
449
    {
450
        return $this->useSupportedLocalesOnly;
451
    }
452
453
    /**
454
     * {@inheritDoc}
455
     */
456
    public function getRegisteredControllers(?string $verb = '*'): array
457
    {
458
        $controllers = [];
459
460
        if ($verb === '*') {
461
            foreach ($this->defaultHTTPMethods as $tmpVerb) {
462
                foreach ($this->routes[$tmpVerb] as $route) {
463
                    $routeKey   = key($route['route']);
464
                    $controller = $this->getControllerName($route['route'][$routeKey]);
465
                    if ($controller !== null) {
466
                        $controllers[] = $controller;
467
                    }
468
                }
469
            }
470
        } else {
471
            $routes = $this->getRoutes($verb);
472
473
            foreach ($routes as $handler) {
474
                $controller = $this->getControllerName($handler);
475
                if ($controller !== null) {
476
                    $controllers[] = $controller;
477
                }
478
            }
479
        }
480
481
        return array_unique($controllers);
482
    }
483
484
    /**
485
     * {@inheritDoc}
486
     */
487
    public function getRoutes(?string $verb = null, bool $withName = false): array
488
    {
489
        if (empty($verb)) {
490
            $verb = $this->getHTTPVerb();
491
        }
492
        $verb = strtolower($verb);
493
494
        // Puisqu'il s'agit du point d'entrée du routeur,
495
        // prenez un moment pour faire toute découverte de route
496
        // que nous pourrions avoir besoin de faire.
497
        $this->discoverRoutes();
498
499
        $routes     = [];
500
        $collection = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $collection is dead and can be removed.
Loading history...
501
502
        if (isset($this->routes[$verb])) {
503
            // Conserve les itinéraires du verbe actuel au début afin qu'ils soient
504
            // mis en correspondance avant l'un des itinéraires génériques "add".
505
            $collection = $this->routes[$verb] + ($this->routes['*'] ?? []);
506
507
            foreach ($collection as $name => $r) {
508
                $key = key($r['route']);
509
510
                if (! $withName) {
511
                    $routes[$key] = $r['route'][$key];
512
                } else {
513
                    $routes[$key] = [
514
                        'name'    => $name,
515
                        'handler' => $r['route'][$key],
516
                    ];
517
                }
518
            }
519
        }
520
521
        // tri des routes par priorité
522
        if ($this->prioritizeDetected && $this->prioritize && $routes !== []) {
523
            $order = [];
524
525
            foreach ($routes as $key => $value) {
526
                $key                    = $key === '/' ? $key : ltrim($key, '/ ');
527
                $priority               = $this->getRoutesOptions($key, $verb)['priority'] ?? 0;
528
                $order[$priority][$key] = $value;
529
            }
530
531
            ksort($order);
532
            $routes = array_merge(...$order);
533
        }
534
535
        return $routes;
536
    }
537
538
    /**
539
     * Renvoie une ou toutes les options d'itinéraire
540
     */
541
    public function getRoutesOptions(?string $from = null, ?string $verb = null): array
542
    {
543
        $options = $this->loadRoutesOptions($verb);
544
545
        return $from ? $options[$from] ?? [] : $options;
546
    }
547
548
    /**
549
     * {@inheritDoc}
550
     */
551
    public function getHTTPVerb(): string
552
    {
553
        return $this->HTTPVerb;
554
    }
555
556
    /**
557
     * {@inheritDoc}
558
     */
559
    public function setHTTPVerb(string $verb): self
560
    {
561
        $this->HTTPVerb = $verb;
562
563
        return $this;
564
    }
565
566
    /**
567
     * Une méthode de raccourci pour ajouter un certain nombre d'itinéraires en une seule fois.
568
     * Il ne permet pas de définir des options sur l'itinéraire, ou de
569
     * définir la méthode utilisée.
570
     */
571
    public function map(array $routes = [], ?array $options = null): self
572
    {
573
        foreach ($routes as $from => $to) {
574
            $this->add($from, $to, $options);
575
        }
576
577
        return $this;
578
    }
579
580
    /**
581
     * {@inheritDoc}
582
     */
583
    public function add(string $from, $to, ?array $options = null): self
584
    {
585
        $this->create('*', $from, $to, $options);
586
587
        return $this;
588
    }
589
590
    /**
591
     * Ajoute une redirection temporaire d'une route à une autre. Utilisé pour
592
     * rediriger le trafic des anciennes routes inexistantes vers les nouvelles
593
     * itinéraires déplacés.
594
     *
595
     * @param string $from   Le modèle à comparer
596
     * @param string $to     Soit un nom de route ou un URI vers lequel rediriger
597
     * @param int    $status Le code d'état HTTP qui doit être renvoyé avec cette redirection
598
     */
599
    public function addRedirect(string $from, string $to, int $status = 302): self
600
    {
601
        // Utilisez le modèle de la route nommée s'il s'agit d'une route nommée.
602
        if (array_key_exists($to, $this->routes['*'])) {
603
            $to = $this->routes['*'][$to]['route'];
604
        } elseif (array_key_exists($to, $this->routes['get'])) {
605
            $to = $this->routes['get'][$to]['route'];
606
        }
607
608
        $this->create('*', $from, $to, ['redirect' => $status]);
609
610
        return $this;
611
    }
612
613
    /**
614
     * {@inheritDoc}
615
     */
616
    public function isRedirect(string $from): bool
617
    {
618
        foreach ($this->routes['*'] as $name => $route) {
619
            // Est-ce une route nommée ?
620
            if ($name === $from || key($route['route']) === $from) {
621
                return isset($route['redirect']) && is_numeric($route['redirect']);
622
            }
623
        }
624
625
        return false;
626
    }
627
628
    /**
629
     * {@inheritDoc}
630
     */
631
    public function getRedirectCode(string $from): int
632
    {
633
        foreach ($this->routes['*'] as $name => $route) {
634
            // Est-ce une route nommée ?
635
            if ($name === $from || key($route['route']) === $from) {
636
                return $route['redirect'] ?? 0;
637
            }
638
        }
639
640
        return 0;
641
    }
642
643
    /**
644
     * Regroupez une série de routes sous un seul segment d'URL. C'est pratique
645
     * pour regrouper des éléments dans une zone d'administration, comme :
646
     *
647
     * Example:
648
     *     // Creates route: admin/users
649
     *     $route->group('admin', function() {
650
     *            $route->resource('users');
651
     *     });
652
     *
653
     * @param string         $name      Le nom avec lequel grouper/préfixer les routes.
654
     * @param array|callable ...$params
655
     */
656
    public function group(string $name, ...$params)
657
    {
658
        $oldGroup   = $this->group;
659
        $oldOptions = $this->currentOptions;
660
661
        // Pour enregistrer une route, nous allons définir un indicateur afin que notre routeur
662
        // donc il verra le nom du groupe.
663
        // Si le nom du groupe est vide, nous continuons à utiliser le nom du groupe précédemment construit.
664
        $this->group = implode('/', array_unique(explode('/', $name ? ltrim($oldGroup . '/' . $name, '/') : $oldGroup)));
0 ignored issues
show
Bug introduced by
It seems like $name ? ltrim($oldGroup ...$name, '/') : $oldGroup can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

664
        $this->group = implode('/', array_unique(explode('/', /** @scrutinizer ignore-type */ $name ? ltrim($oldGroup . '/' . $name, '/') : $oldGroup)));
Loading history...
665
666
        $callback = array_pop($params);
667
668
        if ($params && is_array($params[0])) {
669
            $this->currentOptions = array_shift($params);
670
        }
671
672
        if (is_callable($callback)) {
673
            $callback($this);
674
        }
675
676
        $this->group          = $oldGroup;
677
        $this->currentOptions = $oldOptions;
678
    }
679
680
    /*
681
     * ------------------------------------------------- -------------------
682
     * Routage basé sur les verbes HTTP
683
     * ------------------------------------------------- -------------------
684
     * Le routage fonctionne ici car, comme le fichier de configuration des routes est lu,
685
     * les différentes routes basées sur le verbe HTTP ne seront ajoutées qu'à la mémoire en mémoire
686
     * routes s'il s'agit d'un appel qui doit répondre à ce verbe.
687
     *
688
     * Le tableau d'options est généralement utilisé pour transmettre un 'as' ou var, mais peut
689
     * être étendu à l'avenir. Voir le docblock pour la méthode 'add' ci-dessus pour
690
     * liste actuelle des options disponibles dans le monde.*/
691
692
    /**
693
     * Crée une collection d'itinéraires basés sur HTTP-verb pour un contrôleur.
694
     *
695
     * Options possibles :
696
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
697
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
698
     * 'websafe' - - '1' si seuls les verbes HTTP GET et POST sont pris en charge
699
     *
700
     * Exemple:
701
     *
702
     *      $route->resource('photos');
703
     *
704
     *      // Genère les routes suivantes:
705
     *      HTTP Verb | Path        | Action        | Used for...
706
     *      ----------+-------------+---------------+-----------------
707
     *      GET         /photos             index           un tableau d'objets photo
708
     *      GET         /photos/new         new             un objet photo vide, avec des propriétés par défaut
709
     *      GET         /photos/{id}/edit   edit            un objet photo spécifique, propriétés modifiables
710
     *      GET         /photos/{id}        show            un objet photo spécifique, toutes les propriétés
711
     *      POST        /photos             create          un nouvel objet photo, à ajouter à la ressource
712
     *      DELETE      /photos/{id}        delete          supprime l'objet photo spécifié
713
     *      PUT/PATCH   /photos/{id}        update          propriétés de remplacement pour la photo existante
714
     *
715
     *  Si l'option 'websafe' est présente, les chemins suivants sont également disponibles :
716
     *
717
     *      POST		/photos/{id}/delete delete
718
     *      POST        /photos/{id}        update
719
     *
720
     * @param string     $name    Le nom de la ressource/du contrôleur vers lequel router.
721
     * @param array|null $options Une liste des façons possibles de personnaliser le routage.
722
     */
723
    public function resource(string $name, ?array $options = null): self
724
    {
725
        // Afin de permettre la personnalisation de la route, le
726
        // les ressources sont envoyées à, nous devons avoir un nouveau nom
727
        // pour stocker les valeurs.
728
        $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
729
730
        // Si un nouveau contrôleur est spécifié, alors nous remplaçons le
731
        // valeur de $name avec le nom du nouveau contrôleur.
732
        if (isset($options['controller'])) {
733
            $newName = ucfirst(esc(strip_tags($options['controller'])));
0 ignored issues
show
Bug introduced by
It seems like esc(strip_tags($options['controller'])) can also be of type array; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

733
            $newName = ucfirst(/** @scrutinizer ignore-type */ esc(strip_tags($options['controller'])));
Loading history...
734
        }
735
736
        // Afin de permettre la personnalisation des valeurs d'identifiant autorisées
737
        // nous avons besoin d'un endroit pour les stocker.
738
        $id = $options['placeholder'] ?? $this->placeholders[$this->defaultPlaceholder] ?? '(:segment)';
739
740
        // On s'assure de capturer les références arrière
741
        $id = '(' . trim($id, '()') . ')';
742
743
        $methods = isset($options['only']) ? (is_string($options['only']) ? explode(',', $options['only']) : $options['only']) : ['index', 'show', 'create', 'update', 'delete', 'new', 'edit'];
744
745
        if (isset($options['except'])) {
746
            $options['except'] = is_array($options['except']) ? $options['except'] : explode(',', $options['except']);
747
748
            foreach ($methods as $i => $method) {
749
                if (in_array($method, $options['except'], true)) {
750
                    unset($methods[$i]);
751
                }
752
            }
753
        }
754
755
        if (in_array('index', $methods, true)) {
756
            $this->get($name, $newName . '::index', $options);
757
        }
758
        if (in_array('new', $methods, true)) {
759
            $this->get($name . '/new', $newName . '::new', $options);
760
        }
761
        if (in_array('edit', $methods, true)) {
762
            $this->get($name . '/' . $id . '/edit', $newName . '::edit/$1', $options);
763
        }
764
        if (in_array('show', $methods, true)) {
765
            $this->get($name . '/' . $id, $newName . '::show/$1', $options);
766
        }
767
        if (in_array('create', $methods, true)) {
768
            $this->post($name, $newName . '::create', $options);
769
        }
770
        if (in_array('update', $methods, true)) {
771
            $this->put($name . '/' . $id, $newName . '::update/$1', $options);
772
            $this->patch($name . '/' . $id, $newName . '::update/$1', $options);
773
        }
774
        if (in_array('delete', $methods, true)) {
775
            $this->delete($name . '/' . $id, $newName . '::delete/$1', $options);
776
        }
777
778
        // Websafe ? la suppression doit être vérifiée avant la mise à jour en raison du nom de la méthode
779
        if (isset($options['websafe'])) {
780
            if (in_array('delete', $methods, true)) {
781
                $this->post($name . '/' . $id . '/delete', $newName . '::delete/$1', $options);
782
            }
783
            if (in_array('update', $methods, true)) {
784
                $this->post($name . '/' . $id, $newName . '::update/$1', $options);
785
            }
786
        }
787
788
        return $this;
789
    }
790
791
    /**
792
     * Crée une collection de routes basées sur les verbes HTTP pour un contrôleur de présentateur.
793
     *
794
     * Options possibles :
795
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
796
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
797
     *
798
     * Example:
799
     *
800
     *      $route->presenter('photos');
801
     *
802
     *      // Génère les routes suivantes
803
     *      HTTP Verb | Path        | Action        | Used for...
804
     *      ----------+-------------+---------------+-----------------
805
     *      GET         /photos             index           affiche le tableau des tous les objets photo
806
     *      GET         /photos/show/{id}   show            affiche un objet photo spécifique, toutes les propriétés
807
     *      GET         /photos/new         new             affiche un formulaire pour un objet photo vide, avec les propriétés par défaut
808
     *      POST        /photos/create      create          traitement du formulaire pour une nouvelle photo
809
     *      GET         /photos/edit/{id}   edit            affiche un formulaire d'édition pour un objet photo spécifique, propriétés modifiables
810
     *      POST        /photos/update/{id} update          traitement des données du formulaire d'édition
811
     *      GET         /photos/remove/{id} remove          affiche un formulaire pour confirmer la suppression d'un objet photo spécifique
812
     *      POST        /photos/delete/{id} delete          suppression de l'objet photo spécifié
813
     *
814
     * @param string     $name    Le nom du contrôleur vers lequel router.
815
     * @param array|null $options Une liste des façons possibles de personnaliser le routage.
816
     */
817
    public function presenter(string $name, ?array $options = null): self
818
    {
819
        // Afin de permettre la personnalisation de la route, le
820
        // les ressources sont envoyées à, nous devons avoir un nouveau nom
821
        // pour stocker les valeurs.
822
        $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
823
824
        // Si un nouveau contrôleur est spécifié, alors nous remplaçons le
825
        // valeur de $name avec le nom du nouveau contrôleur.
826
        if (isset($options['controller'])) {
827
            $newName = ucfirst(esc(strip_tags($options['controller'])));
0 ignored issues
show
Bug introduced by
It seems like esc(strip_tags($options['controller'])) can also be of type array; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

827
            $newName = ucfirst(/** @scrutinizer ignore-type */ esc(strip_tags($options['controller'])));
Loading history...
828
        }
829
830
        // Afin de permettre la personnalisation des valeurs d'identifiant autorisées
831
        // nous avons besoin d'un endroit pour les stocker.
832
        $id = $options['placeholder'] ?? $this->placeholders[$this->defaultPlaceholder] ?? '(:segment)';
833
834
        // On s'assure de capturer les références arrière
835
        $id = '(' . trim($id, '()') . ')';
836
837
        $methods = isset($options['only']) ? (is_string($options['only']) ? explode(',', $options['only']) : $options['only']) : ['index', 'show', 'new', 'create', 'edit', 'update', 'remove', 'delete'];
838
839
        if (isset($options['except'])) {
840
            $options['except'] = is_array($options['except']) ? $options['except'] : explode(',', $options['except']);
841
842
            foreach ($methods as $i => $method) {
843
                if (in_array($method, $options['except'], true)) {
844
                    unset($methods[$i]);
845
                }
846
            }
847
        }
848
849
        if (in_array('index', $methods, true)) {
850
            $this->get($name, $newName . '::index', $options);
851
        }
852
        if (in_array('show', $methods, true)) {
853
            $this->get($name . '/show/' . $id, $newName . '::show/$1', $options);
854
        }
855
        if (in_array('new', $methods, true)) {
856
            $this->get($name . '/new', $newName . '::new', $options);
857
        }
858
        if (in_array('create', $methods, true)) {
859
            $this->post($name . '/create', $newName . '::create', $options);
860
        }
861
        if (in_array('edit', $methods, true)) {
862
            $this->get($name . '/edit/' . $id, $newName . '::edit/$1', $options);
863
        }
864
        if (in_array('update', $methods, true)) {
865
            $this->post($name . '/update/' . $id, $newName . '::update/$1', $options);
866
        }
867
        if (in_array('remove', $methods, true)) {
868
            $this->get($name . '/remove/' . $id, $newName . '::remove/$1', $options);
869
        }
870
        if (in_array('delete', $methods, true)) {
871
            $this->post($name . '/delete/' . $id, $newName . '::delete/$1', $options);
872
        }
873
        if (in_array('show', $methods, true)) {
874
            $this->get($name . '/' . $id, $newName . '::show/$1', $options);
875
        }
876
        if (in_array('create', $methods, true)) {
877
            $this->post($name, $newName . '::create', $options);
878
        }
879
880
        return $this;
881
    }
882
883
    /**
884
     * Spécifie une seule route à faire correspondre pour plusieurs verbes HTTP.
885
     *
886
     * Exemple:
887
     *  $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
888
     *
889
     * @param array|Closure|string $to
890
     */
891
    public function match(array $verbs = [], string $from = '', $to = '', ?array $options = null): self
892
    {
893
        if (empty($from) || empty($to)) {
894
            throw new InvalidArgumentException('Vous devez fournir les paramètres : $from, $to.');
895
        }
896
897
        foreach ($verbs as $verb) {
898
            $verb = strtolower($verb);
899
900
            $this->{$verb}($from, $to, $options);
901
        }
902
903
        return $this;
904
    }
905
906
    /**
907
     * Spécifie une route qui n'est disponible que pour les requêtes GET.
908
     *
909
     * @param array|Closure|string $to
910
     */
911
    public function get(string $from, $to, ?array $options = null): self
912
    {
913
        $this->create('get', $from, $to, $options);
914
915
        return $this;
916
    }
917
918
    /**
919
     * Spécifie une route qui n'est disponible que pour les requêtes POST.
920
     *
921
     * @param array|Closure|string $to
922
     */
923
    public function post(string $from, $to, ?array $options = null): self
924
    {
925
        $this->create('post', $from, $to, $options);
926
927
        return $this;
928
    }
929
930
    /**
931
     * Spécifie une route qui n'est disponible que pour les requêtes PUT.
932
     *
933
     * @param array|Closure|string $to
934
     */
935
    public function put(string $from, $to, ?array $options = null): self
936
    {
937
        $this->create('put', $from, $to, $options);
938
939
        return $this;
940
    }
941
942
    /**
943
     * Spécifie une route qui n'est disponible que pour les requêtes DELETE.
944
     *
945
     * @param array|Closure|string $to
946
     */
947
    public function delete(string $from, $to, ?array $options = null): self
948
    {
949
        $this->create('delete', $from, $to, $options);
950
951
        return $this;
952
    }
953
954
    /**
955
     * Spécifie une route qui n'est disponible que pour les requêtes HEAD.
956
     *
957
     * @param array|Closure|string $to
958
     */
959
    public function head(string $from, $to, ?array $options = null): self
960
    {
961
        $this->create('head', $from, $to, $options);
962
963
        return $this;
964
    }
965
966
    /**
967
     * Spécifie une route qui n'est disponible que pour les requêtes PATCH.
968
     *
969
     * @param array|Closure|string $to
970
     */
971
    public function patch(string $from, $to, ?array $options = null): self
972
    {
973
        $this->create('patch', $from, $to, $options);
974
975
        return $this;
976
    }
977
978
    /**
979
     * Spécifie une route qui n'est disponible que pour les requêtes OPTIONS.
980
     *
981
     * @param array|Closure|string $to
982
     */
983
    public function options(string $from, $to, ?array $options = null): self
984
    {
985
        $this->create('options', $from, $to, $options);
986
987
        return $this;
988
    }
989
990
    /**
991
     * Spécifie une route qui n'est disponible que pour les requêtes de ligne de commande.
992
     *
993
     * @param array|Closure|string $to
994
     */
995
    public function cli(string $from, $to, ?array $options = null): self
996
    {
997
        $this->create('cli', $from, $to, $options);
998
999
        return $this;
1000
    }
1001
1002
    /**
1003
     * Spécifie une route qui n'affichera qu'une vue.
1004
     * Ne fonctionne que pour les requêtes GET.
1005
     */
1006
    public function view(string $from, string $view, ?array $options = null): self
1007
    {
1008
        $to = static fn (...$data) => Services::viewer()
1009
            ->setData(['segments' => $data], 'raw')
1010
            ->display($view)
1011
            ->setOptions($options)
1012
            ->render();
1013
1014
        $this->create('get', $from, $to, $options);
1015
1016
        return $this;
1017
    }
1018
1019
    /**
1020
     * Limite les itinéraires à un ENVIRONNEMENT spécifié ou ils ne fonctionneront pas.
1021
     */
1022
    public function environment(string $env, Closure $callback): self
1023
    {
1024
        if ($env === config('app.environment')) {
1025
            $callback($this);
1026
        }
1027
1028
        return $this;
1029
    }
1030
1031
    /**
1032
     * {@inheritDoc}
1033
     */
1034
    public function reverseRoute(string $search, ...$params)
1035
    {
1036
        // Les routes nommées ont une priorité plus élevée.
1037
        foreach ($this->routes as $collection) {
1038
            if (array_key_exists($search, $collection)) {
1039
                return $this->buildReverseRoute(key($collection[$search]['route']), $params);
1040
            }
1041
        }
1042
1043
        // Ajoutez l'espace de noms par défaut si nécessaire.
1044
        $namespace = trim($this->defaultNamespace, '\\') . '\\';
1045
        if (
1046
            substr($search, 0, 1) !== '\\'
1047
            && substr($search, 0, strlen($namespace)) !== $namespace
1048
        ) {
1049
            $search = $namespace . $search;
1050
        }
1051
1052
        // Si ce n'est pas une route nommée, alors bouclez
1053
        // toutes les routes pour trouver une correspondance.
1054
        foreach ($this->routes as $collection) {
1055
            foreach ($collection as $route) {
1056
                $from = key($route['route']);
1057
                $to   = $route['route'][$from];
1058
1059
                // on ignore les closures
1060
                if (! is_string($to)) {
1061
                    continue;
1062
                }
1063
1064
                // Perd toute barre oblique d'espace de noms au début des chaînes
1065
                // pour assurer une correspondance plus cohérente.$to     = ltrim($to, '\\');
1066
                $to     = ltrim($to, '\\');
1067
                $search = ltrim($search, '\\');
1068
1069
                // S'il y a une chance de correspondance, alors ce sera
1070
                // soit avec $search au début de la chaîne $to.
1071
                if (strpos($to, $search) !== 0) {
1072
                    continue;
1073
                }
1074
1075
                // Assurez-vous que le nombre de $params donné ici
1076
                // correspond au nombre de back-references dans la route
1077
                if (substr_count($to, '$') !== count($params)) {
1078
                    continue;
1079
                }
1080
1081
                return $this->buildReverseRoute($from, $params);
1082
            }
1083
        }
1084
1085
        // Si nous sommes toujours là, alors nous n'avons pas trouvé de correspondance.
1086
        return false;
1087
    }
1088
1089
    /**
1090
     * Vérifie une route (en utilisant le "from") pour voir si elle est filtrée ou non.
1091
     */
1092
    public function isFiltered(string $search, ?string $verb = null): bool
1093
    {
1094
        return $this->getFiltersForRoute($search, $verb) !== [];
1095
    }
1096
1097
    /**
1098
     * Renvoie les filtres qui doivent être appliqués pour un seul itinéraire, ainsi que
1099
     * avec tous les paramètres qu'il pourrait avoir. Les paramètres sont trouvés en divisant
1100
     * le nom du paramètre entre deux points pour séparer le nom du filtre de la liste des paramètres,
1101
     * et le fractionnement du résultat sur des virgules. Alors:
1102
     *
1103
     *    'role:admin,manager'
1104
     *
1105
     * a un filtre de "rôle", avec des paramètres de ['admin', 'manager'].
1106
     */
1107
    public function getFiltersForRoute(string $search, ?string $verb = null): array
1108
    {
1109
        $options = $this->loadRoutesOptions($verb);
1110
1111
        $middlewares = $options[$search]['middlewares'] ?? (
1112
            $options[$search]['middleware'] ?? ($options[$search]['filter'] ?? [])
1113
        );
1114
1115
        return (array) $middlewares;
1116
    }
1117
1118
    /**
1119
     * Construit une route inverse
1120
     *
1121
     * @param array $params Un ou plusieurs paramètres à transmettre à la route.
1122
     *                      Le dernier paramètre vous permet de définir la locale.
1123
     */
1124
    protected function buildReverseRoute(string $from, array $params): string
1125
    {
1126
        $locale = null;
1127
1128
        // Retrouvez l'ensemble de nos rétro-références dans le parcours d'origine.
1129
        preg_match_all('/\(([^)]+)\)/', $from, $matches);
1130
1131
        if (empty($matches[0])) {
1132
            if (strpos($from, '{locale}') !== false) {
1133
                $locale = $params[0] ?? null;
1134
            }
1135
1136
            $from = $this->replaceLocale($from, $locale);
1137
1138
            return '/' . ltrim($from, '/');
1139
        }
1140
1141
        // Les paramètres régionaux sont passés ?
1142
        $placeholderCount = count($matches[0]);
1143
        if (count($params) > $placeholderCount) {
1144
            $locale = $params[$placeholderCount];
1145
        }
1146
1147
        // Construisez notre chaîne résultante, en insérant les $params aux endroits appropriés.
1148
        foreach ($matches[0] as $index => $pattern) {
1149
            if (! preg_match('#^' . $pattern . '$#u', $params[$index])) {
1150
                throw RouterException::invalidParameterType();
1151
            }
1152
1153
            // Assurez-vous que le paramètre que nous insérons correspond au type de paramètre attendu.
1154
            $pos  = strpos($from, $pattern);
1155
            $from = substr_replace($from, $params[$index], $pos, strlen($pattern));
1156
        }
1157
1158
        $from = $this->replaceLocale($from, $locale);
0 ignored issues
show
Bug introduced by
It seems like $from can also be of type array; however, parameter $route of BlitzPHP\Router\RouteCollection::replaceLocale() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1158
        $from = $this->replaceLocale(/** @scrutinizer ignore-type */ $from, $locale);
Loading history...
1159
1160
        return '/' . ltrim($from, '/');
1161
    }
1162
1163
    /**
1164
     * Charger les options d'itinéraires en fonction du verbe
1165
     */
1166
    protected function loadRoutesOptions(?string $verb = null): array
1167
    {
1168
        $verb = $verb ?: $this->getHTTPVerb();
1169
1170
        $options = $this->routesOptions[$verb] ?? [];
1171
1172
        if (isset($this->routesOptions['*'])) {
1173
            foreach ($this->routesOptions['*'] as $key => $val) {
1174
                if (isset($options[$key])) {
1175
                    $extraOptions  = array_diff_key($val, $options[$key]);
1176
                    $options[$key] = array_merge($options[$key], $extraOptions);
1177
                } else {
1178
                    $options[$key] = $val;
1179
                }
1180
            }
1181
        }
1182
1183
        return $options;
1184
    }
1185
1186
    /**
1187
     * Fait le gros du travail de création d'un itinéraire réel. Vous devez spécifier
1188
     * la ou les méthodes de demande pour lesquelles cette route fonctionnera. Ils peuvent être séparés
1189
     * par un caractère pipe "|" s'il y en a plusieurs.
1190
     *
1191
     * @param array|Closure|string $to
1192
     */
1193
    protected function create(string $verb, string $from, $to, ?array $options = null)
1194
    {
1195
        $overwrite = false;
1196
        $prefix    = $this->group === null ? '' : $this->group . '/';
1197
1198
        $from = esc(strip_tags($prefix . $from));
1199
1200
        // Alors que nous voulons ajouter une route dans un groupe de '/',
1201
        // ça ne marche pas avec la correspondance, alors supprimez-les...
1202
        if ($from !== '/') {
1203
            $from = trim($from, '/');
0 ignored issues
show
Bug introduced by
It seems like $from can also be of type array; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1203
            $from = trim(/** @scrutinizer ignore-type */ $from, '/');
Loading history...
1204
        }
1205
        
1206
        if (is_string($to) && strpos($to, '::') === false && class_exists($to) && method_exists($to, '__invoke')) {
1207
            $to = [$to, '__invoke'];
1208
        }
1209
1210
        // Lors de la redirection vers une route nommée, $to est un tableau tel que `['zombies' => '\Zombies::index']`.
1211
        if (is_array($to) && count($to) === 2) {
1212
            $to = $this->processArrayCallableSyntax($from, $to);
1213
        }
1214
1215
        $options = array_merge($this->currentOptions ?? [], $options ?? []);
1216
1217
        // Détection de priorité de routage
1218
        if (isset($options['priority'])) {
1219
            $options['priority'] = abs((int) $options['priority']);
1220
1221
            if ($options['priority'] > 0) {
1222
                $this->prioritizeDetected = true;
1223
            }
1224
        }
1225
1226
        // Limitation du nom d'hôte ?
1227
        if (! empty($options['hostname'])) {
1228
            // @todo déterminer s'il existe un moyen de mettre les hôtes sur liste blanche ?
1229
            if (! $this->checkHostname($options['hostname'])) {
1230
                return;
1231
            }
1232
1233
            $overwrite = true;
1234
        }
1235
1236
        // Limitation du nom sous-domaine ?
1237
        elseif (! empty($options['subdomain'])) {
1238
            // Si nous ne correspondons pas au sous-domaine actuel, alors
1239
            // nous n'avons pas besoin d'ajouter la route.
1240
            if (! $this->checkSubdomains($options['subdomain'])) {
1241
                return;
1242
            }
1243
1244
            $overwrite = true;
1245
        }
1246
1247
        // Sommes-nous en train de compenser les liaisons ?
1248
        // Si oui, occupez-vous d'eux ici en un
1249
        // abattre en plein vol.
1250
        if (isset($options['offset']) && is_string($to)) {
1251
            // Récupère une chaîne constante avec laquelle travailler.
1252
            $to = preg_replace('/(\$\d+)/', '$X', $to);
1253
1254
            for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) {
1255
                $to = preg_replace_callback(
1256
                    '/\$X/',
1257
                    static fn ($m) => '$' . $i,
1258
                    $to,
1259
                    1
1260
                );
1261
            }
1262
        }
1263
1264
        // Remplacez nos espaces réservés de regex par la chose réelle
1265
        // pour que le routeur n'ait pas besoin de savoir quoi que ce soit.
1266
        foreach ($this->placeholders as $tag => $pattern) {
1267
            $from = str_ireplace(':' . $tag, $pattern, $from);
1268
        }
1269
1270
        // S'il s'agit d'une redirection, aucun traitement
1271
        if (! isset($options['redirect']) && is_string($to)) {
1272
            // Si aucun espace de noms n'est trouvé, ajouter l'espace de noms par défaut
1273
            if (strpos($to, '\\') === false || strpos($to, '\\') > 0) {
1274
                $namespace = $options['namespace'] ?? $this->defaultNamespace;
1275
                $to        = trim($namespace, '\\') . '\\' . $to;
1276
            }
1277
            // Assurez-vous toujours que nous échappons à notre espace de noms afin de ne pas pointer vers
1278
            // \BlitzPHP\Routes\Controller::method.
1279
            $to = '\\' . ltrim($to, '\\');
1280
        }
1281
1282
        $name = $options['as'] ?? $from;
1283
1284
        // Ne remplacez aucun 'from' existant afin que les routes découvertes automatiquement
1285
        // n'écrase pas les paramètres app/Config/Routes.
1286
        // les routes manuelement définies doivent toujours être la "source de vérité".
1287
        // cela ne fonctionne que parce que les routes découvertes sont ajoutées juste avant
1288
        // pour tenter de router la requête.
1289
        if (isset($this->routes[$verb][$name]) && ! $overwrite) {
1290
            return;
1291
        }
1292
1293
        $this->routes[$verb][$name] = [
1294
            'route' => [$from => $to],
1295
        ];
1296
1297
        $this->routesOptions[$verb][$from] = $options;
1298
1299
        // C'est une redirection ?
1300
        if (isset($options['redirect']) && is_numeric($options['redirect'])) {
1301
            $this->routes['*'][$name]['redirect'] = $options['redirect'];
1302
        }
1303
    }
1304
1305
    /**
1306
     * Compare le nom d'hôte transmis avec le nom d'hôte actuel sur cette demande de page.
1307
     *
1308
     * @param string $hostname Nom d'hôte dans les options d'itinéraire
1309
     */
1310
    private function checkHostname(string $hostname): bool
1311
    {
1312
        // Les appels CLI ne peuvent pas être sur le nom d'hôte.
1313
        if (! isset($this->httpHost) || is_cli()) {
1314
            return false;
1315
        }
1316
1317
        return strtolower($this->httpHost) === strtolower($hostname);
0 ignored issues
show
Bug introduced by
It seems like $this->httpHost can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1317
        return strtolower(/** @scrutinizer ignore-type */ $this->httpHost) === strtolower($hostname);
Loading history...
1318
    }
1319
    
1320
    /**
1321
     * Compare le ou les sous-domaines transmis avec le sous-domaine actuel
1322
     * sur cette page demande.
1323
     *
1324
     * @param mixed $subdomains
1325
     */
1326
    private function checkSubdomains($subdomains): bool
1327
    {
1328
        // Les appels CLI ne peuvent pas être sur le sous-domaine.
1329
        if (! isset($_SERVER['HTTP_HOST'])) {
1330
            return false;
1331
        }
1332
1333
        if ($this->currentSubdomain === null) {
1334
            $this->currentSubdomain = $this->determineCurrentSubdomain();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->determineCurrentSubdomain() can also be of type false. However, the property $currentSubdomain is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1335
        }
1336
1337
        if (! is_array($subdomains)) {
1338
            $subdomains = [$subdomains];
1339
        }
1340
1341
        // Les routes peuvent être limitées à n'importe quel sous-domaine. Dans ce cas, cependant,
1342
        // il nécessite la présence d'un sous-domaine.
1343
        if (! empty($this->currentSubdomain) && in_array('*', $subdomains, true)) {
1344
            return true;
1345
        }
1346
1347
        return in_array($this->currentSubdomain, $subdomains, true);
1348
    }
1349
1350
    /**
1351
     * Examine le HTTP_HOST pour obtenir une meilleure correspondance pour le sous-domaine. Ce
1352
     * ne sera pas parfait, mais devrait répondre à nos besoins.
1353
     *
1354
     * Ce n'est surtout pas parfait puisqu'il est possible d'enregistrer un domaine
1355
     * avec un point (.) dans le cadre du nom de domaine.
1356
     *
1357
     * @return mixed
1358
     */
1359
    private function determineCurrentSubdomain()
1360
    {
1361
        // Nous devons nous assurer qu'un schéma existe
1362
        // sur l'URL sinon parse_url sera mal interprété
1363
        // 'hôte' comme 'chemin'.
1364
        $url = $this->httpHost;
1365
        if (strpos($url, 'http') !== 0) {
0 ignored issues
show
Bug introduced by
It seems like $url can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1365
        if (strpos(/** @scrutinizer ignore-type */ $url, 'http') !== 0) {
Loading history...
1366
            $url = 'http://' . $url;
1367
        }
1368
1369
        $parsedUrl = parse_url($url);
1370
1371
        $host = explode('.', $parsedUrl['host']);
1372
1373
        if ($host[0] === 'www') {
1374
            unset($host[0]);
1375
        }
1376
1377
        // Débarrassez-vous de tous les domaines, qui seront les derniers
1378
        unset($host[count($host) -1]);
1379
1380
        // Compte pour les domaines .co.uk, .co.nz, etc.
1381
        if (end($host) === 'co') {
1382
            $host = array_slice($host, 0, -1);
1383
        }
1384
1385
        // S'il ne nous reste qu'une partie, alors nous n'avons pas de sous-domaine.
1386
        if (count($host) === 1) {
1387
            // Définissez-le sur false pour ne pas revenir ici.
1388
            return false;
1389
        }
1390
1391
        return array_shift($host);
1392
    }
1393
1394
    /**
1395
     *
1396
     */
1397
    private function getControllerName(Closure|string $handler): ?string
1398
    {
1399
        if (! is_string($handler)) {
1400
            return null;
1401
        }
1402
1403
        [$controller] = explode('::', $handler, 2);
1404
1405
        return $controller;
1406
    }
1407
    
1408
    /**
1409
     * Renvoie la chaîne de paramètres de méthode comme `/$1/$2` pour les espaces réservés
1410
     */
1411
    private function getMethodParams(string $from): string
1412
    {
1413
        preg_match_all('/\(.+?\)/', $from, $matches);
1414
        $count = is_countable($matches[0]) ? count($matches[0]) : 0;
1415
1416
        $params = '';
1417
1418
        for ($i = 1; $i <= $count; $i++) {
1419
            $params .= '/$' . $i;
1420
        }
1421
1422
        return $params;
1423
    }
1424
1425
    private function processArrayCallableSyntax(string $from, array $to): string
1426
    {
1427
        // [classname, method]
1428
        // eg, [Home::class, 'index']
1429
        if (is_callable($to, true, $callableName)) {
1430
            // Si la route a des espaces réservés, ajoutez des paramètres automatiquement.
1431
            $params = $this->getMethodParams($from);
1432
1433
            if (strpos($callableName, '\\') !== false && $callableName[0] !== '\\') {
1434
                $callableName = '\\' . $callableName;
1435
            }
1436
1437
            return $callableName . $params;
1438
        }
1439
1440
        // [[classname, method], params]
1441
        // eg, [[Home::class, 'index'], '$1/$2']
1442
        if (
1443
            isset($to[0], $to[1])
1444
            && is_callable($to[0], true, $callableName)
1445
            && is_string($to[1])
1446
        ) {
1447
            $to = '\\' . $callableName . '/' . $to[1];
1448
        }
1449
1450
        return $to;
1451
    }
1452
1453
    /**
1454
     * Remplace la balise {locale} par la locale.
1455
     */
1456
    private function replaceLocale(string $route, ?string $locale = null): string
1457
    {
1458
        if (strpos($route, '{locale}') === false) {
1459
            return $route;
1460
        }
1461
1462
        // Vérifier les paramètres régionaux non valides
1463
        if ($locale !== null) {
1464
            if (! in_array($locale, config('app.supported_locales'), true)) {
1465
                $locale = null;
1466
            }
1467
        }
1468
1469
        if ($locale === null) {
1470
            $locale = Services::request()->getLocale();
1471
        }
1472
1473
        return strtr($route, ['{locale}' => $locale]);
1474
    }
1475
}
1476