Passed
Push — main ( c6deb1...79ccdf )
by Dimitri
12:23
created

RouteCollection::get404Override()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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\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 = false;
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
     * Tableau de fichiers qui contiendrait les définitions de routes.
76
     */
77
    protected array $routeFiles = [];
78
79
    /**
80
     * Espaces réservés définis pouvant être utilisés.
81
     */
82
    protected array $placeholders = [
83
        'any'      => '.*',
84
        'segment'  => '[^/]+',
85
        'alphanum' => '[a-zA-Z0-9]+',
86
        'num'      => '[0-9]+',
87
        'alpha'    => '[a-zA-Z]+',
88
        'hash'     => '[^/]+',
89
        'slug'     => '[a-z0-9-]+',
90
    ];
91
92
    /**
93
     * Tableau de toutes les routes et leurs mappages.
94
     *
95
     * @example
96
     * ```php
97
     * [
98
     *     verb => [
99
     *         routeName => [
100
     *             'route' => [
101
     *                 routeKey(regex) => handler,
102
     *             ],
103
     *             'redirect' => statusCode,
104
     *         ]
105
     *     ],
106
     * ]
107
     * ```
108
     */
109
    protected array $routes = [
110
        '*'       => [],
111
        'options' => [],
112
        'get'     => [],
113
        'head'    => [],
114
        'post'    => [],
115
        'put'     => [],
116
        'delete'  => [],
117
        'trace'   => [],
118
        'connect' => [],
119
        'cli'     => [],
120
    ];
121
122
    /**
123
     * Tableau des noms des routes
124
	 * 
125
     * [
126
     *     verb => [
127
     *         routeName => routeKey(regex)
128
     *     ],
129
     * ]
130
     */
131
    protected array $routesNames = [
132
        '*'       => [],
133
        'options' => [],
134
        'get'     => [],
135
        'head'    => [],
136
        'post'    => [],
137
        'put'     => [],
138
        'delete'  => [],
139
        'trace'   => [],
140
        'connect' => [],
141
        'cli'     => [],
142
    ];
143
144
    /**
145
     * Tableaux des options des routes.
146
     *
147
     * @example
148
     * ```php
149
     * [
150
     *     verb => [
151
     *         routeKey(regex) => [
152
     *             key => value,
153
     *         ]
154
     *     ],
155
     * ]
156
     * ```
157
     */
158
    protected array $routesOptions = [];
159
160
    /**
161
     * La méthode actuelle par laquelle le script est appelé.
162
     */
163
    protected string $HTTPVerb = '*';
164
165
    /**
166
     * La liste par défaut des méthodes HTTP (et CLI pour l'utilisation de la ligne de commande)
167
     * qui est autorisé si aucune autre méthode n'est fournie.
168
     */
169
    protected array $defaultHTTPMethods = [
170
        'options',
171
        'get',
172
        'head',
173
        'post',
174
        'put',
175
        'delete',
176
        'trace',
177
        'connect',
178
        'cli',
179
    ];
180
181
    /**
182
     * Le nom du groupe de route courant
183
     */
184
    protected ?string $group = null;
185
186
    /**
187
     * Le sous domaine courant
188
     */
189
    protected ?string $currentSubdomain = null;
190
191
    /**
192
     * Stocke une copie des options actuelles en cours appliqué lors de la création.
193
     */
194
    protected ?array $currentOptions = null;
195
196
    /**
197
     * Un petit booster de performances.
198
     */
199
    protected bool $didDiscover = false;
200
201
    /**
202
     * Drapeau pour trier les routes par priorité.
203
     */
204
    protected bool $prioritize = false;
205
206
    /**
207
     * Indicateur de détection de priorité de route.
208
     */
209
    protected bool $prioritizeDetected = false;
210
211
    /**
212
     * Drapeau pour limiter ou non les routes avec l'espace réservé {locale} vers App::$supportedLocales
213
     */
214
    protected bool $useSupportedLocalesOnly = false;
215
216
    /**
217
     * Le nom d'hôte actuel de $_SERVER['HTTP_HOST']
218
     */
219
    private ?string $httpHost = null;
220
221
    /**
222
     * Constructor
223
	 * 
224
	 * @var Locator $locator Descripteur du localisateur de fichiers à utiliser.
225
     */
226
    public function __construct(protected Locator $locator, object $routing)
227
    {
228
        $this->httpHost = Services::request()->getEnv('HTTP_HOST');
229
230
		// Configuration basée sur le fichier de config. Laissez le fichier routes substituer.
231
        $this->defaultNamespace   = $routing->default_namespace ?: $this->defaultNamespace;
232
        $this->defaultController  = $routing->default_controller ?: $this->defaultController;
233
        $this->defaultMethod      = $routing->default_method ?: $this->defaultMethod;
234
        $this->translateURIDashes = $routing->translate_uri_dashes ?: $this->translateURIDashes;
235
        $this->override404        = $routing->override404 ?: $this->override404;
236
        $this->autoRoute          = $routing->auto_route ?: $this->autoRoute;
237
        $this->routeFiles         = $routing->route_files ?: $this->routeFiles;
238
        $this->prioritize         = $routing->prioritize ?: $this->prioritize;
239
240
		// Normaliser la chaîne de path dans le tableau routeFiles.
241
        foreach ($this->routeFiles as $routeKey => $routesFile) {
242
            $this->routeFiles[$routeKey] = realpath($routesFile) ?: $routesFile;
243
        }
244
    }
