blitz-php /
framework
| 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
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
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
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
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. 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
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. 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
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
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
$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
|
|||||||
| 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 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
$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
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
$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
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
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
|
|||||||
| 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
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 |