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

Dispatcher::getResponseObject()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 0
dl 0
loc 9
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\Cache\ResponseCache;
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Cache\ResponseCache 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...
15
use BlitzPHP\Container\Services;
16
use BlitzPHP\Contracts\Event\EventManagerInterface;
17
use BlitzPHP\Contracts\Http\ResponsableInterface;
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Contracts\Http\ResponsableInterface 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...
18
use BlitzPHP\Contracts\Router\RouteCollectionInterface;
19
use BlitzPHP\Contracts\Support\Responsable;
20
use BlitzPHP\Controllers\BaseController;
21
use BlitzPHP\Controllers\RestController;
22
use BlitzPHP\Core\App;
23
use BlitzPHP\Debug\Timer;
24
use BlitzPHP\Event\EventDiscover;
25
use BlitzPHP\Exceptions\FrameworkException;
26
use BlitzPHP\Exceptions\PageNotFoundException;
27
use BlitzPHP\Exceptions\RedirectException;
28
use BlitzPHP\Exceptions\ValidationException;
29
use BlitzPHP\Http\Middleware;
30
use BlitzPHP\Http\Response;
31
use BlitzPHP\Http\ServerRequest;
32
use BlitzPHP\Http\Uri;
33
use BlitzPHP\Utilities\Helpers;
34
use BlitzPHP\View\View;
35
use Closure;
36
use InvalidArgumentException;
37
use Psr\Http\Message\ResponseInterface;
38
use Psr\Http\Message\ServerRequestInterface;
39
use stdClass;
40
use Throwable;
41
42
/**
43
 * Cette classe est la porte d'entree du framework. Elle analyse la requete,
44
 * recherche la route correspondante et invoque le bon controleurm puis renvoie la reponse.
45
 */
46
class Dispatcher
47
{
48
    /**
49
     * Heure de démarrage de l'application.
50
     *
51
     * @var mixed
52
     */
53
    protected $startTime;
54
55
    /**
56
     * Durée totale d'exécution de l'application
57
     *
58
     * @var float
59
     */
60
    protected $totalTime;
61
62
    /**
63
     * Main application configuration
64
     *
65
     * @var stdClass
66
     */
67
    protected $config;
68
69
    /**
70
     * instance Timer.
71
     *
72
     * @var Timer
73
     */
74
    protected $timer;
75
76
    /**
77
     * requête courrante.
78
     *
79
     * @var ServerRequest
80
     */
81
    protected $request;
82
83
    /**
84
     * Reponse courrante.
85
     *
86
     * @var Response
87
     */
88
    protected $response;
89
90
    /**
91
     * Router à utiliser.
92
     *
93
     * @var Router
94
     */
95
    protected $router;
96
97
    /**
98
     * @var Middleware
99
     */
100
    private $middleware;
101
102
    /**
103
     * Contrôleur à utiliser.
104
     *
105
     * @var Closure|string
106
     */
107
    protected $controller;
108
109
    /**
110
     * Méthode du ontrôleur à exécuter.
111
     *
112
     * @var string
113
     */
114
    protected $method;
115
116
    /**
117
     * Gestionnaire de sortie à utiliser.
118
     *
119
     * @var string
120
     */
121
    protected $output;
122
123
    /**
124
     * Chemin de requête à utiliser.
125
     *
126
     * @var string
127
	 * 
128
	 * @deprecated No longer used.
129
     */
130
    protected $path;
131
132
    /**
133
     * Application output buffering level
134
     */
135
    protected int $bufferLevel = 0;
136
137
	/**
138
     * Web Page Caching
139
     */
140
    protected ResponseCache $pageCache;
141
142
    /**
143
     * Constructor.
144
     */
145
    public function __construct(protected EventManagerInterface $event)
146
    {
147
        $this->startTime = microtime(true);
148
        $this->config    = (object) config('app');
0 ignored issues
show
Documentation Bug introduced by
It seems like (object)config('app') of type BlitzPHP\Config\Config is incompatible with the declared type stdClass of property $config.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
149
150
		$this->pageCache = Services::factory(ResponseCache::class, [
151
			'cacheQueryString' => config('cache.cache_query_string')
152
		]);
153
    }
154
155
    /**
156
     * Retourne la methode invoquee
157
     */
158
    public static function getMethod(): ?string
159
    {
160
        $method = Services::singleton(self::class)->method;
161
        if (empty($method)) {
162
            $method = Services::routes()->getDefaultMethod();
163
        }
164
165
        return $method;
166
    }
167
168
    /**
169
     * Retourne le contrôleur utilisé
170
     *
171
     * @return Closure|string
172
     */
173
    public static function getController(bool $fullName = true)
174
    {
175
        $routes = Services::routes();
176
177
        $controller = Services::singleton(self::class)->controller;
178
        if (empty($controller)) {
179
            $controller = $routes->getDefaultController();
180
        }
181
182
		if (! $fullName && is_string($controller)) {
183
            $controller = str_replace($routes->getDefaultNamespace(), '', $controller);
184
        }
185
186
        return $controller;
187
    }
188
189
    /**
190
     * Lancez l'application !
191
     *
192
     * C'est "la boucle" si vous voulez. Le principal point d'entrée dans le script
193
     * qui obtient les instances de classe requises, déclenche les filtres,
194
     * essaie d'acheminer la réponse, charge le contrôleur et généralement
195
     * fait fonctionner toutes les pièces ensemble.
196
     *
197
     * @return bool|mixed|ResponseInterface|ServerRequestInterface
198
     *
199
     * @throws Exception
200
     * @throws RedirectException
201
     */
202
    public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
203
    {
204
        $this->pageCache->setTtl(0);
205
        $this->bufferLevel = ob_get_level();
206
207
        $this->startBenchmark();
208
209
        $this->getRequestObject();
210
        $this->getResponseObject();
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 BlitzPHP\Config\Config; 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|RedirectException $e) {
217
            $this->outputBufferingEnd();
218
            if ($e instanceof RedirectException) {
219
                $e = new RedirectException($e->getMessage(), $e->getCode(), $e);
220
            }
221
222
            $this->response = $e->getResponse();
223
        } catch (PageNotFoundException $e) {
224
            $this->response = $this->display404errors($e);
225
        } catch (Throwable $e) {
226
            $this->outputBufferingEnd();
227
228
            throw $e;
229
        }
230
231
        if ($returnResponse) {
232
            return $this->response;
233
        }
234
235
        $this->sendResponse();
236
    }