245
246
    /**
247
     * Charge le fichier des routes principales et découvre les routes.
248
     *
249
     * Charge une seule fois sauf réinitialisation.
250
     */
251
    public function loadRoutes(string $routesFile = CONFIG_PATH . 'routes.php'): self
252
    {
253
        if ($this->didDiscover) {
254
            return $this;
255
        }
256
257
        // Incluez le fichier routesFile s'il n'existe pas. 
258
		// Ne conserver que pour les fins BC pour l'instant. 
259
        $routeFiles = $this->routeFiles;
260
        if (! in_array($routesFile, $routeFiles, true)) {
261
            $routeFiles[] = $routesFile;
262
        }
263
		
264
		// Nous avons besoin de cette variable dans la portée locale pour que les fichiers de route puissent y accéder.
265
        $routes = $this;
0 ignored issues
show
Unused Code introduced by
The assignment to $routes is dead and can be removed.
Loading history...
266
267
		foreach ($routeFiles as $routesFile) {
268
            if (! is_file($routesFile)) {
269
				logger()->warning(sprintf('Fichier de route introuvable : "%s"', $routesFile));
0 ignored issues
show
Bug introduced by
Are you sure the usage of logger() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
270
271
                continue;
272
            }
273
274
            require_once $routesFile;
275
        }
276
277
        $this->discoverRoutes();
278
279
        return $this;
280
    }
281
282
    /**
283
     * Réinitialisez les routes, afin qu'un cas de test puisse fournir le
284
     * ceux explicites nécessaires pour cela.
285
     */
286
    public function resetRoutes()
287
    {
288
        $this->routes = $this->routesNames = ['*' => []];
289
290
        foreach ($this->defaultHTTPMethods as $verb) {
291
			$this->routes[$verb]      = [];
292
			$this->routesNames[$verb] = [];
293
        }
294
295
		$this->routesOptions = [];
296
297
        $this->prioritizeDetected = false;
298
        $this->didDiscover        = false;
299
    }
300
301
    /**
302
     * {@inheritDoc}
303
     */
304
    public function addPlaceholder($placeholder, ?string $pattern = null): self
305
    {
306
        if (! is_array($placeholder)) {
307
            $placeholder = [$placeholder => $pattern];
308
        }
309
310
        $this->placeholders = array_merge($this->placeholders, $placeholder);
311
312
        return $this;
313
    }
314
315
    /**
316
     * {@inheritDoc}
317
     */
318
    public function setDefaultNamespace(string $value): self
319
    {
320
        $this->defaultNamespace = esc(strip_tags($value));
321
        $this->defaultNamespace = rtrim($this->defaultNamespace, '\\') . '\\';
322
323
        return $this;
324
    }
325
326
    /**
327
     * {@inheritDoc}
328
     */
329
    public function setDefaultController(string $value): self
330
    {
331
        $this->defaultController = esc(strip_tags($value));
332
333
        return $this;
334
    }
335
336
    /**
337
     * {@inheritDoc}
338
     */
339
    public function setDefaultMethod(string $value): self
340
    {
341
        $this->defaultMethod = esc(strip_tags($value));
342
343
        return $this;
344
    }
345
346
    /**
347
     * {@inheritDoc}
348
     */
349
    public function setTranslateURIDashes(bool $value): self
350
    {
351
        $this->translateURIDashes = $value;
352
353
        return $this;
354
    }
355
356
    /**
357
     * {@inheritDoc}
358
     */
359
    public function setAutoRoute(bool $value): self
