Passed
Push — main ( 8b73d6...c69a6f )
by Dimitri
03:18
created

RouteCollection::formatRouteName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
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\Container\Services;
16
use BlitzPHP\Contracts\Router\RouteCollectionInterface;
17
use BlitzPHP\Exceptions\RouterException;
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 ? (rtrim($oldGroup ?? '', '/') . '/' . ltrim($name, '/')) : $oldGroup)));
0 ignored issues
show
Bug introduced by
It seems like $name ? rtrim($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 ? (rtrim($oldGroup ?? '', '/') . '/' . ltrim($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
				'as'  => $name . '.index',
758
			]);
759
        }
760
        if (in_array('new', $methods, true)) {
761
            $this->get($name . '/new', $newName . '::new', $options + [
762
				'as'  => $name . '.new',
763
			]);
764
        }
765
        if (in_array('edit', $methods, true)) {
766
            $this->get($name . '/' . $id . '/edit', $newName . '::edit/$1', $options + [
767
				'as'  => $name . '.edit',
768
			]);
769
        }
770
        if (in_array('show', $methods, true)) {
771
            $this->get($name . '/' . $id, $newName . '::show/$1', $options + [
772
				'as'  => $name . '.show',
773
			]);
774
        }
775
        if (in_array('create', $methods, true)) {
776
            $this->post($name, $newName . '::create', $options + [
777
				'as'  => $name . '.create',
778
			]);
779
        }
780
        if (in_array('update', $methods, true)) {
781
			$this->match(['put', 'patch'], $name . '/' . $id, $newName . '::update/$1', $options + [
782
				'as'  => $name . '.update',
783
			]);
784
        }
785
        if (in_array('delete', $methods, true)) {
786
            $this->delete($name . '/' . $id, $newName . '::delete/$1', $options + [
787
				'as'  => $name . '.delete',
788
			]);
789
        }
790
791
        // Websafe ? la suppression doit être vérifiée avant la mise à jour en raison du nom de la méthode
792
        if (isset($options['websafe'])) {
793
            if (in_array('delete', $methods, true)) {
794
                $this->post($name . '/' . $id . '/delete', $newName . '::delete/$1', $options + [
795
					'as'  => $name . '.websafe.delete',
796
				]);
797
            }
798
            if (in_array('update', $methods, true)) {
799
                $this->post($name . '/' . $id, $newName . '::update/$1', $options + [
800
					'as'  => $name . '.websafe.update',
801
				]);
802
            }
803
        }
804
805
        return $this;
806
    }
807
808
    /**
809
     * Crée une collection de routes basées sur les verbes HTTP pour un contrôleur de présentateur.
810
     *
811
     * Options possibles :
812
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
813
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
814
     *
815
     * Example:
816
     *
817
     *      $route->presenter('photos');
818
     *
819
     *      // Génère les routes suivantes
820
     *      HTTP Verb | Path        | Action        | Used for...
821
     *      ----------+-------------+---------------+-----------------
822
     *      GET         /photos             index           affiche le tableau des tous les objets photo
823
     *      GET         /photos/show/{id}   show            affiche un objet photo spécifique, toutes les propriétés
824
     *      GET         /photos/new         new             affiche un formulaire pour un objet photo vide, avec les propriétés par défaut
825
     *      POST        /photos/create      create          traitement du formulaire pour une nouvelle photo
826
     *      GET         /photos/edit/{id}   edit            affiche un formulaire d'édition pour un objet photo spécifique, propriétés modifiables
827
     *      POST        /photos/update/{id} update          traitement des données du formulaire d'édition
828
     *      GET         /photos/remove/{id} remove          affiche un formulaire pour confirmer la suppression d'un objet photo spécifique
829
     *      POST        /photos/delete/{id} delete          suppression de l'objet photo spécifié
830
     *
831
     * @param string     $name    Le nom du contrôleur vers lequel router.
832
     * @param array|null $options Une liste des façons possibles de personnaliser le routage.
833
     */
834
    public function presenter(string $name, ?array $options = null): self