237
238
    /**
239
     * Gère la logique de requête principale et déclenche le contrôleur.
240
     *
241
     * @throws PageNotFoundException
242
     * @throws RedirectException
243
     */
244
    protected function handleRequest(?RouteCollectionInterface $routes = null, ?array $cacheConfig = null): ResponseInterface
245
    {
246
		$this->forceSecureAccess();
247
248
		/**
249
         * Init event manager
250
         */
251
		Services::singleton(EventDiscover::class)->discove();
252
253
		$this->event->trigger('pre_system');
254
255
		// Check for a cached page. 
256
		// Execution will stop if the page has been cached.
257
        if (($response = $this->displayCache($cacheConfig)) instanceof ResponseInterface) {
258
            return $response;
259
        }
260
261
        $routeMiddlewares = (array) $this->dispatchRoutes($routes);
262
263
        // Le bootstrap dans un middleware
264
		$this->middleware->alias('blitz', $this->bootApp());
265
        $this->middleware->append('blitz');
266
267
        /**
268
         * Ajouter des middlewares de routes
269
         */
270
        foreach ($routeMiddlewares as $middleware) {
271
            $this->middleware->prepend($middleware);
272
        }
273
274
        // Enregistrer notre URI actuel en tant qu'URI précédent dans la session
275
        // pour une utilisation plus sûre et plus précise avec la fonction d'assistance `previous_url()`.
276
        $this->storePreviousURL(current_url(true));
277
278
        /**
279
         * Emission de la reponse
280
         */
281
        $this->gatherOutput($this->middleware->handle($this->request));
282
283
        // Y a-t-il un événement post-système ?
284
        $this->event->trigger('post_system');
285
286
        return $this->response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->response returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
287
    }
288
289
    /**
290
     * Démarrer le benchmark
291
     *
292
     * La minuterie est utilisée pour afficher l'exécution totale du script à la fois dans la
293
     * barre d'outils de débogage, et éventuellement sur la page affichée.
294
     */
