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
![]() |
|||||||
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
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
![]() |
|||||||
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
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 The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||||||
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
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
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. ![]() |
|||||||
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
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
![]() |
|||||||
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
$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;
}
![]() |
|||||||
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
|
|||||||
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
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
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
$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
![]() |
|||||||
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
$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;
}
![]() |
|||||||
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
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
![]() |
|||||||
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
|
|||||||
634 | } |
||||||
635 | |||||||
636 | return $response; |
||||||
0 ignored issues
–
show
|
|||||||
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
'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
![]() |
|||||||
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 |