835
    {
836
        // Afin de permettre la personnalisation de la route, le
837
        // les ressources sont envoyées à, nous devons avoir un nouveau nom
838
        // pour stocker les valeurs.
839
        $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
840
841
        // Si un nouveau contrôleur est spécifié, alors nous remplaçons le
842
        // valeur de $name avec le nom du nouveau contrôleur.
843
        if (isset($options['controller'])) {
844
            $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

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

1191
        $from = $this->replaceLocale(/** @scrutinizer ignore-type */ $from, $locale);
Loading history...
1192
1193
        return '/' . ltrim($from, '/');
1194
    }
1195
1196
    /**
1197
     * Charger les options d'itinéraires en fonction du verbe
1198
     */
1199
    protected function loadRoutesOptions(?string $verb = null): array
1200
    {
1201
        $verb = strtolower($verb ?: $this->getHTTPVerb());
1202
        
1203
        $options = $this->routesOptions[$verb] ?? [];
1204
1205
        if (isset($this->routesOptions['*'])) {
1206
            foreach ($this->routesOptions['*'] as $key => $val) {
1207
                if (isset($options[$key])) {
1208
                    $extraOptions  = array_diff_key($val, $options[$key]);
1209
                    $options[$key] = array_merge($options[$key], $extraOptions);
1210
                } else {
1211
                    $options[$key] = $val;
1212
                }
1213
            }
1214
        }
1215
1216
        return $options;
1217
    }
1218
1219
    /**
1220
     * Fait le gros du travail de création d'un itinéraire réel. Vous devez spécifier
1221
     * la ou les méthodes de demande pour lesquelles cette route fonctionnera. Ils peuvent être séparés
1222
     * par un caractère pipe "|" s'il y en a plusieurs.
1223
     *
1224
     * @param array|Closure|string $to
1225
     */
1226
    protected function create(string $verb, string $from, $to, ?array $options = null)
1227
    {
1228
        $overwrite = false;
1229
        $prefix    = $this->group === null ? '' : $this->group . '/';
1230
1231
        $from = esc(strip_tags(rtrim($prefix, '/') . '/' . ltrim($from, '/')));
1232
1233
        // Alors que nous voulons ajouter une route dans un groupe de '/',
1234
        // ça ne marche pas avec la correspondance, alors supprimez-les...
1235
        if ($from !== '/') {
1236
            $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

1236
            $from = trim(/** @scrutinizer ignore-type */ $from, '/');
Loading history...
1237
        }
1238
1239
        if (is_string($to) && strpos($to, '::') === false && class_exists($to) && method_exists($to, '__invoke')) {
1240
            $to = [$to, '__invoke'];
1241
        }
1242
1243
        // Lors de la redirection vers une route nommée, $to est un tableau tel que `['zombies' => '\Zombies::index']`.
1244
        if (is_array($to) && count($to) === 2) {
1245
            $to = $this->processArrayCallableSyntax($from, $to);
1246
        }
1247
1248
        $options = array_merge($this->currentOptions ?? [], $options ?? []);
1249
1250
        // Détection de priorité de routage
1251
        if (isset($options['priority'])) {
1252
            $options['priority'] = abs((int) $options['priority']);
1253
1254
            if ($options['priority'] > 0) {
1255
                $this->prioritizeDetected = true;
1256
            }
1257
        }
1258
1259
        // Limitation du nom d'hôte ?
1260
        if (! empty($options['hostname'])) {
1261
            // @todo déterminer s'il existe un moyen de mettre les hôtes sur liste blanche ?
1262
            if (! $this->checkHostname($options['hostname'])) {
1263
                return;
1264
            }
1265
1266
            $overwrite = true;
1267
        }
1268
1269
        // Limitation du nom sous-domaine ?
1270
        elseif (! empty($options['subdomain'])) {
1271
            // Si nous ne correspondons pas au sous-domaine actuel, alors
1272
            // nous n'avons pas besoin d'ajouter la route.
1273
            if (! $this->checkSubdomains($options['subdomain'])) {
1274
                return;
1275
            }
1276
1277
            $overwrite = true;
1278
        }
1279
1280
        // Sommes-nous en train de compenser les liaisons ?
1281
        // Si oui, occupez-vous d'eux ici en un
1282
        // abattre en plein vol.
1283
        if (isset($options['offset']) && is_string($to)) {
1284
            // Récupère une chaîne constante avec laquelle travailler.
1285
            $to = preg_replace('/(\$\d+)/', '$X', $to);
1286
1287
            for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) {
1288
                $to = preg_replace_callback(
1289
                    '/\$X/',
1290
                    static fn ($m) => '$' . $i,
1291
                    $to,
1292
                    1
1293
                );
1294
            }
1295
        }
1296
1297
        // Remplacez nos espaces réservés de regex par la chose réelle
1298
        // pour que le routeur n'ait pas besoin de savoir quoi que ce soit.
1299
        foreach ($this->placeholders as $tag => $pattern) {
1300
            $from = str_ireplace(':' . $tag, $pattern, $from);
1301
        }
1302
1303
        // S'il s'agit d'une redirection, aucun traitement
1304
        if (! isset($options['redirect']) && is_string($to)) {
1305
            // Si aucun espace de noms n'est trouvé, ajouter l'espace de noms par défaut
1306
            if (strpos($to, '\\') === false || strpos($to, '\\') > 0) {
1307
                $namespace = $options['namespace'] ?? $this->defaultNamespace;
1308
                $to        = trim($namespace, '\\') . '\\' . $to;
1309
            }
1310
            // Assurez-vous toujours que nous échappons à notre espace de noms afin de ne pas pointer vers
1311
            // \BlitzPHP\Routes\Controller::method.
1312
            $to = '\\' . ltrim($to, '\\');
1313
        }
1314
1315
        $name = $this->formatRouteName($options['as'] ?? $from);
0 ignored issues
show
Bug introduced by
It seems like $options['as'] ?? $from can also be of type array; however, parameter $name of BlitzPHP\Router\RouteCollection::formatRouteName() 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

1315
        $name = $this->formatRouteName(/** @scrutinizer ignore-type */ $options['as'] ?? $from);
Loading history...
1316
1317
        // Ne remplacez aucun 'from' existant afin que les routes découvertes automatiquement
1318
        // n'écrase pas les paramètres app/Config/Routes.
1319
        // les routes manuelement définies doivent toujours être la "source de vérité".
1320
        // cela ne fonctionne que parce que les routes découvertes sont ajoutées juste avant
1321
        // pour tenter de router la requête.
1322
        if (isset($this->routes[$verb][$name]) && ! $overwrite) {
1323
            return;
1324
        }
1325
1326
        $this->routes[$verb][$name] = [
1327
            'route' => [$from => $to],
1328
        ];
1329
1330
        $this->routesOptions[$verb][$from] = $options;
1331
1332
        // C'est une redirection ?
1333
        if (isset($options['redirect']) && is_numeric($options['redirect'])) {
1334
            $this->routes['*'][$name]['redirect'] = $options['redirect'];
1335
        }
1336
    }