295
    protected function startBenchmark()
296
    {
297
        if ($this->startTime === null) {
298
            $this->startTime = microtime(true);
299
        }
300
301
        $this->timer = Services::timer();
302
        $this->timer->start('total_execution', $this->startTime);
0 ignored issues
show
Bug introduced by
It seems like $this->startTime can also be of type string; however, parameter $time of BlitzPHP\Debug\Timer::start() does only seem to accept double|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

302
        $this->timer->start('total_execution', /** @scrutinizer ignore-type */ $this->startTime);
Loading history...
303
        $this->timer->start('bootstrap');
304
    }
305
306
    /**
307
     * Définit un objet Request à utiliser pour cette requête.
308
     * Utilisé lors de l'exécution de certains tests.
309
     */
310
    public function setRequest(ServerRequestInterface $request): self
311
    {
312
        $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\ServerRequest. 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...
313
314
        return $this;
315
    }
316
317
    /**
318
     * Obtenez notre objet Request et définissez le protocole du serveur en fonction des informations fournies
319
     * par le serveur.
320
     */
321
    protected function getRequestObject()
322
    {
323
        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...
324
            return;
325
        }
326
327
        if (is_cli() && ! on_test()) {
328
            // @codeCoverageIgnoreStart
329
            // $this->request = Services::clirequest($this->config);
330
            // @codeCoverageIgnoreEnd
331
        }
332
333
        $version = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
334
        if (! is_numeric($version)) {
335
            $version = substr($version, strpos($version, '/') + 1);
336
        }
337
338
        // Assurez-vous que la version est au bon format
339
        $version = number_format((float) $version, 1);
340
341
        $this->request = Services::request()->withProtocolVersion($version);
342
    }
343
344
    /**
345
     * Obtenez notre objet Response et définissez des valeurs par défaut, notamment
346
     * la version du protocole HTTP et une réponse réussie par défaut.
347
     */
348
    protected function getResponseObject()
349
    {
350
        // Supposons le succès jusqu'à preuve du contraire.
351
        $this->response = Services::response()->withStatus(200);
352
353
        if (! is_cli() || on_test()) {
354
        }
355
356
        $this->response = $this->response->withProtocolVersion($this->request->getProtocolVersion());
357
    }
358
359
    /**
360
     * Forcer l'accès au site sécurisé ? Si la valeur de configuration 'forceGlobalSecureRequests'
361
     * est vrai, imposera que toutes les demandes adressées à ce site soient effectuées via
362
     * HTTPS. Redirigera également l'utilisateur vers la page actuelle avec HTTPS
363
     * comme défini l'en-tête HTTP Strict Transport Security pour ces navigateurs
364
     * qui le supportent.
365
     *
366
     * @param int $duration Combien de temps la sécurité stricte des transports
367
     *                      doit être appliqué pour cette URL.
368
     */
369
    protected function forceSecureAccess($duration = 31536000)
370
    {
371
        if ($this->config->force_global_secure_requests !== true) {
372
            return;
373
        }
374
375
        force_https($duration, $this->request, $this->response);
376
    }
377
378
    /**
379
     * Détermine si une réponse a été mise en cache pour l'URI donné.
380
     *
381
     * @return bool|ResponseInterface
382
     *
383
     * @throws FrameworkException
384
     */
385
    public function displayCache(?array $config = null)
386
    {
387
		if ($cachedResponse = $this->pageCache->get($this->request, $this->response)) {
388
            $this->response = $cachedResponse;
389
390
            $this->totalTime = $this->timer->getElapsedTime('total_execution');
391
            $output          = $this->displayPerformanceMetrics($cachedResponse->getBody());
392
        
393
			return $this->response->withBody(to_stream($output));
394
        }
395
396
        return false;
397
    }
398
399
    /**
400
     * Renvoie un tableau avec nos statistiques de performances de base collectées.
401
     */
402
    public function getPerformanceStats(): array
403
    {
404
        return [
405
            'startTime' => $this->startTime,
406
            'totalTime' => $this->totalTime,
407
        ];
408
    }
409
410
    /**
411
     * Remplace les balises memory_usage et elapsed_time.
412
     */
413
    public function displayPerformanceMetrics(string $output): string
