Issues (536)

src/Router/RouteBuilder.php (1 issue)

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 BadMethodCallException;
15
use BlitzPHP\Container\Services;
16
use BlitzPHP\Utilities\Iterable\Arr;
17
use BlitzPHP\Utilities\String\Text;
18
use Closure;
19
use InvalidArgumentException;
20
21
/**
22
 * @method void  add(string $from, array|callable|string $to, array $options = [])     Enregistre une seule route à la collection.
23
 * @method $this addPlaceholder($placeholder, ?string $pattern = null)                 Enregistre une nouvelle contrainte auprès du système.
24
 * @method $this addRedirect(string $from, string $to, int $status = 302)              Ajoute une redirection temporaire d'une route à une autre.
25
 * @method $this as(string $name)                                                      Defini un nom de route
26
 * @method void  cli(string $from, array|callable|string $to, array $options = [])     Enregistre une route qui ne sera disponible que pour les requêtes de ligne de commande.
27
 * @method $this controller(string $controller)                                        Defini le contrôleur a utiliser dans le routage
28
 * @method void  delete(string $from, array|callable|string $to, array $options = [])  Enregistre une route qui ne sera disponible que pour les requêtes DELETE.
29
 * @method $this domain(string $domain)                                                Defini une restriction de domaine pour la route
30
 * @method $this fallback($callable = null)                                            Définit la classe/méthode qui doit être appelée si le routage ne trouver pas une correspondance.
31
 * @method void  get(string $from, array|callable|string $to, array $options = [])     Enregistre une route qui ne sera disponible que pour les requêtes GET.
32
 * @method void  head(string $from, array|callable|string $to, array $options = [])    Enregistre une route qui ne sera disponible que pour les requêtes HEAD.
33
 * @method $this hostname(string $hostname)                                            Defini une restriction de non d'hôte pour la route
34
 * @method $this middleware(array|string $middleware)
35
 * @method $this name(string $name)                                                    Defini un nom de route
36
 * @method $this namespace(string $namespace)                                          Defini le namespace a utiliser dans le routage
37
 * @method void  options(string $from, array|callable|string $to, array $options = []) Enregistre une route qui ne sera disponible que pour les requêtes OPTIONS.
38
 * @method void  patch(string $from, array|callable|string $to, array $options = [])   Enregistre une route qui ne sera disponible que pour les requêtes PATCH.
39
 * @method $this permanentRedirect(string $from, string $to)                           Ajoute une redirection permanente d'une route à une autre.
40
 * @method $this placeholder($placeholder, ?string $pattern = null)                    Enregistre une nouvelle contrainte auprès du système.
41
 * @method void  post(string $from, array|callable|string $to, array $options = [])    Enregistre une route qui ne sera disponible que pour les requêtes POST.
42
 * @method $this prefix(string $prefix)
43
 * @method $this priority(int $priority)
44
 * @method void  put(string $from, array|callable|string $to, array $options = [])     Enregistre une route qui ne sera disponible que pour les requêtes PUT.
45
 * @method $this redirect(string $from, string $to, int $status = 302)                 Ajoute une redirection temporaire d'une route à une autre.
46
 * @method $this set404Override($callable = null)                                      Définit la classe/méthode qui doit être appelée si le routage ne trouver pas une correspondance.
47
 * @method $this setAutoRoute(bool $value)                                             Si TRUE, le système tentera de faire correspondre l'URI avec Contrôleurs en faisant correspondre chaque segment avec des dossiers/fichiers dans CONTROLLER_PATH, lorsqu'aucune correspondance n'a été trouvée pour les routes définies.
48
 * @method $this setDefaultConstraint(string $placeholder)                             Définit la contrainte par défaut à utiliser dans le système. Typiquement à utiliser avec la méthode 'ressource'.
49
 * @method $this setDefaultController(string $value)                                   Définit le contrôleur par défaut à utiliser lorsqu'aucun autre contrôleur n'a été spécifié.
50
 * @method $this setDefaultMethod(string $value)                                       Définit la méthode par défaut pour appeler le contrôleur lorsqu'aucun autre méthode a été définie dans la route.
51
 * @method $this setDefaultNamespace(string $value)                                    Définit l'espace de noms par défaut à utiliser pour les contrôleurs lorsqu'aucun autre n'a été spécifié.
52
 * @method $this setPrioritize(bool $enabled = true)                                   Activer ou désactiver le tri des routes par priorité
53
 * @method $this setTranslateURIDashes(bool $value)                                    Indique au système s'il faut convertir les tirets des chaînes URI en traits de soulignement.
54
 * @method $this subdomain(string $subdomain)                                          Defini une restriction de sous domaine pour la route
55
 * @method $this useSupportedLocalesOnly(bool $useOnly)                                Indique au router de limiter ou non les routes avec l'espace réservé {locale} à App::$supportedLocales
56
 * @method $this where($placeholder, ?string $pattern = null)                          Enregistre une nouvelle contrainte auprès du système.
57
 */
