Dispatcher::gatherOutput()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 19
ccs 0
cts 5
cp 0
crap 12
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\Cache\ResponseCache;
15
use BlitzPHP\Container\Container;
16
use BlitzPHP\Container\Services;
17
use BlitzPHP\Contracts\Event\EventManagerInterface;
18
use BlitzPHP\Contracts\Http\ResponsableInterface;
19
use BlitzPHP\Contracts\Router\RouteCollectionInterface;
20
use BlitzPHP\Contracts\Support\Arrayable;
21
use BlitzPHP\Controllers\BaseController;
22
use BlitzPHP\Debug\Timer;
23
use BlitzPHP\Enums\Method;
24
use BlitzPHP\Exceptions\PageNotFoundException;
25
use BlitzPHP\Exceptions\RedirectException;
26
use BlitzPHP\Exceptions\ValidationException;
27
use BlitzPHP\Http\MiddlewareQueue;
28
use BlitzPHP\Http\MiddlewareRunner;
29
use BlitzPHP\Http\Request;
30
use BlitzPHP\Http\Response;
31
use BlitzPHP\Http\Uri;
32
use BlitzPHP\Utilities\Helpers;
33
use BlitzPHP\Utilities\String\Text;
34
use BlitzPHP\Validation\ErrorBag;
35
use Closure;
36
use Exception;
37
use InvalidArgumentException;
38
use Psr\Http\Message\ResponseInterface;
39
use Psr\Http\Message\ServerRequestInterface;
40
use stdClass;
41
use Throwable;
42
43
/**
44
 * Cette classe est la porte d'entree du framework. Elle analyse la requete,
45
 * recherche la route correspondante et invoque le bon controleurm puis renvoie la reponse.
46
 */