1337
1338
    /**
1339
     * Compare le nom d'hôte transmis avec le nom d'hôte actuel sur cette demande de page.
1340
     *
1341
     * @param string $hostname Nom d'hôte dans les options d'itinéraire
1342
     */
1343
    private function checkHostname(string $hostname): bool
1344
    {
1345
        // Les appels CLI ne peuvent pas être sur le nom d'hôte.
1346
        if (! isset($this->httpHost) || is_cli()) {
1347
            return false;
1348
        }
1349
1350
        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

1350
        return strtolower(/** @scrutinizer ignore-type */ $this->httpHost) === strtolower($hostname);
Loading history...
1351
    }
1352
1353
    /**
1354
     * Compare le ou les sous-domaines transmis avec le sous-domaine actuel
1355
     * sur cette page demande.
1356
     *
1357
     * @param mixed $subdomains
1358
     */
1359
    private function checkSubdomains($subdomains): bool
1360
    {
1361
        // Les appels CLI ne peuvent pas être sur le sous-domaine.
1362
        if (! isset($_SERVER['HTTP_HOST'])) {
1363
            return false;
1364
        }
1365
1366
        if ($this->currentSubdomain === null) {
1367
            $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...
1368
        }
1369
1370
        if (! is_array($subdomains)) {
1371
            $subdomains = [$subdomains];
1372
        }
1373
1374
        // Les routes peuvent être limitées à n'importe quel sous-domaine. Dans ce cas, cependant,
1375
        // il nécessite la présence d'un sous-domaine.
1376
        if (! empty($this->currentSubdomain) && in_array('*', $subdomains, true)) {
1377
            return true;
1378
        }
1379
1380
        return in_array($this->currentSubdomain, $subdomains, true);
1381
    }
1382
1383
    /**
1384
     * Examine le HTTP_HOST pour obtenir une meilleure correspondance pour le sous-domaine. Ce
1385
     * ne sera pas parfait, mais devrait répondre à nos besoins.
1386
     *
1387
     * Ce n'est surtout pas parfait puisqu'il est possible d'enregistrer un domaine
1388
     * avec un point (.) dans le cadre du nom de domaine.
1389
     *
1390
     * @return mixed
1391
     */
