From 6e3a783e8f1d8e0ca8364944682f412cf1ad286e Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 12 Mar 2025 17:37:11 +0100 Subject: [PATCH 1/2] chore: refactorisation du gestionnaire d'exception - seul whoops est pris en charge - suppression de la prise en charge de spatie/ignition - meilleure organisation du gestionnaire d'exceptions --- src/Core/Application.php | 4 +- src/Debug/Debugger.php | 64 ----------- src/Debug/ExceptionManager.php | 198 +++++++++++++++++++++------------ 3 files changed, 128 insertions(+), 138 deletions(-) delete mode 100644 src/Debug/Debugger.php diff --git a/src/Core/Application.php b/src/Core/Application.php index 09f1af44..6d0f1cb9 100644 --- a/src/Core/Application.php +++ b/src/Core/Application.php @@ -12,7 +12,7 @@ namespace BlitzPHP\Core; use BlitzPHP\Container\Services; -use BlitzPHP\Debug\Debugger; +use BlitzPHP\Debug\ExceptionManager; use BlitzPHP\Event\EventDiscover; use BlitzPHP\Exceptions\ExceptionInterface; use BlitzPHP\Router\Dispatcher; @@ -67,7 +67,7 @@ public function init(): self /** * Lance la capture des exceptions et erreurs */ - Debugger::init(); + service(ExceptionManager::class)->register(); /** * Initialisation du gestionnaire d'evenement diff --git a/src/Debug/Debugger.php b/src/Debug/Debugger.php deleted file mode 100644 index 4b937f1a..00000000 --- a/src/Debug/Debugger.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace BlitzPHP\Debug; - -use Spatie\Ignition\Ignition; -use Whoops\Run; - -/** - * Capture et affiche les erreurs et exceptions via whoops - * - * Necessite l'instalation de `flip/whoops` ou `spatie/ignition` - */ -class Debugger -{ - /** - * Demarre le processus - */ - public static function init(): void - { - $config = config('exceptions'); - - if (class_exists(Ignition::class)) { - self::initIgnition($config); - } elseif (class_exists(Run::class)) { - self::initWhoops($config); - } - } - - /** - * Initialisation du debugger a travers filp/Whoops - */ - private static function initWhoops(array $config): void - { - $debugger = new Run(); - - $debugger = ExceptionManager::registerWhoopsHandler($debugger, $config); - $debugger = ExceptionManager::registerHttpErrors($debugger, $config); - $debugger = ExceptionManager::registerAppHandlers($debugger, $config); - - $debugger->register(); - } - - /** - * Initialisation du debugger a travers spatie/ignition - * - * @todo customisation du debugger et log des erreurs - */ - private static function initIgnition(array $config): void - { - $debugger = Ignition::make(); - $debugger->applicationPath(ROOTPATH) - ->shouldDisplayException(! on_prod()) - ->register(); - } -} diff --git a/src/Debug/ExceptionManager.php b/src/Debug/ExceptionManager.php index 0f2be488..4b9d74b0 100644 --- a/src/Debug/ExceptionManager.php +++ b/src/Debug/ExceptionManager.php @@ -27,46 +27,102 @@ use Whoops\Util\Misc; /** - * Gestionnaire d'exceptions + * Capture et affiche les erreurs et exceptions via whoops + * + * Necessite l'instalation de `flip/whoops` */ class ExceptionManager { /** - * Gestionnaire d'exceptions de type http (404, 500) qui peuvent avoir une page d'erreur personnalisée. + * Gestionnaire d'exception (instance Whoops) + */ + private Run $debugger; + + /** + * Configuration du gestionnaire d'exception + */ + private object $config; + + public function __construct() + { + if (class_exists(Run::class)) { + $this->debugger = new Run(); + $this->config = (object) config('exceptions'); + } + } + + /** + * Demarre le processus */ - public static function registerHttpErrors(Run $debugger, array $config): Run + public function register(): void { - return $debugger->pushHandler(static function (Throwable $exception, InspectorInterface $inspector, RunInterface $run) use ($config): int { - $exception = self::prepareException($exception); + if (! $this->debugger) { + return; + } + $this->registerWhoopsHandler() + ->registerHttpErrorsHandler() + ->registerAppHandlers(); + + $this->debugger->register(); + } + + /** + * Enregistre les gestionnaires d'exception spécifiques à l'application. + * + * Cette méthode parcourt les gestionnaires configurés et les ajoute au débogueur. + * Elle prend en charge à la fois les gestionnaires callable et les noms de classe sous forme de chaîne qui peuvent être instanciés. + */ + private function registerAppHandlers(): self + { + foreach ($this->config->handlers as $handler) { + if (is_callable($handler)) { + $this->debugger->pushHandler($handler); + } elseif (is_string($handler) && class_exists($handler)) { + $class = service('container')->make($handler); + if (is_callable($class) || $class instanceof HandlerInterface) { + $this->debugger->pushHandler($class); + } + } + } + + return $this; + } + + /** + * Enregistre un gestionnaire pour les erreurs HTTP. + * + * Cette méthode met en place un gestionnaire d'erreurs personnalisé qui traite les exceptions, + * les consigne si elle est configurée, et tente d'afficher les vues d'erreur appropriées. + * Elle gère les codes d'état HTTP, la journalisation et les vues d'erreur personnalisées. + */ + private function registerHttpErrorsHandler(): self + { + $this->debugger->pushHandler(function (Throwable $exception, InspectorInterface $inspector, RunInterface $run): int { + $exception = $this->prepareException($exception); $exception_code = $exception->getCode(); + if ($exception_code >= 400 && $exception_code < 600) { $run->sendHttpCode($exception_code); } - if (true === $config['log'] && ! in_array($exception->getCode(), $config['ignore_codes'], true)) { + if (true === $this->config->log && ! in_array($exception_code, $this->config->ignore_codes, true)) { service('logger')->error($exception); } - if (is_dir($config['error_view_path'])) { - $files = array_map(static fn (SplFileInfo $file) => $file->getFilenameWithoutExtension(), service('fs')->files($config['error_view_path'])); + if (is_dir($this->config->error_view_path)) { + $files = array_map(static fn (SplFileInfo $file) => $file->getFilenameWithoutExtension(), service('fs')->files($this->config->error_view_path)); } else { $files = []; } - if (in_array((string) $exception->getCode(), $files, true)) { - $view = new View(); - $view->setAdapter(config('view.active_adapter', 'native'), ['view_path' => $config['error_view_path']]) - ->display((string) $exception->getCode()) - ->setData(['message' => $exception->getMessage()]) - ->render(); + $files = collect($files)->flip()->only($exception_code, is_online() ? 'production' : '')->flip()->all(); - return Handler::QUIT; - } - if (in_array('production', $files, true) && is_online()) { + if ($files !== []) { $view = new View(); - $view->setAdapter(config('view.active_adapter', 'native'), ['view_path_locator' => $config['error_view_path']]) - ->display('production') + + $view->setAdapter(config('view.active_adapter', 'native'), ['view_path' => $this->config->error_view_path]) + ->first($files, ['message' => $exception->getMessage()]) ->render(); return Handler::QUIT; @@ -74,70 +130,46 @@ public static function registerHttpErrors(Run $debugger, array $config): Run return Handler::DONE; }); - } - - /** - * Gestionnaire d'applications fournis par le developpeur. - */ - public static function registerAppHandlers(Run $debugger, array $config): Run - { - foreach ($config['handlers'] ?? [] as $handler) { - if (is_callable($handler)) { - $debugger->pushHandler($handler); - } elseif (is_string($handler) && class_exists($handler)) { - $class = service('container')->make($handler); - if (is_callable($class) || $class instanceof HandlerInterface) { - $debugger->pushHandler($class); - } - } - } - return $debugger; + return $this; } /** - * Gestionnaire d'erreurs globales whoops + * Enregistre un gestionnaire de Whoops à des fins de débogage. + * + * Cette méthode met en place différents gestionnaires en fonction de l'environnement et des paramètres de configuration. + * Elle vérifie la ligne de commande, l'état en ligne, les requêtes AJAX et les requêtes JSON. + * En fonction des conditions, elle utilise PlainTextHandler, JsonResponseHandler ou PrettyPageHandler. + * + * Le PrettyPageHandler est configuré avec les paramètres de l'éditeur, le titre de la page, les chemins d'accès à l'application, + * les données sur liste noire et les tables de données. Il gère également différents types de données pour les tables de données. */ - public static function registerWhoopsHandler(Run $debugger, array $config): Run + private function registerWhoopsHandler(): self { if (Misc::isCommandLine()) { - $debugger->pushHandler(new PlainTextHandler()); - } - - if (! is_online()) { - if (Misc::isAjaxRequest() || service('request')->isJson()) { - $debugger->pushHandler(new JsonResponseHandler()); - } else { - $handler = new PrettyPageHandler(); + $this->debugger->pushHandler(new PlainTextHandler(service('logger'))); - $handler->setEditor($config['editor'] ?: PrettyPageHandler::EDITOR_VSCODE); - $handler->setPageTitle($config['title'] ?: $handler->getPageTitle()); - $handler->setApplicationRootPath(APP_PATH); - $handler->setApplicationPaths([APP_PATH, SYST_PATH, VENDOR_PATH]); + return $this; + } - $handler = self::setBlacklist($handler, $config['blacklist']); + if (is_online()) { + return $this; + } - foreach ($config['data'] as $label => $data) { - if (is_array($data)) { - $handler->addDataTable($label, $data); - } elseif (is_callable($data)) { - $handler->addDataTableCallback($label, $data); - } - } + if (Misc::isAjaxRequest() || service('request')->isJson()) { + $this->debugger->pushHandler(new JsonResponseHandler()); - $debugger->pushHandler($handler); - } + return $this; } - return $debugger; - } + $handler = new PrettyPageHandler(); - /** - * Enregistre les elements blacklisté dans l'affichage du rapport d'erreur - */ - private static function setBlacklist(PrettyPageHandler $handler, array $blacklists): PrettyPageHandler - { - foreach ($blacklists as $blacklist) { + $handler->handleUnconditionally(true); + $handler->setEditor($this->config->editor ?: PrettyPageHandler::EDITOR_VSCODE); + $handler->setPageTitle($this->config->title ?: $handler->getPageTitle()); + $handler->setApplicationPaths($this->getApplicationPaths()); + + foreach ($this->config->blacklist as $blacklist) { [$name, $key] = explode('/', $blacklist) + [1 => '*']; if ($name[0] !== '_') { @@ -168,18 +200,40 @@ private static function setBlacklist(PrettyPageHandler $handler, array $blacklis } } - return $handler; + foreach ($this->config->data as $label => $data) { + if (is_array($data)) { + $handler->addDataTable($label, $data); + } elseif (is_callable($data)) { + $handler->addDataTableCallback($label, $data); + } + } + + $this->debugger->pushHandler($handler); + + return $this; } /** - * Prepare exception for rendering. + * Préparer l'exception pour le rendu. */ private static function prepareException(Throwable $e): Throwable { if ($e instanceof TokenMismatchException) { - $e = new HttpException($e->getMessage(), 419, $e); + return new HttpException($e->getMessage(), 419, $e); } return $e; } + + /** + * Récupère les chemins d'accès à l'application. + */ + private function getApplicationPaths(): array + { + return collect(service('fs')->directories(base_path())) + ->flip() + ->except(base_path('vendor')) + ->flip() + ->all(); + } } From 2c70bf6a2549e68e4b4b1e90f97d61806e33ef4f Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Wed, 12 Mar 2025 18:02:57 +0100 Subject: [PATCH 2/2] fix phpstan --- phpstan-baseline.php | 6 ------ spec/system/framework/Cli/Commands/ClearLog.spec.php | 4 ++++ src/Debug/ExceptionManager.php | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 6f18fc2d..0f7e37c3 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -73,12 +73,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Controllers/RestController.php', ]; -$ignoreErrors[] = [ - // identifier: class.notFound - 'message' => '#^Call to static method make\\(\\) on an unknown class Spatie\\\\Ignition\\\\Ignition\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/src/Debug/Debugger.php', -]; $ignoreErrors[] = [ // identifier: booleanNot.alwaysFalse 'message' => '#^Negated boolean expression is always false\\.$#', diff --git a/spec/system/framework/Cli/Commands/ClearLog.spec.php b/spec/system/framework/Cli/Commands/ClearLog.spec.php index 386debf7..c360f111 100644 --- a/spec/system/framework/Cli/Commands/ClearLog.spec.php +++ b/spec/system/framework/Cli/Commands/ClearLog.spec.php @@ -23,6 +23,10 @@ $date = $this->date; $path = STORAGE_PATH . 'logs' . DS . "log-{$date}.log"; + if (! is_dir($dir = dirname($path))) { + @mkdir($dir, 0777, true); + } + // creer 10 faux ficher de log for ($i = 0; $i < 10; $i++) { $newDate = date('Y-m-d', strtotime("+1 year -{$i} day")); diff --git a/src/Debug/ExceptionManager.php b/src/Debug/ExceptionManager.php index 4b9d74b0..84ce6cc4 100644 --- a/src/Debug/ExceptionManager.php +++ b/src/Debug/ExceptionManager.php @@ -36,7 +36,7 @@ class ExceptionManager /** * Gestionnaire d'exception (instance Whoops) */ - private Run $debugger; + private ?Run $debugger = null; /** * Configuration du gestionnaire d'exception