414
    {
415
        $this->totalTime = $this->timer->getElapsedTime('total_execution');
416
417
        return str_replace('{elapsed_time}', (string) $this->totalTime, $output);
418
    }
419
420
    /**
421
     * Fonctionne avec le routeur pour
422
     * faire correspondre une route à l'URI actuel. Si la route est une
423
     * "route de redirection", gérera également la redirection.
424
     *
425
     * @param RouteCollectionInterface|null $routes Une interface de collecte à utiliser à la place
426
     *                                              du fichier de configuration.
427
     *
428
     * @return string[]
429
     *
430
     * @throws RedirectException
431
     */
432
    protected function dispatchRoutes(?RouteCollectionInterface $routes = null): array
433
    {
434
        if ($routes === null) {
435
            $routes = Services::routes()->loadRoutes();
436
        }
437
438
        $this->router = Services::router($routes, $this->request, false);
439
440
        $path = $this->determinePath();
441
442
        $this->timer->stop('bootstrap');
443
        $this->timer->start('routing');
444
445
        $this->outputBufferingStart();
446
447
        $this->controller = $this->router->handle($path ?: '/');
448
        $this->method     = $this->router->methodName();
449
450
        // Si un segment {locale} correspondait dans la route finale,
451
        // alors nous devons définir les paramètres régionaux corrects sur notre requête.
452
        if ($this->router->hasLocale()) {
453
            $this->request = $this->request->withLocale($this->router->getLocale());
454
        }
455
456
        $this->timer->stop('routing');
457
458
        return $this->router->getMiddlewares();
459
    }
460
461
    /**
462
     * Détermine le chemin à utiliser pour que nous essayions d'acheminer vers, en fonction
463
     * de l'entrée de l'utilisateur (setPath), ou le chemin CLI/IncomingRequest.
464
     */
465
    protected function determinePath(): string