1392
    private function determineCurrentSubdomain()
1393
    {
1394
        // Nous devons nous assurer qu'un schéma existe
1395
        // sur l'URL sinon parse_url sera mal interprété
1396
        // 'hôte' comme 'chemin'.
1397
        $url = $this->httpHost;
1398
        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

1398
        if (strpos(/** @scrutinizer ignore-type */ $url, 'http') !== 0) {
Loading history...
1399
            $url = 'http://' . $url;
1400
        }
1401
1402
        $parsedUrl = parse_url($url);
1403
1404
        $host = explode('.', $parsedUrl['host']);
1405
1406
        if ($host[0] === 'www') {
1407
            unset($host[0]);
1408
        }
1409
1410
        // Débarrassez-vous de tous les domaines, qui seront les derniers
1411
        unset($host[count($host) - 1]);
1412
1413
        // Compte pour les domaines .co.uk, .co.nz, etc.
1414
        if (end($host) === 'co') {
1415
            $host = array_slice($host, 0, -1);
1416
        }
1417
1418
        // S'il ne nous reste qu'une partie, alors nous n'avons pas de sous-domaine.
1419
        if (count($host) === 1) {
1420
            // Définissez-le sur false pour ne pas revenir ici.
1421
            return false;
1422
        }
1423
1424
        return array_shift($host);
1425
    }
1426
1427
	/**
1428
	 * Formate le nom des routes
1429
	 */
1430
	private function formatRouteName(string $name): string
1431
	{
1432
		$name = trim($name, '/');
1433
1434
		return strtolower(str_replace(['/', '\\', '_', '.', ' '], '.', $name));
1435
	}
1436
1437
    private function getControllerName(Closure|string $handler): ?string
1438
    {
1439
        if (! is_string($handler)) {
1440
            return null;
1441
        }
1442
1443
        [$controller] = explode('::', $handler, 2);
1444
1445
        return $controller;
1446
    }
1447
1448
    /**
1449
     * Renvoie la chaîne de paramètres de méthode comme `/$1/$2` pour les espaces réservés
1450
     */
1451
    private function getMethodParams(string $from): string
1452
    {
1453
        preg_match_all('/\(.+?\)/', $from, $matches);
1454
        $count = is_countable($matches[0]) ? count($matches[0]) : 0;
1455
1456
        $params = '';
1457
1458
        for ($i = 1; $i <= $count; $i++) {
1459
            $params .= '/$' . $i;
1460
        }
1461
1462
        return $params;
1463
    }
1464
1465
    private function processArrayCallableSyntax(string $from, array $to): string
1466
    {
1467
        // [classname, method]
1468
        // eg, [Home::class, 'index']
1469
        if (is_callable($to, true, $callableName)) {
1470
            // Si la route a des espaces réservés, ajoutez des paramètres automatiquement.
1471
            $params = $this->getMethodParams($from);
1472
1473
            if (strpos($callableName, '\\') !== false && $callableName[0] !== '\\') {
1474
                $callableName = '\\' . $callableName;
1475
            }
1476
1477
            return $callableName . $params;
1478
        }
1479
1480
        // [[classname, method], params]
1481
        // eg, [[Home::class, 'index'], '$1/$2']
1482
        if (
1483
            isset($to[0], $to[1])
1484
            && is_callable($to[0], true, $callableName)
1485
            && is_string($to[1])
1486
        ) {
1487
            $to = '\\' . $callableName . '/' . $to[1];
1488
        }
1489
1490
        return $to;
1491
    }
1492
1493
    /**
1494
     * Remplace la balise {locale} par la locale.
1495
     */
1496
    private function replaceLocale(string $route, ?string $locale = null): string
1497
    {
1498
        if (strpos($route, '{locale}') === false) {
1499
            return $route;
1500
        }
1501
1502
        // Vérifier les paramètres régionaux non valides
1503
        if ($locale !== null) {
1504
            if (! in_array($locale, config('app.supported_locales'), true)) {
0 ignored issues
show
Bug introduced by
config('app.supported_locales') of type BlitzPHP\Config\Config|null is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

1504
            if (! in_array($locale, /** @scrutinizer ignore-type */ config('app.supported_locales'), true)) {
Loading history...
1505
                $locale = null;
1506
            }
1507
        }
1508
1509
        if ($locale === null) {
1510
            $locale = Services::request()->getLocale();
1511
        }
1512
1513
        return strtr($route, ['{locale}' => $locale]);
1514
    }
1515
}
1516