47
class Dispatcher
48
{
49
    /**
50
     * Heure de démarrage de l'application.
51
     *
52
     * @var mixed
53
     */
54
    protected $startTime;
55
56
    /**
57
     * Durée totale d'exécution de l'application
58
     *
59
     * @var float
60
     */
61
    protected $totalTime;
62
63
    /**
64
     * Main application configuration
65
     *
66
     * @var stdClass
67
     */
68
    protected $config;
69
70
    /**
71
     * instance Timer.
72
     *
73
     * @var Timer
74
     */
75
    protected $timer;
76
77
    /**
78
     * requête courrante.
79
     *
80
     * @var Request
81
     */
82
    protected $request;
83
84
    /**
85
     * Reponse courrante.
86
     *
87
     * @var Response
88
     */
89
    protected $response;
90
91
    /**
92
     * Router à utiliser.
93
     *
94
     * @var Router
95
     */
96
    protected $router;
97
98
    private ?MiddlewareQueue $middleware = null;
99
100
    /**
101
     * Contrôleur à utiliser.
102
     *
103
     * @var (Closure(mixed...): ResponseInterface|string)|string
0 ignored issues
show
Documentation Bug introduced by
The doc comment (Closure(mixed...): Resp...nterface|string)|string at position 1 could not be parsed: Expected ')' at position 1, but found 'Closure'.
Loading history...
104
     */
105
    protected $controller;
106
107
    /**
108
     * Méthode du ontrôleur à exécuter.
109
     *
110
     * @var string
111
     */
112
    protected $method;
113
114
    /**
115
     * Gestionnaire de sortie à utiliser.
116
     *
117
     * @var string
118
     */
119
    protected $output;
120
121
    /**
122
     * Chemin de requête à utiliser.
123
     *
124
     * @var string
125
     *
126
     * @deprecated No longer used.
127
     */
128
    protected $path;
129
130
    /**
131
     * Niveau de mise en mémoire tampon de sortie de l'application
132
     */
133
    protected int $bufferLevel = 0;
134
135
    /**
136
     * Mise en cache des pages Web
137
     */
138
    protected ResponseCache $pageCache;
139
140
    /**
141
     * Constructeur.
142
     */
143
    public function __construct(protected EventManagerInterface $event, protected Container $container)
144
    {
145
        $this->startTime = microtime(true);
146
        $this->config    = (object) config('app');
147
148
        $this->pageCache = service('responsecache');
149
    }
150
151
    /**
152
     * Retourne la methode invoquee
153
     */
154
    public static function getMethod(): ?string
155
    {
156
        $method = Services::singleton(self::class)->method;
157
        if (empty($method)) {
158
            $method = service('routes')->getDefaultMethod();
159
        }
160
161
        return $method;
162
    }
163
164
    /**
165
     * Retourne le contrôleur utilisé
166
     *
167
     * @return Closure|string
168
     */
169
    public static function getController(bool $fullName = true)
170
    {
171
        $routes = service('routes');
172
173
        $controller = Services::singleton(self::class)->controller;
174
        if (empty($controller)) {
175
            $controller = $routes->getDefaultController();
176
        }
177
178
        if (! $fullName && is_string($controller)) {
179
            $controller = str_replace($routes->getDefaultNamespace(), '', $controller);
180
        }
181
182
        return $controller;
183
    }
184
185
    /**
186
     * Lancez l'application !
187
     *
188
     * C'est "la boucle" si vous voulez. Le principal point d'entrée dans le script
189
     * qui obtient les instances de classe requises, déclenche les filtres,
190
     * essaie d'acheminer la réponse, charge le contrôleur et généralement
191
     * fait fonctionner toutes les pièces ensemble.
192
     *
193
     * @return bool|mixed|ResponseInterface|ServerRequestInterface
194
     *
195
     * @throws Exception
196
     * @throws RedirectException
197
     */
198
    public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
199
    {
200
        $this->pageCache->setTtl(0);
201
        $this->bufferLevel = ob_get_level();
202
203
        $this->startBenchmark();
204
205
        $this->getRequestObject();
206
        $this->getResponseObject();
207
208
        $this->event->emit('pre_system');
209
210
        $this->timer->stop('bootstrap');
211
212
        $this->initMiddlewareQueue();
213
214
        try {
215
            $this->response = $this->handleRequest($routes, config('cache'));
0 ignored issues
show
Bug introduced by
It seems like config('cache') can also be of type T and object; however, parameter $cacheConfig of BlitzPHP\Router\Dispatcher::handleRequest() does only seem to accept array|null, 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

215
            $this->response = $this->handleRequest($routes, /** @scrutinizer ignore-type */ config('cache'));
Loading history...
216
        } catch (ResponsableInterface $e) {
217
            $this->outputBufferingEnd();
218
            $this->response = $e->getResponse();
219
        } catch (PageNotFoundException $e) {
220
            $this->response = $this->display404errors($e);
221
        } catch (Throwable $e) {
222
            $this->outputBufferingEnd();
223
224
            throw $e;
225
        }
226
227
        // Y a-t-il un événement post-système ?
228
        $this->event->emit('post_system');
229
230
        if ($returnResponse) {
231
            return $this->response;
232
        }
233
234
        return $this->sendResponse();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->sendResponse() targeting BlitzPHP\Router\Dispatcher::sendResponse() 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...
235
    }
236
237
    /**
238
     * Gère la logique de requête principale et déclenche le contrôleur.
239
     *
240
     * @throws PageNotFoundException
241
     * @throws RedirectException
242
     */
243
    protected function handleRequest(?RouteCollectionInterface $routes = null, ?array $cacheConfig = null): ResponseInterface
244
    {
245
        $routeMiddlewares = $this->dispatchRoutes($routes);
246
247
        /**
248
         * Ajouter des middlewares de routes
249
         */
250
        foreach ($routeMiddlewares as $middleware) {
251
            $this->middleware->append($middleware);
0 ignored issues
show
Bug introduced by
The method append() does not exist on null. ( Ignorable by Annotation )

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

251
            $this->middleware->/** @scrutinizer ignore-call */ 
252
                               append($middleware);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
252
        }
253
254
        $this->middleware->append($this->bootApp());
255
256
        // Enregistrer notre URI actuel en tant qu'URI précédent dans la session
257
        // pour une utilisation plus sûre et plus précise avec la fonction d'assistance `previous_url()`.
258
        $this->storePreviousURL(current_url(true));
259
260
        return (new MiddlewareRunner())->run($this->middleware, $this->request);
0 ignored issues
show
Bug introduced by
It seems like $this->middleware can also be of type null; however, parameter $queue of BlitzPHP\Http\MiddlewareRunner::run() does only seem to accept BlitzPHP\Http\MiddlewareQueue, 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

260
        return (new MiddlewareRunner())->run(/** @scrutinizer ignore-type */ $this->middleware, $this->request);
Loading history...
261
    }
262
263
    /**
264
     * Démarrer le benchmark
265
     *
266
     * La minuterie est utilisée pour afficher l'exécution totale du script à la fois dans la
267
     * barre d'outils de débogage, et éventuellement sur la page affichée.
268
     */
269
    protected function startBenchmark()
270
    {
271
        if ($this->startTime === null) {
272
            $this->startTime = microtime(true);
273
        }
274
275
        $this->timer = service('timer');
276
        $this->timer->start('total_execution', $this->startTime);
277
        $this->timer->start('bootstrap');
278
    }
279
280
    /**
281
     * Définit un objet Request à utiliser pour cette requête.
282
     * Utilisé lors de l'exécution de certains tests.
283
     */
284
    public function setRequest(ServerRequestInterface $request): self
285
    {
286
        $this->request = $request;
0 ignored issues
show
Documentation Bug introduced by
$request is of type Psr\Http\Message\ServerRequestInterface, but the property $request was declared to be of type BlitzPHP\Http\Request. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
287
288
        return $this;
289
    }
290
291
    /**
292
     * Obtenez notre objet Request et définissez le protocole du serveur en fonction des informations fournies
293
     * par le serveur.
294
     */
295
    protected function getRequestObject()
296
    {
297
        if ($this->request instanceof ServerRequestInterface) {
0 ignored issues
show
introduced by
$this->request is always a sub-type of Psr\Http\Message\ServerRequestInterface.
Loading history...
298
            return;
299
        }
300
301
        if (is_cli() && ! on_test()) {
302
            // @codeCoverageIgnoreStart
303
            // $this->request = Services::clirequest($this->config);
304
            // @codeCoverageIgnoreEnd
305
        }
306
307
        $version = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
308
        if (! is_numeric($version)) {
309
            $version = substr($version, strpos($version, '/') + 1);
310
        }
311
312
        // Assurez-vous que la version est au bon format
313
        $version = number_format((float) $version, 1);
314
315
        $this->request = service('request')->withProtocolVersion($version);
316
    }
317
318
    /**
319
     * Obtenez notre objet Response et définissez des valeurs par défaut, notamment
320
     * la version du protocole HTTP et une réponse réussie par défaut.
321
     */
322
    protected function getResponseObject()
323
    {
324
        // Supposons le succès jusqu'à preuve du contraire.
325
        $this->response = service('response')->withStatus(200);
326
327
        if (! is_cli() || on_test()) {
328
        }
329
330
        $this->response = $this->response->withProtocolVersion($this->request->getProtocolVersion());
331
    }
332
333
    /**
334
     * Renvoie un tableau avec nos statistiques de performances de base collectées.
335
     */
336
    public function getPerformanceStats(): array
337
    {
338
        // Après le filtre, la barre d'outils de débogage nécessite 'total_execution'.
339
        $this->totalTime = $this->timer->getElapsedTime('total_execution');
340
341
        return [
342
            'startTime' => $this->startTime,
343
            'totalTime' => $this->totalTime,
344
        ];
345
    }
346
347
    /**
348
     * Fonctionne avec le routeur pour
349
     * faire correspondre une route à l'URI actuel. Si la route est une
350
     * "route de redirection", gérera également la redirection.
351
     *
352
     * @param RouteCollectionInterface|null $routes Une interface de collecte à utiliser à la place
353
     *                                              du fichier de configuration.
354
     *
355
     * @return list<string>
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Router\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
356
     *
357
     * @throws RedirectException
358
     */
359
    protected function dispatchRoutes(?RouteCollectionInterface $routes = null): array
360
    {
361
        $this->timer->start('routing');
362
363
        if ($routes === null) {
364
            $routes = service('routes')->loadRoutes();
365
        }
366
367
        // $routes est defini dans app/Config/routes.php
368
        $this->router = service('router', $routes, $this->request);
369
370
        $this->outputBufferingStart();
371
372
        $this->controller = $this->router->handle($this->request->getPath());
373
        $this->method     = $this->router->methodName();
374
375
        // Si un segment {locale} correspondait dans la route finale,
376
        // alors nous devons définir les paramètres régionaux corrects sur notre requête.
377
        if ($this->router->hasLocale()) {
378
            $this->request = $this->request->withLocale($this->router->getLocale());
379
        }
380
381
        $this->timer->stop('routing');
382
383
        return $this->router->getMiddlewares();
384
    }
385
386
    /**
387
     * Maintenant que tout a été configuré, cette méthode tente d'exécuter le
388
     * méthode du contrôleur et lancez le script. S'il n'en est pas capable, le fera
389
     * afficher l'erreur Page introuvable appropriée.
390
     */
391
    protected function startController()
392
    {
393
        $this->timer->start('controller');
394
        $this->timer->start('controller_constructor');
395
396
        // Aucun contrôleur spécifié - nous ne savons pas quoi faire maintenant.
397
        if (empty($this->controller)) {
398
            throw PageNotFoundException::emptyController();
399
        }
400
401
        // Est-il acheminé vers une Closure ?
402
        if (is_object($this->controller) && ($this->controller::class === 'Closure')) {
403
            if (empty($returned = $this->container->call($this->controller, $this->router->params()))) {
0 ignored issues
show
Bug introduced by
$this->controller of type object is incompatible with the type array|callable|string expected by parameter $callback of BlitzPHP\Container\Container::call(). ( Ignorable by Annotation )

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

403
            if (empty($returned = $this->container->call(/** @scrutinizer ignore-type */ $this->controller, $this->router->params()))) {
Loading history...
404
                $returned = $this->outputBufferingEnd();
405
            }
406
407
            return $returned;
408
        }
409
410
        // Essayez de charger automatiquement la classe
411
        if (! class_exists($this->controller, true) || ($this->method[0] === '_' && $this->method !== '__invoke')) {
412
            throw PageNotFoundException::controllerNotFound($this->controller, $this->method);
413
        }
414
415
        return null;
416
    }
417
418
    /**
419
     * Instancie la classe contrôleur.
420
     *
421
     * @return BaseController|mixed
422
     */
423
    private function createController(ServerRequestInterface $request, ResponseInterface $response)
424
    {
425
        /**
426
         * @var BaseController
427
         */
428
        $class = $this->container->get($this->controller);
429
430
        if (method_exists($class, 'initialize')) {
431
            $class->initialize($request, $response, service('logger'));
432
        }
433
434
        $this->timer->stop('controller_constructor');
435
436
        return $class;
437
    }
438
439
    /**
440
     * Exécute le contrôleur, permettant aux méthodes _remap de fonctionner.
441
     *
442
     * @param mixed $class
443
     *
444
     * @return mixed
445
     */
446
    protected function runController($class)
447
    {
448
        $params = $this->router->params();
449
        $method = $this->method;
450
451
        if (method_exists($class, '_remap')) {
452
            $params = [$method, $params];
453
            $method = '_remap';
454
        }
455
456
        if (empty($output = $this->container->call([$class, $method], $params))) {
457
            $output = $this->outputBufferingEnd();
458
        }
459
460
        $this->timer->stop('controller');
461
462
        return $output;
463
    }
464
465
    /**
466
     * Affiche une page d'erreur 404 introuvable. S'il est défini, essaiera de
467
     * appelez le contrôleur/méthode 404Override qui a été défini dans la configuration de routage.
468
     */
469
    protected function display404errors(PageNotFoundException $e)
470
    {
471
        // Existe-t-il une dérogation 404 disponible ?
472
        if ($override = $this->router->get404Override()) {
473
            $returned = null;
474
475
            if ($override instanceof Closure) {
476
                echo $override($e->getMessage());
477
            } elseif (is_array($override)) {
478
                $this->timer->start('controller');
479
                $this->timer->start('controller_constructor');
480
481
                $this->controller = $override[0];
482
                $this->method     = $override[1];
483
484
                $controller = $this->createController($this->request, $this->response);
485
                $returned   = $controller->{$this->method}($e->getMessage());
486
487
                $this->timer->stop('controller');
488
            }
489
490
            unset($override);
491
492
            $this->gatherOutput($returned);
493
494
            return $this->response;
495
        }
496
497
        // Affiche l'erreur 404
498
        $this->response = $this->response->withStatus($e->getCode());
499
500
        $this->outputBufferingEnd();
501
502
        throw PageNotFoundException::pageNotFound(! on_prod() || is_cli() ? $e->getMessage() : '');
503
    }
504
505
    /**
506
     * Rassemble la sortie du script à partir du tampon, remplace certaines balises d'exécutions
507
     * d'horodatage dans la sortie et affiche la barre d'outils de débogage, si nécessaire.
508
     *
509
     * @param mixed|null $returned
510
     */
511
    protected function gatherOutput($returned = null)
512
    {
513
        $this->output = $this->outputBufferingEnd();
514
515
        // Si le contrôleur a renvoyé un objet de réponse,
516
        // nous devons en saisir le corps pour qu'il puisse
517
        // être ajouté à tout ce qui aurait déjà pu être ajouté avant de faire le écho.
518
        // Nous devons également enregistrer l'instance localement
519
        // afin que tout changement de code d'état, etc., ait lieu.
520
        if ($returned instanceof ResponseInterface) {
521
            $this->response = $returned;
0 ignored issues
show
Documentation Bug introduced by
$returned is of type Psr\Http\Message\ResponseInterface, but the property $response was declared to be of type BlitzPHP\Http\Response. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
522
            $returned       = $returned->getBody()->getContents();
523
        }
524
525
        if (is_string($returned)) {
526
            $this->output .= $returned;
527
        }
528
529
        $this->response = $this->response->withBody(to_stream($this->output));
530
    }
531
532
    /**
533
     * Si nous avons un objet de session à utiliser, stockez l'URI actuel
534
     * comme l'URI précédent. Ceci est appelé juste avant d'envoyer la
535
     * réponse au client, et le rendra disponible à la prochaine demande.
536
     *
537
     * Cela permet au fournisseur une détection plus sûre et plus fiable de la fonction previous_url().
538
     *
539
     * @param string|Uri $uri
540
     */
541
    public function storePreviousURL($uri)
542
    {
543
        // Ignorer les requêtes CLI
544
        if (is_cli() && ! on_test()) {
545
            return; // @codeCoverageIgnore
546
        }
547
548
        // Ignorer les requêtes AJAX
549
        if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) {
0 ignored issues
show
Bug introduced by
The method isAJAX() does not exist on BlitzPHP\Http\Request. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

549
        if (method_exists($this->request, 'isAJAX') && $this->request->/** @scrutinizer ignore-call */ isAJAX()) {
Loading history...
550
            return;
551
        }
552
553
        // Ignorer les reponses non-HTML
554
        if (! str_contains($this->response->getHeaderLine('Content-Type'), 'text/html')) {
555
            return;
556
        }
557
558
        // Ceci est principalement nécessaire lors des tests ...
559
        if (is_string($uri)) {
560
            $uri = single_service('uri', $uri);
561
        }
562
563
        session()->setPreviousUrl(Uri::createURIString(
564
            $uri->getScheme(),
565
            $uri->getAuthority(),
566
            $uri->getPath(),
567
            $uri->getQuery(),
568
            $uri->getFragment()
569
        ));
570
    }
571
572
    /**
573
     * Renvoie la sortie de cette requête au client.
574
     * C'est ce qu'il attendait !
575
     */
576
    protected function sendResponse()
577
    {
578
        if (! $this->isAjaxRequest()) {
579
            $this->response = service('toolbar')->process(
580
                $this->getPerformanceStats(),
581
                $this->request,
582
                $this->response
583
            );
584
        }
585
586
        service('emitter')->emit($this->response);
587
    }
588
589
    protected function emitResponse()
590
    {
591
        $this->gatherOutput();
592
        $this->sendResponse();
593
    }
594
595
    /**
596
     * Construit une reponse adequate en fonction du retour du controleur
597
     *
598
     * @param mixed $returned
599
     */
600
    protected function formatResponse(ResponseInterface $response, $returned): ResponseInterface
601
    {
602
        if ($returned instanceof ResponseInterface) {
603
            return $returned;
604
        }
605
606
        if ($returned instanceof ResponsableInterface) {
607
            return $returned->toResponse($this->request);
608
        }
609
610
        if ($returned instanceof Arrayable) {
611
            $returned = $returned->toArray();
612
        }
613
614
        if (is_object($returned)) {
615
            if (method_exists($returned, 'toArray')) {
616
                $returned = $returned->toArray();
617
            } elseif (method_exists($returned, 'toJSON')) {
618
                $returned = $returned->toJSON();
619
            } elseif (method_exists($returned, '__toString')) {
620
                $returned = $returned->__toString();
621
            } else {
622
                $returned = (array) $returned;
623
            }
624
        }
625
626
        if (is_array($returned)) {
627
            $returned = Helpers::collect($returned);
628
            $response = $response->withHeader('Content-Type', 'application/json');
629
        }
630
631
        try {
632
            $response = $response->withBody(to_stream($returned));
633
        } catch (InvalidArgumentException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
634
        }
635
636
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
637
    }
638
639
    /**
640
     * Initialise le gestionnaire de middleware
641
     */
642
    protected function initMiddlewareQueue(): void
643
    {
644
        $this->middleware = new MiddlewareQueue($this->container, [], $this->request, $this->response);
645
646
        $this->middleware->append($this->spoofRequestMethod());
647
        $this->middleware->register(/** @scrutinizer ignore-type */ config('middlewares'));
648
    }
649
650
    protected function outputBufferingStart(): void
651
    {
652
        $this->bufferLevel = ob_get_level();
653
654
        ob_start();
655
    }
656
657
    protected function outputBufferingEnd(): string
658
    {
659
        $buffer = '';
660
661
        while (ob_get_level() > $this->bufferLevel) {
662
            $buffer .= ob_get_contents();
663
            ob_end_clean();
664
        }
665
666
        return $buffer;
667
    }
668
669
    /**
670
     * Modifie l'objet de requête pour utiliser une méthode différente
671
     * si une variable POST appelée _method est trouvée.
672
     */
673
    private function spoofRequestMethod(): callable
674
    {
675
        return static function (ServerRequestInterface $request, ResponseInterface $response, callable $next) {
676
            $post = $request->getParsedBody();
677
678
            // Ne fonctionne qu'avec les formulaires POST
679
            // Accepte seulement PUT, PATCH, DELETE
680
            if ($request->getMethod() === Method::POST && ! empty($post['_method']) && in_array($post['_method'], [Method::PUT, Method::PATCH, Method::DELETE], true)) {
681
                $request = $request->withMethod($post['_method']);
682
            }
683
684
            return $next($request, $response);
685
        };
686
    }
687
688
    /**
689
     * Démarre l'application en configurant la requete et la réponse,
690
     * en exécutant le contrôleur et en gérant les exceptions de validation.
691
     *
692
     * Cette méthode renvoie un objet callable qui sert de middleware pour le cycle requête-réponse de l'application.
693
     */
694
    private function bootApp(): callable
695
    {
696
        return function (ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface {
697
            Services::set(Request::class, $request);
698
            Services::set(Response::class, $response);
699
700
            try {
701
                $returned = $this->startController();
702
703
                // Les controleur sous forme de Closure sont executes dans startController().
704
                if (! is_callable($this->controller)) {
705
                    $controller = $this->createController($request, $response);
706
707
                    if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
708
                        throw PageNotFoundException::methodNotFound($this->method);
709
                    }
710
711
                    // Y'a t-il un evenement "post_controller_constructor"
712
                    $this->event->emit('post_controller_constructor');
713
714
                    $returned = $this->runController($controller);
715
                } else {
716
                    $this->timer->stop('controller_constructor');
717
                    $this->timer->stop('controller');
718
                }
719
720
                $this->event->emit('post_system');
721
722
                return $this->formatResponse($response, $returned);
723
            } catch (ValidationException $e) {
724
                return $this->formatValidationResponse($e, $request, $response);
725
            }
726
        };
727
    }
728
729
    /**
730
     * Formattage des erreurs de validation
731
     */
732
    private function formatValidationResponse(ValidationException $e, ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
733
    {
734
        $code = $e->getCode();
735
        if (null === $errors = $e->getErrors()) {
736
            $errors = [$e->getMessage()];
737
        }
738
739
        if (in_array($request->getMethod(), [Method::OPTIONS, Method::HEAD], true)) {
740
            throw $e;
741
        }
742
743
        if ($this->isAjaxRequest()) {
744
            return $this->formatResponse($response->withStatus($code), [
745
                'success' => false,
746
                'code'    => $code,
747
                'errors'  => $errors instanceof ErrorBag ? $errors->all() : $errors,
748
            ]);
749
        }
750
751
        return back()->withInput()->withErrors($errors)->withStatus($code);
752
    }
753
754
    /**
755
     * Verifie que la requete est xhr/fetch pour eviter d'afficher la toolbar dans la reponse
756
     */
757
    private function isAjaxRequest(): bool
758
    {
759
        return $this->request->expectsJson()
760
                || $this->request->isJson()
761
                || $this->request->is('ajax')
0 ignored issues
show
Bug introduced by
'ajax' of type string is incompatible with the type BlitzPHP\Http\list expected by parameter $type of BlitzPHP\Http\ServerRequest::is(). ( Ignorable by Annotation )

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

761
                || $this->request->is(/** @scrutinizer ignore-type */ 'ajax')
Loading history...
762
                || $this->request->hasHeader('Hx-Request')
763
                || Text::contains($this->response->getType(), ['/json', '+json'])
764
                || Text::contains($this->response->getType(), ['/xml', '+xml']);
765
    }
766
}
767