58
final class RouteBuilder
59
{
60
    /**
61
     * Les attributs à transmettre au routeur.
62
     */
63
    private array $attributes = [];
64
65
    /**
66
     * Les méthodes à transmettre dynamiquement au routeur.
67
     */
68
    private array $passthru = [
69
        'add', 'cli', 'delete', 'get', 'head', 'options', 'post', 'put', 'patch',
70
    ];
71
72
    /**
73
     * Les attributs qui peuvent être définis via cette classe.
74
     */
75
    private array $allowedAttributes = [
76
        'as', 'controller', 'domain', 'hostname', 'middlewares', 'middleware',
77
        'name', 'namespace', 'where', 'prefix', 'priority', 'subdomain',
78
    ];
79
80
    /**
81
     * Les attributs qui sont des alias.
82
     */
83
    private array $aliases = [
84
        'name'        => 'as',
85
        'middlewares' => 'middleware',
86
    ];
87
88
    private array $allowedMethods = [
89
        'addPlaceholder', 'placeholder',
90
        'addRedirect', 'redirect', 'permanentRedirect',
91
        'set404Override', 'setAutoRoute', 'fallback',
92
        'setDefaultConstraint', 'setDefaultController', 'setDefaultMethod', 'setDefaultNamespace',
93
        'setTranslateURIDashes', 'setPrioritize', 'useSupportedLocalesOnly',
94
    ];
95
96
    /**
97
     * Constructeur
98
     */
99
    public function __construct(private RouteCollection $collection)
100
    {
101
    }
102
103
    /**
104
     * Gérez dynamiquement les appels dans le registraire de routage.
105
     *
106
     * @return self
107
     *
108
     * @throws BadMethodCallException
109
     */
110
    public function __call(string $method, array $parameters = [])
111
    {
112
        if (in_array($method, $this->passthru, true)) {
113 14
            return $this->registerRoute($method, ...$parameters);
114
        }
115
116
        if (in_array($method, $this->allowedAttributes, true)) {
117
            if (in_array($method, ['middleware', 'middlewares'], true)) {
118 2
                $parameters = is_array($parameters[0]) ? $parameters[0] : $parameters;
119
120 2
                return $this->attribute($method, array_merge($this->attributes[$method] ?? [], $parameters));
121
            }
122
123 10
            return $this->attribute($method, $parameters[0]);
124
        }
125
126
        if (in_array($method, $this->allowedMethods, true)) {
127 10
            $collection = $this->collection->{$method}(...$parameters);
128
129
            if ($collection instanceof RouteCollection) {
130 10
                Services::set(RouteCollection::class, $collection);
131 10
                $this->collection = $collection;
132
            }
133
134 10
            return $this;
135
        }
136
137 2
        throw new BadMethodCallException(sprintf('La méthode %s::%s n\'existe pas.', self::class, $method));
138
    }
139
140
    /**
141
     * Limite les routes à un ENVIRONNEMENT spécifié ou ils ne fonctionneront pas.
142
     */
143
    public function environment(string $env, Closure $callback): void
144
    {
145
        if ($env === config('app.environment')) {
0 ignored issues
show
The condition $env === config('app.environment') is always false.
Loading history...
146 2
            $callback($this);
147
        }
148
    }
149
150
    public function form(string $from, $to, array $options = []): void
151
    {
152 2
        $options += $this->attributes;
153 2
        $this->attributes = [];
154
155
        if (isset($options['unique'])) {
156 2
            $this->collection->form($from, $to, $options);
157
158 2
            return;
159
        }
160
161 2
        $toGet = $toPost = $to;
162
163
        if (is_string($to)) {
164 2
            $parts = explode('::', $to);
165
        } elseif (is_array($to)) {
166 2
            $parts = $to;
167
        } else {
168 2
            $parts = [];
169
        }
170
171
        if (count($parts) === 2) { // Si on a defini le controleur et la methode
172 2
            $controller = $parts[0];
173 2
            $method     = $parts[1];
174
        } elseif (count($parts) === 1) {
175
            // Si on est ici, ca veut dire 2 choses.
176
            // - Soit c'est la methode qui est definie (utilisation d'un string)
177
            // - Soit c'est le controleur qui est defini (utilisation d'un array)
178
179
            if (is_array($to)) {
180 2
                $controller = $parts[0];
181 2
                $method     = $this->collection->getDefaultMethod();
182
            } else {
183 2
                $controller = '';
184 2
                $method     = $parts[0];
185
            }
186
        }
187
188
        if (isset($controller, $method)) {
189 2
            $toGet  = implode('::', array_filter([$controller, Text::camel('form_' . $method)]));
190 2
            $toPost = implode('::', array_filter([$controller, Text::camel('process_' . $method)]));
191
        }
192
193 2
        $this->collection->get($from, $toGet, $options);
194 2
        $this->collection->post($from, $toPost, $options);
195
    }
196
197
    /**
198
     * Create a route group with shared attributes.
199
     */
200
    public function group(callable $callback): void
201
    {
202 4
        $prefix = $this->attributes['prefix'] ?? '';
203 4
        unset($this->attributes['prefix']);
204
205 4
        $this->collection->group($prefix, $this->attributes, fn () => $callback($this));
206
    }
207
208
    /**
209
     * Ajoute une seule route à faire correspondre pour plusieurs verbes HTTP.
210
     *
211
     * Exemple:
212
     *  $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
213
     *
214
     * @param array|Closure|string $to
215
     */
216
    public function match(array $verbs = [], string $from = '', $to = '', array $options = []): void
217
    {
218 2
        $this->collection->match($verbs, $from, $to, $this->attributes + $options);
219
    }
220
221
    /**
222
     * Crée une collection de routes basées sur les verbes HTTP pour un contrôleur de présentation.
223
     *
224
     * Options possibles :
225
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
226
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
227
     *
228
     * Example:
229
     *
230
     *      $route->presenter('photos');
231
     *
232
     *      // Génère les routes suivantes
233
     *      HTTP Verb | Path        | Action        | Used for...
234
     *      ----------+-------------+---------------+-----------------
235
     *      GET         /photos             index           affiche le tableau des tous les objets photo
236
     *      GET         /photos/show/{id}   show            affiche un objet photo spécifique, toutes les propriétés
237
     *      GET         /photos/new         new             affiche un formulaire pour un objet photo vide, avec les propriétés par défaut
238
     *      POST        /photos/create      create          traitement du formulaire pour une nouvelle photo
239
     *      GET         /photos/edit/{id}   edit            affiche un formulaire d'édition pour un objet photo spécifique, propriétés modifiables
240
     *      POST        /photos/update/{id} update          traitement des données du formulaire d'édition
241
     *      GET         /photos/remove/{id} remove          affiche un formulaire pour confirmer la suppression d'un objet photo spécifique
242
     *      POST        /photos/delete/{id} delete          suppression de l'objet photo spécifié
243
     *
244
     * @param string $name    Le nom du contrôleur vers lequel router.
245
     * @param array  $options Une liste des façons possibles de personnaliser le routage.
246
     */
247
    public function presenter(string $name, array $options = []): void
248
    {
249 2
        $this->collection->presenter($name, $this->attributes + $options);
250
251 2
        $this->attributes = [];
252
    }
253
254
    /**
255
     * Crée une collection de routes basés sur HTTP-verb pour un contrôleur.
256
     *
257
     * Options possibles :
258
     * 'controller' - Personnalisez le nom du contrôleur utilisé dans la route 'to'
259
     * 'placeholder' - L'expression régulière utilisée par le routeur. La valeur par défaut est '(:any)'
260
     * 'websafe' - - '1' si seuls les verbes HTTP GET et POST sont pris en charge
261
     *
262
     * Exemple:
263
     *
264
     *      $route->resource('photos');
265
     *
266
     *      // Genère les routes suivantes:
267
     *      HTTP Verb | Path        | Action        | Used for...
268
     *      ----------+-------------+---------------+-----------------
269
     *      GET         /photos             index           un tableau d'objets photo
270
     *      GET         /photos/new         new             un objet photo vide, avec des propriétés par défaut
271
     *      GET         /photos/{id}/edit   edit            un objet photo spécifique, propriétés modifiables
272
     *      GET         /photos/{id}        show            un objet photo spécifique, toutes les propriétés
273
     *      POST        /photos             create          un nouvel objet photo, à ajouter à la ressource
274
     *      DELETE      /photos/{id}        delete          supprime l'objet photo spécifié
275
     *      PUT/PATCH   /photos/{id}        update          propriétés de remplacement pour la photo existante
276
     *
277
     *  Si l'option 'websafe' est présente, les chemins suivants sont également disponibles :
278
     *
279
     *      POST		/photos/{id}/delete delete
280
     *      POST        /photos/{id}        update
281
     *
282
     * @param string $name    Le nom de la ressource/du contrôleur vers lequel router.
283
     * @param array  $options Une liste des façons possibles de personnaliser le routage.
284
     */
285
    public function resource(string $name, array $options = []): void
286
    {
287 2
        $this->collection->resource($name, $this->attributes + $options);
288
289 2
        $this->attributes = [];
290
    }
291
292
    /**
293
     * Une méthode de raccourci pour ajouter un certain nombre de routes en une seule fois.
294
     * Il ne permet pas de définir des options sur la route, ou de définir la méthode utilisée.
295
     */
296
    public function map(array $routes = [], array $options = []): void
297
    {
298 2
        $this->collection->map($routes, $this->attributes + $options);
299
    }
300
301
    /**
302
     * Spécifie une route qui n'affichera qu'une vue.
303
     * Ne fonctionne que pour les requêtes GET.
304
     */
305
    public function view(string $from, string $view, array $options = []): void
306
    {
307 2
        $this->collection->view($from, $view, $this->attributes + $options);
308
    }
309
310
    /**
311
     * Defini une valeur pour l'attribut donné
312
     *
313
     * @param mixed $value
314
     *
315
     * @throws InvalidArgumentException
316
     */
317
    private function attribute(string $key, $value): self
318
    {
319
        if (! in_array($key, $this->allowedAttributes, true)) {
320 10
            throw new InvalidArgumentException("L'attribute [{$key}] n'existe pas.");
321
        }
322
323 10
        $this->attributes[Arr::get($this->aliases, $key, $key)] = $value;
324
325 10
        return $this;
326
    }
327
328
    /**
329
     * Enregistre une nouvelle route avec le routeur.
330
     *
331
     * @param mixed $to
332
     */
333
    private function registerRoute(string $method, string $from, $to, array $options = []): self
334
    {
335 14
        $this->collection->{$method}($from, $to, $this->attributes + $options);
336
337 14
        $this->attributes = [];
338
339 14
        return $this;
340
    }
341
}
342