360
    {
361
        $this->autoRoute = $value;
362
363
        return $this;
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369
    public function set404Override($callable = null): self
370
    {
371
        $this->override404 = $callable;
372
373
        return $this;
374
    }
375
376
    /**
377
     * {@inheritDoc}
378
     */
379
    public function get404Override()
380
    {
381
        return $this->override404;
382
    }
383
384
    /**
385
     * Tentera de découvrir d'éventuelles routes supplémentaires, soit par
386
     * les espaces de noms PSR4 locaux ou via des packages Composer sélectionnés.
387
     */
388
    protected function discoverRoutes()
389
    {
390
        if ($this->didDiscover) {
391
            return;
392
        }
393
394
        // Nous avons besoin de cette variable dans la portée locale pour que les fichiers de route puissent y accéder.
395
        $routes = $this;
0 ignored issues
show
Unused Code introduced by
The assignment to $routes is dead and can be removed.
Loading history...
396
397
        $files    = $this->locator->search('Config/routes.php');
398
        
399
        foreach ($files as $file) {
400
            // N'incluez plus notre fichier principal...
401
            if (in_array($file, $this->routeFiles, true)) {
402
                continue;
403
            }
404
405
            include_once $file;
406
        }
407
408
        $this->didDiscover = true;
409
    }
410
411
    /**
412
     * Définit la contrainte par défaut à utiliser dans le système. Typiquement
413
     * à utiliser avec la méthode 'ressource'.
414
     */
415
    public function setDefaultConstraint(string $placeholder): self
416
    {
417
        if (array_key_exists($placeholder, $this->placeholders)) {
418
            $this->defaultPlaceholder = $placeholder;
419
        }
420
421
        return $this;
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427
    public function getDefaultController(): string
428
    {
429
        return preg_replace('#Controller$#i', '', $this->defaultController) . 'Controller';
430
    }
431
432
    /**
433
     * {@inheritDoc}
434
     */
435
    public function getDefaultMethod(): string
436
    {
437
        return $this->defaultMethod;
438
    }
439
440
    /**
441
     * {@inheritDoc}
442
     */
443
    public function getDefaultNamespace(): string
444
    {
445
        return $this->defaultNamespace;
446
    }
447
448
    public function getPlaceholders(): array
449
    {
450
        return $this->placeholders;
451
    }
452
453
    /**
454
     *{@inheritDoc}
455
     */
456
    public function shouldTranslateURIDashes(): bool
457
    {
458
        return $this->translateURIDashes;
459
    }
460
461
    /**
462
     * {@inheritDoc}
463
     */
464
    public function shouldAutoRoute(): bool
465
    {
466
        return $this->autoRoute;
467
    }
468
469
    /**
470
     * Activer ou désactiver le tri des routes par priorité
471
     */
472
    public function setPrioritize(bool $enabled = true): self
473
    {
474
        $this->prioritize = $enabled;
475
476
        return $this;
477
    }
478
479
    /**
480
     * Définissez le drapeau qui limite ou non les routes avec l'espace réservé {locale} à App::$supportedLocales
481
     */
482
    public function useSupportedLocalesOnly(bool $useOnly): self
483
    {
484
        $this->useSupportedLocalesOnly = $useOnly;
485
486
        return $this;
487
    }
488
489
    /**
490
     * Obtenez le drapeau qui limite ou non les routes avec l'espace réservé {locale} vers App::$supportedLocales
491
     */
492
    public function shouldUseSupportedLocalesOnly(): bool
493
    {
494
        return $this->useSupportedLocalesOnly;
495
    }
496
497
    /**
498
     * {@inheritDoc}
499
     */
500
    public function getRegisteredControllers(?string $verb = '*'): array
501
    {
502
        $controllers = [];
503
504
        if ($verb === '*') {
505
            foreach ($this->defaultHTTPMethods as $tmpVerb) {
506
                foreach ($this->routes[$tmpVerb] as $route) {
507
                    $controller = $this->getControllerName($route['handler']);
508
                    if ($controller !== null) {
509
                        $controllers[] = $controller;
510
                    }
511
                }
512
            }
513
        } else {
514
            $routes = $this->getRoutes($verb);
515
516
            foreach ($routes as $handler) {
517
                $controller = $this->getControllerName($handler);
518
                if ($controller !== null) {
519
                    $controllers[] = $controller;
520
                }
521
            }
522
        }
523
524
        return array_unique($controllers);
525
    }
526
527
    /**
528
     * {@inheritDoc}
529
     */
530
    public function getRoutes(?string $verb = null, bool $includeWildcard = true): array
531
    {
532
        if (empty($verb)) {
533
            $verb = $this->getHTTPVerb();
534
        }
535
536
        // Puisqu'il s'agit du point d'entrée du routeur,
537
        // prenez un moment pour faire toute découverte de route
538
        // que nous pourrions avoir besoin de faire.
539
        $this->discoverRoutes();
540
541
        $routes     = [];
542
        if (isset($this->routes[$verb])) {
543
            // Conserve les itinéraires du verbe actuel au début afin qu'ils soient
544
            // mis en correspondance avant l'un des itinéraires génériques "add".
545
			$collection = $includeWildcard ? $this->routes[$verb] + ($this->routes['*'] ?? []) : $this->routes[$verb];
546
547
		    foreach ($collection as $routeKey => $r) {
548
                $routes[$routeKey] = $r['handler'];
549
            }
550
        }
551
552
        // tri des routes par priorité
553
        if ($this->prioritizeDetected && $this->prioritize && $routes !== []) {
554
            $order = [];
555
556
            foreach ($routes as $key => $value) {
557
                $key                    = $key === '/' ? $key : ltrim($key, '/ ');
558
                $priority               = $this->getRoutesOptions($key, $verb)['priority'] ?? 0;
559
                $order[$priority][$key] = $value;
560
            }
561
562
            ksort($order);
563
            $routes = array_merge(...$order);
564
        }
565
566
        return $routes;
567
    }
568
569
    /**
570
     * Renvoie une ou toutes les options d'itinéraire
571
     */
572
    public function getRoutesOptions(?string $from = null, ?string $verb = null): array
573
    {
574
        $options = $this->loadRoutesOptions($verb);
575
576
        return $from ? $options[$from] ?? [] : $options;
577
    }
578
579
    /**
580
     * {@inheritDoc}
581
     */
582
    public function getHTTPVerb(): string
583
    {
584
        return $this->HTTPVerb;
585
    }
586
587
    /**
588
     * {@inheritDoc}
589
     */
590
    public function setHTTPVerb(string $verb): self
591
    {
592
        $this->HTTPVerb = strtolower($verb);
593
594
        return $this;
595
    }
596
597
    /**
598
     * Une méthode de raccourci pour ajouter un certain nombre d'itinéraires en une seule fois.
599
     * Il ne permet pas de définir des options sur l'itinéraire, ou de
600
     * définir la méthode utilisée.
601
     */
602
    public function map(array $routes = [], ?array $options = null): self
603
    {
604
        foreach ($routes as $from => $to) {
605
            $this->add($from, $to, $options);
606
        }
607
608
        return $this;
609
    }
610
611
    /**
612
     * {@inheritDoc}
613
     */
614
    public function add(string $from, $to, ?array $options = null): self
615
    {
616
        $this->create('*', $from, $to, $options);
617
618
        return $this;
619
    }
620
621
    /**
622
     * Ajoute une redirection temporaire d'une route à une autre. Utilisé pour
623
     * rediriger le trafic des anciennes routes inexistantes vers les nouvelles
624
     * itinéraires déplacés.
625
     *
626
     * @param string $from   Le modèle à comparer
627
     * @param string $to     Soit un nom de route ou un URI vers lequel rediriger
628
     * @param int    $status Le code d'état HTTP qui doit être renvoyé avec cette redirection
629
     */
630
    public function addRedirect(string $from, string $to, int $status = 302): self
631
    {
632
        // Utilisez le modèle de la route nommée s'il s'agit d'une route nommée.
633
        if (array_key_exists($to, $this->routesNames['*'])) {
634
			$routeName  = $to;
635
            $routeKey   = $this->routesNames['*'][$routeName];
636
            $redirectTo = [$routeKey => $this->routes['*'][$routeKey]['handler']];
637
638
        } elseif (array_key_exists($to, $this->routes['get'])) {
639
            $routeName  = $to;
640
            $routeKey   = $this->routesNames['get'][$routeName];
641
            $redirectTo = [$routeKey => $this->routes['get'][$routeKey]['handler']];
642
        } else {
643
            // La route nommee n'a pas ete trouvée
644
            $redirectTo = $to;
645
        }
646
647
        $this->create('*', $from, $redirectTo, ['redirect' => $status]);
648
649
        return $this;
650
    }
651
652
    /**
653
     * {@inheritDoc}
654
	 * 
655
	 * @param string $routeKey cle de route ou route nommee
656
     */
657
    public function isRedirect(string $routeKey): bool
658
    {
659
		return isset($this->routes['*'][$routeKey]['redirect']);
660
    }
661
662
    /**
663
     * {@inheritDoc}
664
	 * 
665
	 * @param string $routeKey cle de route ou route nommee
666
     */
667
    public function getRedirectCode(string $routeKey): int
668
    {
669
        if (isset($this->routes['*'][$routeKey]['redirect'])) {
670
            return $this->routes['*'][$routeKey]['redirect'];
671
        }
672
673
        return 0;
674
    }
675
676
    /**
677
     * Regroupez une série de routes sous un seul segment d'URL. C'est pratique
678
     * pour regrouper des éléments dans une zone d'administration, comme :
679
     *
680
     * Example:
681
     *     // Creates route: admin/users
682
     *     $route->group('admin', function() {
683
     *            $route->resource('users');
684
     *     });
685
     *
686
     * @param string         $name      Le nom avec lequel grouper/préfixer les routes.
687
     * @param array|callable ...$params
688
     */
689
    public function group(string $name, ...$params)
690
    {
691
        $oldGroup   = $this->group;
692
        $oldOptions = $this->currentOptions;
693
694
        // Pour enregistrer une route, nous allons définir un indicateur afin que notre routeur
695
        // donc il verra le nom du groupe.
696
        // Si le nom du groupe est vide, nous continuons à utiliser le nom du groupe précédemment construit.
697
        $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

697
        $this->group = implode('/', array_unique(explode('/', /** @scrutinizer ignore-type */ $name ? (rtrim($oldGroup ?? '', '/') . '/' . ltrim($name, '/')) : $oldGroup)));
Loading history...
698
699
        $callback = array_pop($params);
700
701
        if ($params && is_array($params[0])) {
702
            $this->currentOptions = array_shift($params);
703
        }
704
705
        if (is_callable($callback)) {
706
            $callback($this);
707
        }
708
709
        $this->group          = $oldGroup;
710
        $this->currentOptions = $oldOptions;
711
    }
712
713
    /*
714
     * ------------------------------------------------- -------------------
715
     * Routage basé sur les verbes HTTP
716
     * ------------------------------------------------- -------------------
717
     * Le routage fonctionne ici car, comme le fichier de configuration des routes est lu,
718
     * les différentes routes basées sur le verbe HTTP ne seront ajoutées qu'à la mémoire en mémoire
719
     * routes s'il s'agit d'un appel qui doit répondre à ce verbe.
720
     *
721
     * Le tableau d'options est généralement utilisé pour transmettre un 'as' ou var, mais peut
722
     * être étendu à l'avenir. Voir le docblock pour la méthode 'add' ci-dessus pour
723
     * liste actuelle des options disponibles dans le monde.*/
724
725
    /**
726
     * Crée une collection d'itinéraires basés sur HTTP-verb pour un contrôleur.
727
     *
728
     * Options possibles :
729
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
730
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
731
     * 'websafe' - - '1' si seuls les verbes HTTP GET et POST sont pris en charge
732
     *
733
     * Exemple:
734
     *
735
     *      $route->resource('photos');
736
     *
737
     *      // Genère les routes suivantes:
738
     *      HTTP Verb | Path        | Action        | Used for...
739
     *      ----------+-------------+---------------+-----------------
740
     *      GET         /photos             index           un tableau d'objets photo
741
     *      GET         /photos/new         new             un objet photo vide, avec des propriétés par défaut
742
     *      GET         /photos/{id}/edit   edit            un objet photo spécifique, propriétés modifiables
743
     *      GET         /photos/{id}        show            un objet photo spécifique, toutes les propriétés
744
     *      POST        /photos             create          un nouvel objet photo, à ajouter à la ressource
745
     *      DELETE      /photos/{id}        delete          supprime l'objet photo spécifié
746
     *      PUT/PATCH   /photos/{id}        update          propriétés de remplacement pour la photo existante
747
     *
748
     *  Si l'option 'websafe' est présente, les chemins suivants sont également disponibles :
749
     *
750
     *      POST		/photos/{id}/delete delete
751
     *      POST        /photos/{id}        update
752
     *
753
     * @param string     $name    Le nom de la ressource/du contrôleur vers lequel router.
754
     * @param array|null $options Une liste des façons possibles de personnaliser le routage.
755
     */
756
    public function resource(string $name, ?array $options = null): self
757
    {
758
        // Afin de permettre la personnalisation de la route, le
759
        // les ressources sont envoyées à, nous devons avoir un nouveau nom
760
        // pour stocker les valeurs.
761
        $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
762
763
        // Si un nouveau contrôleur est spécifié, alors nous remplaçons le
764
        // valeur de $name avec le nom du nouveau contrôleur.
765
        if (isset($options['controller'])) {
766
            $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

766
            $newName = ucfirst(/** @scrutinizer ignore-type */ esc(strip_tags($options['controller'])));
Loading history...
767
        }
768
769
        // Afin de permettre la personnalisation des valeurs d'identifiant autorisées
770
        // nous avons besoin d'un endroit pour les stocker.
771
        $id = $options['placeholder'] ?? $this->placeholders[$this->defaultPlaceholder] ?? '(:segment)';
772
773
        // On s'assure de capturer les références arrière
774
        $id = '(' . trim($id, '()') . ')';
775
776
        $methods = isset($options['only']) ? (is_string($options['only']) ? explode(',', $options['only']) : $options['only']) : ['index', 'show', 'create', 'update', 'delete', 'new', 'edit'];
777
778
        if (isset($options['except'])) {
779
            $options['except'] = is_array($options['except']) ? $options['except'] : explode(',', $options['except']);
780
781
            foreach ($methods as $i => $method) {
782
                if (in_array($method, $options['except'], true)) {
783
                    unset($methods[$i]);
784
                }
785
            }
786
        }
787
788
		$routeName = $name;
789
		if (isset($options['as']) || isset($options['name'])) {
790
			$routeName = trim($options['as'] ?? $options['name'], " .");
791
			unset($options['name'], $options['as']);
792
		}
793
794
        if (in_array('index', $methods, true)) {
795
            $this->get($name, $newName . '::index', $options + [
796
				'as' => $routeName . '.index',
797
			]);
798
        }
799
        if (in_array('new', $methods, true)) {
800
            $this->get($name . '/new', $newName . '::new', $options + [
801
				'as' => $routeName . '.new',
802
			]);
803
        }
804
        if (in_array('edit', $methods, true)) {
805
            $this->get($name . '/' . $id . '/edit', $newName . '::edit/$1', $options + [
806
				'as' => $routeName . '.edit',
807
			]);
808
        }
809
        if (in_array('show', $methods, true)) {
810
            $this->get($name . '/' . $id, $newName . '::show/$1', $options + [
811
				'as' => $routeName . '.show',
812
			]);
813
        }
814
        if (in_array('create', $methods, true)) {
815
            $this->post($name, $newName . '::create', $options + [
816
				'as' => $routeName . '.create',
817
			]);
818
        }
819
        if (in_array('update', $methods, true)) {
820
			$this->match(['put', 'patch'], $name . '/' . $id, $newName . '::update/$1', $options + [
821
				'as' => $routeName . '.update',
822
			]);
823
        }
824
        if (in_array('delete', $methods, true)) {
825
            $this->delete($name . '/' . $id, $newName . '::delete/$1', $options + [
826
				'as' => $routeName . '.delete',
827
			]);
828
        }
829
830
        // Websafe ? la suppression doit être vérifiée avant la mise à jour en raison du nom de la méthode
831
        if (isset($options['websafe'])) {
832
            if (in_array('delete', $methods, true)) {
833
                $this->post($name . '/' . $id . '/delete', $newName . '::delete/$1', $options + [
834
					'as' => $routeName . '.websafe.delete',
835
				]);
836
            }
837
            if (in_array('update', $methods, true)) {
838
                $this->post($name . '/' . $id, $newName . '::update/$1', $options + [
839
					'as' => $routeName . '.websafe.update',
840
				]);
841
            }
842
        }
843
844
        return $this;
845
    }
846
847
    /**
848
     * Crée une collection de routes basées sur les verbes HTTP pour un contrôleur de présentateur.
849
     *
850
     * Options possibles :
851
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
852
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
853
     *
854
     * Example:
855
     *
856
     *      $route->presenter('photos');
857
     *
858
     *      // Génère les routes suivantes
859
     *      HTTP Verb | Path        | Action        | Used for...
860
     *      ----------+-------------+---------------+-----------------
861
     *      GET         /photos             index           affiche le tableau des tous les objets photo
862
     *      GET         /photos/show/{id}   show            affiche un objet photo spécifique, toutes les propriétés
863
     *      GET         /photos/new         new             affiche un formulaire pour un objet photo vide, avec les propriétés par défaut
864
     *      POST        /photos/create      create          traitement du formulaire pour une nouvelle photo
865
     *      GET         /photos/edit/{id}   edit            affiche un formulaire d'édition pour un objet photo spécifique, propriétés modifiables
866
     *      POST        /photos/update/{id} update          traitement des données du formulaire d'édition
867
     *      GET         /photos/remove/{id} remove          affiche un formulaire pour confirmer la suppression d'un objet photo spécifique
868
     *      POST        /photos/delete/{id} delete          suppression de l'objet photo spécifié
869
     *
870
     * @param string     $name    Le nom du contrôleur vers lequel router.
871
     * @param array|null $options Une liste des façons possibles de personnaliser le routage.
872
     */
873
    public function presenter(string $name, ?array $options = null): self
874
    {
875
        // Afin de permettre la personnalisation de la route, le
876
        // les ressources sont envoyées à, nous devons avoir un nouveau nom
877
        // pour stocker les valeurs.
878
        $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
879
880
        // Si un nouveau contrôleur est spécifié, alors nous remplaçons le
881
        // valeur de $name avec le nom du nouveau contrôleur.
882
        if (isset($options['controller'])) {
883
            $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

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

1256
        $routeKey = $this->replaceLocale(/** @scrutinizer ignore-type */ $routeKey, $locale);
Loading history...
1257
1258
        return '/' . ltrim($routeKey, '/');
1259
    }
1260
1261
    /**
1262
     * Charger les options d'itinéraires en fonction du verbe
1263
     */
1264
    protected function loadRoutesOptions(?string $verb = null): array
1265
    {
1266
        $verb = strtolower($verb ?: $this->getHTTPVerb());
1267
        
1268
        $options = $this->routesOptions[$verb] ?? [];
1269
1270
        if (isset($this->routesOptions['*'])) {
1271
            foreach ($this->routesOptions['*'] as $key => $val) {
1272
                if (isset($options[$key])) {
1273
                    $extraOptions  = array_diff_key($val, $options[$key]);
1274
                    $options[$key] = array_merge($options[$key], $extraOptions);
1275
                } else {
1276
                    $options[$key] = $val;
1277
                }
1278
            }
1279
        }
1280
1281
        return $options;
1282
    }
1283
1284
    /**
1285
     * Fait le gros du travail de création d'un itinéraire réel. Vous devez spécifier
1286
     * la ou les méthodes de demande pour lesquelles cette route fonctionnera. Ils peuvent être séparés
1287
     * par un caractère pipe "|" s'il y en a plusieurs.
1288
     *
1289
     * @param array|Closure|string $to
1290
     */
1291
    protected function create(string $verb, string $from, $to, ?array $options = null)
1292
    {
1293
        $overwrite = false;
1294
        $prefix    = $this->group === null ? '' : $this->group . '/';
1295
1296
        $from = esc(strip_tags(rtrim($prefix, '/') . '/' . ltrim($from, '/')));
1297
1298
        // Alors que nous voulons ajouter une route dans un groupe de '/',
1299
        // ça ne marche pas avec la correspondance, alors supprimez-les...
1300
        if ($from !== '/') {
1301
            $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

1301
            $from = trim(/** @scrutinizer ignore-type */ $from, '/');
Loading history...
1302
        }
1303
1304
        if (is_string($to) && strpos($to, '::') === false && class_exists($to) && method_exists($to, '__invoke')) {
1305
            $to = [$to, '__invoke'];
1306
        }
1307
1308
        // Lors de la redirection vers une route nommée, $to est un tableau tel que `['zombies' => '\Zombies::index']`.
1309
        if (is_array($to) && isset($to[0])) {
1310
            $to = $this->processArrayCallableSyntax($from, $to);
1311
        }
1312
1313
        $options = array_merge($this->currentOptions ?? [], $options ?? []);
1314
1315
        // Détection de priorité de routage
1316
        if (isset($options['priority'])) {
1317
            $options['priority'] = abs((int) $options['priority']);
1318
1319
            if ($options['priority'] > 0) {
1320
                $this->prioritizeDetected = true;
1321
            }
1322
        }
1323
1324
        // Limitation du nom d'hôte ?
1325
        if (! empty($options['hostname'])) {
1326
            // @todo déterminer s'il existe un moyen de mettre les hôtes sur liste blanche ?
1327
            if (! $this->checkHostname($options['hostname'])) {
1328
                return;
1329
            }
1330
1331
            $overwrite = true;
1332
        }
1333
1334
        // Limitation du nom sous-domaine ?
1335
        elseif (! empty($options['subdomain'])) {
1336
            // Si nous ne correspondons pas au sous-domaine actuel, alors
1337
            // nous n'avons pas besoin d'ajouter la route.
1338
            if (! $this->checkSubdomains($options['subdomain'])) {
1339
                return;
1340
            }
1341
1342
            $overwrite = true;
1343
        }
1344
1345
        // Sommes-nous en train de compenser les liaisons ?
1346
        // Si oui, occupez-vous d'eux ici en un
1347
        // abattre en plein vol.
1348
        if (isset($options['offset']) && is_string($to)) {
1349
            // Récupère une chaîne constante avec laquelle travailler.
1350
            $to = preg_replace('/(\$\d+)/', '$X', $to);
1351
1352
            for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) {
1353
                $to = preg_replace_callback(
1354
                    '/\$X/',
1355
                    static fn ($m) => '$' . $i,
1356
                    $to,
1357
                    1
1358
                );
1359
            }
1360
        }
1361
1362
		$routeKey = $from;
1363
1364
        // Remplacez nos espaces réservés de regex par la chose réelle
1365
        // pour que le routeur n'ait pas besoin de savoir quoi que ce soit.
1366
        foreach ($this->placeholders as $tag => $pattern) {
1367
            $from = str_ireplace(':' . $tag, $pattern, $routeKey);
1368
        }
1369
1370
        // S'il s'agit d'une redirection, aucun traitement
1371
        if (! isset($options['redirect']) && is_string($to)) {
1372
            // Si aucun espace de noms n'est trouvé, ajouter l'espace de noms par défaut
1373
            if (strpos($to, '\\') === false || strpos($to, '\\') > 0) {
1374
                $namespace = $options['namespace'] ?? $this->defaultNamespace;
1375
                $to        = trim($namespace, '\\') . '\\' . $to;
1376
            }
1377
            // Assurez-vous toujours que nous échappons à notre espace de noms afin de ne pas pointer vers
1378
            // \BlitzPHP\Routes\Controller::method.
1379
            $to = '\\' . ltrim($to, '\\');
1380
        }
1381
1382
        $name = $this->formatRouteName($options['as'] ?? $routeKey);
1383
1384
        // Ne remplacez aucun 'from' existant afin que les routes découvertes automatiquement
1385
        // n'écrase pas les paramètres app/Config/Routes.
1386
        // les routes manuelement définies doivent toujours être la "source de vérité".
1387
        // cela ne fonctionne que parce que les routes découvertes sont ajoutées juste avant
1388
        // pour tenter de router la requête.
1389
        $routeKeyExists = isset($this->routes[$verb][$routeKey]);
1390
        if ((isset($this->routesNames[$verb][$name]) || $routeKeyExists) && ! $overwrite) {
1391
            return;
1392
        }
1393
1394
		$this->routes[$verb][$routeKey] = [
1395
            'name'    => $name,
1396
            'handler' => $to,
1397
            'from'    => $from,
1398
        ];
1399
        $this->routesOptions[$verb][$routeKey] = $options;
1400
        $this->routesNames[$verb][$name]       = $routeKey;
1401
1402
        // C'est une redirection ?
1403
        if (isset($options['redirect']) && is_numeric($options['redirect'])) {
1404
            $this->routes['*'][$routeKey]['redirect'] = $options['redirect'];
1405
        }
1406
    }
1407
1408
    /**
1409
     * Compare le nom d'hôte transmis avec le nom d'hôte actuel sur cette demande de page.
1410
     *
1411
     * @param string $hostname Nom d'hôte dans les options d'itinéraire
1412
     */
1413
    private function checkHostname(string $hostname): bool
1414
    {
1415
        // Les appels CLI ne peuvent pas être sur le nom d'hôte.
1416
        if (! isset($this->httpHost) || is_cli()) {
1417
            return false;
1418
        }
1419
1420
        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

1420
        return strtolower(/** @scrutinizer ignore-type */ $this->httpHost) === strtolower($hostname);
Loading history...
1421
    }
1422
1423
    /**
1424
     * Compare le ou les sous-domaines transmis avec le sous-domaine actuel
1425
     * sur cette page demande.
1426
     *
1427
     * @param mixed $subdomains
1428
     */
1429
    private function checkSubdomains($subdomains): bool
1430
    {
1431
        // Les appels CLI ne peuvent pas être sur le sous-domaine.
1432
        if (! isset($this->httpHost)) {
1433
            return false;
1434
        }
1435
1436
        if ($this->currentSubdomain === null) {
1437
            $this->currentSubdomain = $this->determineCurrentSubdomain();
1438
        }
1439
1440
        if (! is_array($subdomains)) {
1441
            $subdomains = [$subdomains];
1442
        }
1443
1444
        // Les routes peuvent être limitées à n'importe quel sous-domaine. Dans ce cas, cependant,
1445
        // il nécessite la présence d'un sous-domaine.
1446
        if (! empty($this->currentSubdomain) && in_array('*', $subdomains, true)) {
1447
            return true;
1448
        }
1449
1450
        return in_array($this->currentSubdomain, $subdomains, true);
1451
    }
1452
1453
    /**
1454
     * Examine le HTTP_HOST pour obtenir une meilleure correspondance pour le sous-domaine. Ce
1455
     * ne sera pas parfait, mais devrait répondre à nos besoins.
1456
     *
1457
     * Ce n'est surtout pas parfait puisqu'il est possible d'enregistrer un domaine
1458
     * avec un point (.) dans le cadre du nom de domaine.
1459
     *
1460
     * @return mixed
1461
     */
1462
    private function determineCurrentSubdomain()
1463
    {
1464
        // Nous devons nous assurer qu'un schéma existe
1465
        // sur l'URL sinon parse_url sera mal interprété
1466
        // 'hôte' comme 'chemin'.
1467
        $url = $this->httpHost;
1468
        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

1468
        if (strpos(/** @scrutinizer ignore-type */ $url, 'http') !== 0) {
Loading history...
1469
            $url = 'http://' . $url;
1470
        }
1471
1472
        $parsedUrl = parse_url($url);
1473
1474
        $host = explode('.', $parsedUrl['host']);
1475
1476
        if ($host[0] === 'www') {
1477
            unset($host[0]);
1478
        }
1479
1480
        // Débarrassez-vous de tous les domaines, qui seront les derniers
1481
        unset($host[count($host) - 1]);
1482
1483
        // Compte pour les domaines .co.uk, .co.nz, etc.
1484
        if (end($host) === 'co') {
1485
            $host = array_slice($host, 0, -1);
1486
        }
1487
1488
        // S'il ne nous reste qu'une partie, alors nous n'avons pas de sous-domaine.
1489
        if (count($host) === 1) {
1490
            // Définissez-le sur false pour ne pas revenir ici.
1491
            return false;
1492
        }
1493
1494
        return array_shift($host);
1495
    }
1496
1497
	/**
1498
	 * Formate le nom des routes
1499
	 */
1500
	private function formatRouteName(string $name): string
1501
	{
1502
		$name = trim($name, '/');
1503
1504
		return strtolower(str_replace(['/', '\\', '_', '.', ' '], '.', $name));
1505
	}
1506
1507
    private function getControllerName(Closure|string $handler): ?string
1508
    {
1509
        if (! is_string($handler)) {
1510
            return null;
1511
        }
1512
1513
        [$controller] = explode('::', $handler, 2);
1514
1515
        return $controller;
1516
    }
1517
1518
    /**
1519
     * Renvoie la chaîne de paramètres de méthode comme `/$1/$2` pour les espaces réservés
1520
     */
1521
    private function getMethodParams(string $from): string
1522
    {
1523
        preg_match_all('/\(.+?\)/', $from, $matches);
1524
        $count = is_countable($matches[0]) ? count($matches[0]) : 0;
1525
1526
        $params = '';
1527
1528
        for ($i = 1; $i <= $count; $i++) {
1529
            $params .= '/$' . $i;
1530
        }
1531
1532
        return $params;
1533
    }
1534
1535
    private function processArrayCallableSyntax(string $from, array $to): string
1536
    {
1537
        // [classname, method]
1538
        // eg, [Home::class, 'index']
1539
        if (is_callable($to, true, $callableName)) {
1540
            // Si la route a des espaces réservés, ajoutez des paramètres automatiquement.
1541
            $params = $this->getMethodParams($from);
1542
1543
            if (strpos($callableName, '\\') !== false && $callableName[0] !== '\\') {
1544
                $callableName = '\\' . $callableName;
1545
            }
1546
1547
            return $callableName . $params;
1548
        }
1549
1550
        // [[classname, method], params]
1551
        // eg, [[Home::class, 'index'], '$1/$2']
1552
        if (
1553
            isset($to[0], $to[1])
1554
            && is_callable($to[0], true, $callableName)
1555
            && is_string($to[1])
1556
        ) {
1557
            $to = '\\' . $callableName . '/' . $to[1];
1558
        }
1559
1560
        return $to;
1561
    }
1562
1563
    /**
1564
     * Remplace la balise {locale} par la locale.
1565
     */
1566
    private function replaceLocale(string $route, ?string $locale = null): string
1567
    {
1568
        if (strpos($route, '{locale}') === false) {
1569
            return $route;
1570
        }
1571
1572
        // Vérifier les paramètres régionaux non valides
1573
        if ($locale !== null) {
1574
            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

1574
            if (! in_array($locale, /** @scrutinizer ignore-type */ config('app.supported_locales'), true)) {
Loading history...
1575
                $locale = null;
1576
            }
1577
        }
1578
1579
        if ($locale === null) {
1580
            $locale = Services::request()->getLocale();
1581
        }
1582
1583
        return strtr($route, ['{locale}' => $locale]);
1584
    }
1585
}
1586