466
    {
467
        if (! empty($this->path)) {
0 ignored issues
show
Deprecated Code introduced by
The property BlitzPHP\Router\Dispatcher::$path has been deprecated: No longer used. ( Ignorable by Annotation )

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

467
        if (! empty(/** @scrutinizer ignore-deprecated */ $this->path)) {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
468
            return $this->path;
0 ignored issues
show
Deprecated Code introduced by
The property BlitzPHP\Router\Dispatcher::$path has been deprecated: No longer used. ( Ignorable by Annotation )

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

468
            return /** @scrutinizer ignore-deprecated */ $this->path;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
469
        }
470
471
        $path = method_exists($this->request, 'getPath')
472
            ? $this->request->getPath()
473
            : $this->request->getUri()->getPath();
474
475
        return $this->path = preg_replace('#^' . App::getUri()->getPath() . '#i', '', $path);
0 ignored issues
show
Deprecated Code introduced by
The property BlitzPHP\Router\Dispatcher::$path has been deprecated: No longer used. ( Ignorable by Annotation )

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

475
        return /** @scrutinizer ignore-deprecated */ $this->path = preg_replace('#^' . App::getUri()->getPath() . '#i', '', $path);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
476
    }
477
478
    /**
479
     * Maintenant que tout a été configuré, cette méthode tente d'exécuter le
480
     * méthode du contrôleur et lancez le script. S'il n'en est pas capable, le fera
481
     * afficher l'erreur Page introuvable appropriée.
482
     */
483
    protected function startController(ServerRequest $request, Response $response)
484
    {
485
        // Aucun contrôleur spécifié - nous ne savons pas quoi faire maintenant.
486
        if (empty($this->controller)) {
487
            throw PageNotFoundException::emptyController();
488
        }
489
490
        $this->timer->start('controller');
491
        $this->timer->start('controller_constructor');
492
493
        // Est-il acheminé vers une Closure ?
494
        if (is_object($this->controller) && (get_class($this->controller) === 'Closure')) {
495
            $controller = $this->controller;
496
497
            $sendParameters = [];
498
499
            foreach ($this->router->params() as $parameter) {
500
                $sendParameters[] = $parameter;
501
            }
502
            array_push($sendParameters, $request, $response);
503
504
            return Services::injector()->call($controller, $sendParameters);
505
        }
506
507
        // Essayez de charger automatiquement la classe
508
        if (! class_exists($this->controller, true) || ($this->method[0] === '_' && $this->method !== '__invoke')) {
0 ignored issues
show
Bug introduced by
It seems like $this->controller can also be of type Closure; however, parameter $class of class_exists() 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

508
        if (! class_exists(/** @scrutinizer ignore-type */ $this->controller, true) || ($this->method[0] === '_' && $this->method !== '__invoke')) {
Loading history...
509
            throw PageNotFoundException::controllerNotFound($this->controller, $this->method);
0 ignored issues
show
Bug introduced by
It seems like $this->controller can also be of type Closure; however, parameter $controller of BlitzPHP\Exceptions\Page...n::controllerNotFound() 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

509
            throw PageNotFoundException::controllerNotFound(/** @scrutinizer ignore-type */ $this->controller, $this->method);
Loading history...
510
        }
511
512
        return null;
513
    }
514
515
    /**
516
     * Instancie la classe contrôleur.
517
     *
518
     * @return \BlitzPHP\Controllers\BaseController|mixed
519
     */
520
    private function createController(ServerRequestInterface $request, ResponseInterface $response)
521
    {
522
        /**
523
         * @var \BlitzPHP\Controllers\BaseController
524
         */
525
        $class = Services::injector()->get($this->controller);
0 ignored issues
show
Bug introduced by
It seems like $this->controller can also be of type Closure; however, parameter $name of BlitzPHP\Container\Injector::get() 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

525
        $class = Services::injector()->get(/** @scrutinizer ignore-type */ $this->controller);
Loading history...
526
527
        if (method_exists($class, 'initialize')) {
528
            $class->initialize($request, $response, Services::logger());
529
        }
530
531
        $this->timer->stop('controller_constructor');
532
533
        return $class;
534
    }
535
536
    /**
537
     * Exécute le contrôleur, permettant aux méthodes _remap de fonctionner.
538
     *
539
     * @param mixed $class
540
     *
541
     * @return mixed
542
     */
543
    protected function runController($class)
544
    {
545
        // S'il s'agit d'une demande de console, utilisez les segments d'entrée comme paramètres
546
        $params = defined('KLINGED') ? $this->request->getSegments() : $this->router->params();
0 ignored issues
show
Bug introduced by
The method getSegments() does not exist on BlitzPHP\Http\ServerRequest. 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

546
        $params = defined('KLINGED') ? $this->request->/** @scrutinizer ignore-call */ getSegments() : $this->router->params();
Loading history...
547
        $method = $this->method;
548
549
        if (method_exists($class, '_remap')) {
550
            $params = [$method, $params];
551
            $method = '_remap';
552
        }
553
554
        $output = Services::injector()->call([$class, $method], (array) $params);
555
556
        $this->timer->stop('controller');
557
558
        if ($output instanceof View) {
559
            $output = $this->response->withBody(to_stream($output->get()));
560
        }
561
562
        return $output;
563
    }
564
565
    /**
566
     * Affiche une page d'erreur 404 introuvable. S'il est défini, essaiera de
567
     * appelez le contrôleur/méthode 404Override qui a été défini dans la configuration de routage.
568
     */
569
    protected function display404errors(PageNotFoundException $e)
570
    {
571
        // Existe-t-il une dérogation 404 disponible ?
572
        if ($override = $this->router->get404Override()) {
573
            $returned = null;
574
575
            if ($override instanceof Closure) {
0 ignored issues
show
introduced by
$override is never a sub-type of Closure.
Loading history...
576
                echo $override($e->getMessage());
577
            } elseif (is_array($override)) {
0 ignored issues
show
introduced by
The condition is_array($override) is always true.
Loading history...
578
                $this->timer->start('controller');
579
                $this->timer->start('controller_constructor');
580
581
                $this->controller = $override[0];
582
                $this->method     = $override[1];
583
584
                $controller = $this->createController($this->request, $this->response);
585
                $returned   = $this->runController($controller);
586
            }
587
588
            unset($override);
589
590
            $this->gatherOutput($returned);
591
           
592
			return $this->response;
593
        }
594
595
        // Affiche l'erreur 404
596
        $this->response = $this->response->withStatus($e->getCode());
597
598
        echo $this->outputBufferingEnd();
599
        flush();
600
601
        throw PageNotFoundException::pageNotFound(! on_prod() || is_cli() ? $e->getMessage() : '');
602
    }
603
604
    /**
605
     * Rassemble la sortie du script à partir du tampon, remplace certaines balises d'exécutions
606
     * d'horodatage dans la sortie et affiche la barre d'outils de débogage, si nécessaire.
607
     *
608
     * @param mixed|null $returned
609
     */
610
    protected function gatherOutput($returned = null)
611
    {
612
        $this->output = $this->outputBufferingEnd();
613
614
        // Si le contrôleur a renvoyé un objet de réponse,
615
        // nous devons en saisir le corps pour qu'il puisse
616
        // être ajouté à tout ce qui aurait déjà pu être ajouté avant de faire le écho.
617
        // Nous devons également enregistrer l'instance localement
618
        // afin que tout changement de code d'état, etc., ait lieu.
619
        if ($returned instanceof ResponseInterface) {
620
            $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...
621
            $returned       = $returned->getBody()->getContents();
622
        }
623
624
        if (is_string($returned)) {
625
            $this->output .= $returned;
626
        }
627
628
        $this->response = $this->response->withBody(to_stream($this->output));
629
    }
630
631
    /**
632
     * Si nous avons un objet de session à utiliser, stockez l'URI actuel
633
     * comme l'URI précédent. Ceci est appelé juste avant d'envoyer la
634
     * réponse au client, et le rendra disponible à la prochaine demande.
635
     *
636
     * Cela permet au fournisseur une détection plus sûre et plus fiable de la fonction previous_url().
637
     *
638
     * @param \BlitzPHP\Http\URI|string $uri
639
     */
640
    public function storePreviousURL($uri)
641
    {
642
        // Ignorer les requêtes CLI
643
        if (is_cli() && ! on_test()) {
644
            return; // @codeCoverageIgnore
645
        }
646
647
        // Ignorer les requêtes AJAX
648
        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\ServerRequest. 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

648
        if (method_exists($this->request, 'isAJAX') && $this->request->/** @scrutinizer ignore-call */ isAJAX()) {
Loading history...
649
            return;
650
        }
651
652
        // Ignorer les reponses non-HTML
653
        if (strpos($this->response->getHeaderLine('Content-Type'), 'text/html') === false) {
654
            return;
655
        }
656
657
        // Ceci est principalement nécessaire lors des tests ...
658
        if (is_string($uri)) {
659
            $uri = Services::uri($uri, false);
660
        }
661
662
		Services::session()->setPreviousUrl(Uri::createURIString(
663
			$uri->getScheme(),
664
			$uri->getAuthority(),
665
			$uri->getPath(),
666
			$uri->getQuery(),
667
			$uri->getFragment()
668
		));
669
    }
670
671
    /**
672
     * Renvoie la sortie de cette requête au client.
673
     * C'est ce qu'il attendait !
674
     */
675
    protected function sendResponse()
676
    {
677
        $this->totalTime = $this->timer->getElapsedTime('total_execution');
678
        Services::emitter()->emit(
679
            Services::toolbar()->prepare($this->getPerformanceStats(), $this->request, $this->response)
680
        );
681
    }
682
683
    protected function emitResponse()
684
    {
685
        $this->gatherOutput();
686
        $this->sendResponse();
687
    }
688
689
    /**
690
     * Construit une reponse adequate en fonction du retour du controleur
691
     *
692
     * @param mixed $returned
693
     */
694
    protected function formatResponse(ResponseInterface $response, $returned): ResponseInterface
695
    {
696
        if ($returned instanceof ResponseInterface) {
697
            return $returned;
698
        }
699
700
		if ($returned instanceof Responsable) {
701
			return $returned->toResponse($this->request);
702
		}
703
704
        if (is_object($returned)) {
705
            if (method_exists($returned, '__toString')) {
706
                $returned = $returned->__toString();
707
            } elseif (method_exists($returned, 'toArray')) {
708
                $returned = $returned->toArray();
709
            } elseif (method_exists($returned, 'toJSON')) {
710
                $returned = $returned->toJSON();
711
            } else {
712
                $returned = (array) $returned;
713
            }
714
        }
715
716
        if (is_array($returned)) {
717
            $returned = Helpers::collect($returned);
718
            $response = $response->withHeader('Content-Type', 'application/json');
719
        }
720
721
        try {
722
            $response = $response->withBody(to_stream($returned));
723
        } catch (InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
724
        }
725
726
        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...
727
    }
728
729
    /**
730
     * Initialise le gestionnaire de middleware
731
     */
732
    protected function initMiddlewareQueue(): void
733
    {
734
        $this->middleware = Services::injector()->make(Middleware::class, [
735
            'response' => $this->response,
736
            'path'     => $this->determinePath(),
737
        ]);
738
739
        $middlewaresFile = CONFIG_PATH . 'middlewares.php';
740
        if (file_exists($middlewaresFile) && ! in_array($middlewaresFile, get_included_files(), true)) {
741
            $middleware = require $middlewaresFile;
742
            if (is_callable($middleware)) {
743
                $middleware($this->middleware, $this->request);
744
            }
745
        }
746
747
        $this->middleware->prepend($this->spoofRequestMethod());
748
    }
749
750
	protected function outputBufferingStart(): void
751
    {
752
        $this->bufferLevel = ob_get_level();
753
       
754
		ob_start();
755
    }
756
757
    protected function outputBufferingEnd(): string
758
    {
759
        $buffer = '';
760
761
        while (ob_get_level() > $this->bufferLevel) {
762
            $buffer .= ob_get_contents();
763
            ob_end_clean();
764
        }
765
766
        return $buffer;
767
    }
768
769
    /**
770
     * Modifie l'objet de requête pour utiliser une méthode différente
771
     * si une variable POST appelée _method est trouvée.
772
     */
773
    private function spoofRequestMethod(): callable
774
    {
775
        return static function (ServerRequestInterface $request, ResponseInterface $response, callable $next) {
776
            $post = $request->getParsedBody();
777
778
            // Ne fonctionne qu'avec les formulaires POST
779
            if (strtoupper($request->getMethod()) === 'POST' && ! empty($post['_method'])) {
780
                // Accepte seulement PUT, PATCH, DELETE
781
                if (in_array(strtoupper($post['_method']), ['PUT', 'PATCH', 'DELETE'], true)) {
782
                    $request = $request->withMethod($post['_method']);
783
                }
784
            }
785
786
            return $next($request, $response);
787
        };
788
    }
789
790
    private function bootApp(): callable
791
    {
792
        return function (ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface {
793
            try {
794
                $returned = $this->startController($request, $response);
795
796
                // Closure controller has run in startController().
797
                if (! is_callable($this->controller)) {
798
                    $controller = $this->createController($request, $response);
799
800
                    if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
801
                        throw PageNotFoundException::methodNotFound($this->method);
802
                    }
803
804
                    // Y'a t-il un evenement "post_controller_constructor"
805
                    $this->event->trigger('post_controller_constructor');
806
807
                    $returned = $this->runController($controller);
808
                } else {
809
                    $this->timer->stop('controller_constructor');
810
                    $this->timer->stop('controller');
811
                }
812
813
                $this->event->trigger('post_system');
814
815
                return $this->formatResponse($response, $returned);
816
            } catch (ValidationException $e) {
817
                $code   = $e->getCode();
818
                $errors = $e->getErrors();
819
                if (empty($errors)) {
820
                    $errors = [$e->getMessage()];
821
                }
822
823
                if (is_string($this->controller)) {
824
					if (strtoupper($request->getMethod()) === 'POST') {
825
                        if (is_subclass_of($this->controller, RestController::class)) {
826
                            return $this->formatResponse($response->withStatus($code), [
827
                                'success' => false,
828
                                'code'    => $code,
829
                                'errors'  => $errors,
830
                            ]);
831
                        }
832
						if (is_subclass_of($this->controller, BaseController::class)) {
833
                            return Services::redirection()->back()->withInput()->withErrors($errors)->withStatus($code);
834
                        }
835
                    }
836
                } elseif (strtoupper($request->getMethod()) === 'POST') {
837
                    return Services::redirection()->back()->withInput()->withErrors($errors)->withStatus($code);
838
                }
839
840
                throw $e;
841
            }
842
        };
843
    }
844
}
845