From 5fcee5180976e10ad9e47f08a8416367777f4942 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 4 Nov 2026 19:37:22 -0600 Subject: [PATCH 1/2] [#48] - adding debug page in adr Assisted-by: Claude Code --- src/DebugBar/ADR/DebugPageListener.php | 74 ++++++ src/DebugBar/ADR/Provider.php | 26 +- src/DebugBar/DebugBar.php | 9 + src/DebugBar/DebugBarTypes.php | 1 + .../DebugBar/ADR/DebugPageListenerTest.php | 232 ++++++++++++++++++ .../DebugBar/ADR/ProviderDebugPageTest.php | 197 +++++++++++++++ .../Unit/DebugBar/DebugBarConvenienceTest.php | 25 ++ .../DebugBar/ADR/Action/Halt/GetHalt.php | 33 +++ 8 files changed, 596 insertions(+), 1 deletion(-) create mode 100644 src/DebugBar/ADR/DebugPageListener.php create mode 100644 tests/Unit/DebugBar/ADR/DebugPageListenerTest.php create mode 100644 tests/Unit/DebugBar/ADR/ProviderDebugPageTest.php create mode 100644 tests/support/DebugBar/ADR/Action/Halt/GetHalt.php diff --git a/src/DebugBar/ADR/DebugPageListener.php b/src/DebugBar/ADR/DebugPageListener.php new file mode 100644 index 0000000..601ce00 --- /dev/null +++ b/src/DebugBar/ADR/DebugPageListener.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\DebugBar\ADR; + +use Phalcon\Debug; +use Phalcon\DebugBar\DebugBar; +use Phalcon\DebugBar\Injector; +use Phalcon\DebugBar\ResponseListener; +use Phalcon\DebugBar\Security\AccessGate; +use Phalcon\Events\EventInterface; +use Phalcon\Http\RequestInterface; +use Phalcon\Http\ResponseInterface; +use Throwable; + +use function is_string; + +/** + * Shows the debug page for a server error in an ADR application. The application + * catches each exception before `Debug::listen()` can see it, so this class reads + * the exception that the logger gave to the bar. It runs the bar listener first, + * so that the debug page replaces a page with the bar in it. The event fires + * outside the `try` block of the application: a failure in the debug page keeps + * the error page of the application. + */ +final class DebugPageListener +{ + public function __construct( + private readonly ResponseListener $listener, + private readonly DebugBar $bar, + private readonly Debug $debug, + private readonly AccessGate $accessGate, + private readonly ?RequestInterface $request + ) { + } + + public function __invoke(EventInterface $event, mixed $source, mixed $response): void + { + ($this->listener)($event, $source, $response); + + $exception = $this->bar->getLastException(); + if (null === $exception || !$response instanceof ResponseInterface || !$this->applies($response)) { + return; + } + + try { + $response->setContent($this->debug->renderHtml($exception)); + } catch (Throwable) { + return; + } + } + + /** + * A server error on an HTML page, for a client that the access gate allows. + */ + private function applies(ResponseInterface $response): bool + { + $clientIp = $this->request?->getClientAddress(); + + return ($response->getStatusCode() ?? 0) >= 500 + && $this->accessGate->allows(is_string($clientIp) ? $clientIp : null) + && (new Injector())->shouldInject($response, $this->request?->isAjax() ?? false); + } +} diff --git a/src/DebugBar/ADR/Provider.php b/src/DebugBar/ADR/Provider.php index f50dab5..915906a 100644 --- a/src/DebugBar/ADR/Provider.php +++ b/src/DebugBar/ADR/Provider.php @@ -18,16 +18,19 @@ use Phalcon\Contracts\Container\Service\Provider as ProviderContract; use Phalcon\Contracts\Events\Manager as ManagerContract; use Phalcon\Contracts\Http\AttributeRequest; +use Phalcon\Debug as DebugPage; use Phalcon\DebugBar\Collector\HistoryCollector; use Phalcon\DebugBar\Contracts\History; use Phalcon\DebugBar\Core; use Phalcon\DebugBar\Debug; +use Phalcon\DebugBar\DebugBar; use Phalcon\DebugBar\DebugBarTypes; use Phalcon\DebugBar\Exceptions\CannotUseInProduction; use Phalcon\DebugBar\History\FilesystemHistory; use Phalcon\DebugBar\History\HistoryCookie; use Phalcon\DebugBar\History\HistoryEndpoint; use Phalcon\DebugBar\History\HistoryOptions; +use Phalcon\DebugBar\ResponseListener; use Phalcon\Events\ManagerInterface; use Phalcon\Http\RequestInterface; @@ -46,6 +49,8 @@ final class Provider implements ProviderContract { private Core $core; + private bool | DebugPage $debugPage; + private ?HistoryCookie $historyCookie = null; private ?HistoryEndpoint $historyEndpoint = null; @@ -58,6 +63,7 @@ final class Provider implements ProviderContract public function __construct(array $config = []) { $this->core = new Core($config); + $this->debugPage = $config['debug_page'] ?? false; $this->historyOptions = HistoryOptions::fromConfig($config['history'] ?? []); } @@ -89,7 +95,7 @@ public function provide(Collection $services): void return; } - $eventsManager->attach('application:afterHandle', $listener); + $eventsManager->attach('application:afterHandle', $this->responseListener($listener, $bar, $request)); (new Bridge($bar))->subscribe($eventsManager); } @@ -143,4 +149,22 @@ private function resolve(Collection $services, string $name): mixed { return $services->has($name) ? $services->get($name) : null; } + + private function responseListener( + ResponseListener $listener, + DebugBar $bar, + ?RequestInterface $request + ): DebugPageListener | ResponseListener { + if (false === $this->debugPage) { + return $listener; + } + + return new DebugPageListener( + $listener, + $bar, + true === $this->debugPage ? new DebugPage() : $this->debugPage, + $this->core->getAccessGate(), + $request + ); + } } diff --git a/src/DebugBar/DebugBar.php b/src/DebugBar/DebugBar.php index b993210..fc071fb 100644 --- a/src/DebugBar/DebugBar.php +++ b/src/DebugBar/DebugBar.php @@ -50,6 +50,8 @@ class DebugBar 'meta' => [], ]; + protected ?Throwable $lastException = null; + /** * Registers (or replaces) a collector under its name. */ @@ -62,6 +64,8 @@ public function addCollector(Collector $collector): static public function addException(Throwable $throwable): static { + $this->lastException = $throwable; + $collector = $this->collectors['exceptions'] ?? null; if ($collector instanceof ExceptionAware) { $collector->addThrowable($throwable); @@ -158,6 +162,11 @@ public function getData(): array return $this->data; } + public function getLastException(): ?Throwable + { + return $this->lastException; + } + public function hasCollector(string $name): bool { return isset($this->collectors[$name]); diff --git a/src/DebugBar/DebugBarTypes.php b/src/DebugBar/DebugBarTypes.php index f2b0f2e..24ce96c 100644 --- a/src/DebugBar/DebugBarTypes.php +++ b/src/DebugBar/DebugBarTypes.php @@ -75,6 +75,7 @@ * assets?: array{nonce?: string|null}, * access?: array{allow_ips?: list, callback?: (\Closure(): bool)|null}, * collectors?: array, + * debug_page?: bool|\Phalcon\Debug, * headers?: bool, * history?: history_config, * redact?: array{mask?: list, hidden?: list} diff --git a/tests/Unit/DebugBar/ADR/DebugPageListenerTest.php b/tests/Unit/DebugBar/ADR/DebugPageListenerTest.php new file mode 100644 index 0000000..f03b22e --- /dev/null +++ b/tests/Unit/DebugBar/ADR/DebugPageListenerTest.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Tests\Unit\DebugBar\ADR; + +use Phalcon\Debug; +use Phalcon\Debug\Contracts\Renderer as DebugRenderer; +use Phalcon\DebugBar\ADR\DebugPageListener; +use Phalcon\DebugBar\BarOptions; +use Phalcon\DebugBar\DebugBar; +use Phalcon\DebugBar\Injector; +use Phalcon\DebugBar\Renderer; +use Phalcon\DebugBar\ResponseListener; +use Phalcon\DebugBar\Security\AccessGate; +use Phalcon\Events\Event; +use Phalcon\Http\RequestInterface; +use Phalcon\Http\Response; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use RuntimeException; + +final class DebugPageListenerTest extends AbstractUnitTestCase +{ + private const APP_BODY = 'application error page'; + + private const APP_PAGE = '' . self::APP_BODY . ''; + + /** + * The debug page lists the source of the files in the backtrace, this file + * also. It escapes the source, so these strings of raw markup do not match it. + */ + private const BAR = '
'; + + private const DEBUG_PAGE = "data-tab='backtrace'"; + + private const MESSAGE = 'the action failed'; + + public function testAjaxRequestKeepsTheResponse(): void + { + $request = $this->createMock(RequestInterface::class); + $request->method('isAjax')->willReturn(true); + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException(), $request), $response); + + $this->assertSame(self::APP_PAGE, $response->getContent()); + } + + public function testDeniedClientKeepsTheResponse(): void + { + $request = $this->createMock(RequestInterface::class); + $request->method('getClientAddress')->willReturn('203.0.113.10'); + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException(), $request, ['127.0.0.1']), $response); + + $this->assertSame(self::APP_PAGE, $response->getContent()); + } + + public function testDeniedClientWithNoAddressKeepsTheResponse(): void + { + $request = $this->createMock(RequestInterface::class); + $request->method('getClientAddress')->willReturn(false); + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException(), $request, ['127.0.0.1']), $response); + + $this->assertSame(self::APP_PAGE, $response->getContent()); + } + + public function testFailureInTheDebugPageKeepsTheApplicationPage(): void + { + $renderer = $this->createMock(DebugRenderer::class); + $renderer->method('render')->willThrowException(new RuntimeException('renderer failed')); + $response = $this->response(); + + $this->invoke( + $this->listener($this->barWithException(), null, [], (new Debug())->setRenderer($renderer)), + $response + ); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertStringContainsString(self::APP_BODY, $response->getContent()); + $this->assertStringContainsString(self::BAR, $response->getContent()); + } + + public function testJsonResponseKeepsTheResponse(): void + { + $response = $this->response(); + $response->setContentType('application/json'); + $response->setContent('{"error":true}'); + + $this->invoke($this->listener($this->barWithException()), $response); + + $this->assertSame('{"error":true}', $response->getContent()); + } + + public function testNoExceptionKeepsThePage(): void + { + $response = $this->response(); + + $this->invoke($this->listener(new DebugBar()), $response); + + $this->assertStringContainsString(self::APP_BODY, $response->getContent()); + $this->assertStringContainsString(self::BAR, $response->getContent()); + } + + public function testPayloadThatIsNotAResponseIsIgnored(): void + { + $bar = $this->barWithException(); + + ($this->listener($bar))(new Event('application:afterHandle', $this), null, null); + + $this->assertSame([], $bar->getData()['data']); + } + + public function testResponseWithNoStatusKeepsThePage(): void + { + $response = new Response(); + $response->setContent(self::APP_PAGE); + + $this->invoke($this->listener($this->barWithException()), $response); + + $this->assertStringContainsString(self::APP_BODY, $response->getContent()); + } + + public function testServerErrorShowsTheDebugPage(): void + { + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException()), $response); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertStringContainsString(self::MESSAGE, $response->getContent()); + $this->assertStringNotContainsString(self::APP_BODY, $response->getContent()); + } + + public function testServerErrorWithNoRequestShowsTheDebugPage(): void + { + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException(), null), $response); + + $this->assertStringContainsString(self::MESSAGE, $response->getContent()); + } + + public function testStatusBelowTheServerErrorsKeepsThePage(): void + { + $response = $this->response(499); + + $this->invoke($this->listener($this->barWithException()), $response); + + $this->assertStringContainsString(self::APP_BODY, $response->getContent()); + $this->assertStringNotContainsString(self::DEBUG_PAGE, $response->getContent()); + } + + public function testTheBarIsNotInTheDebugPage(): void + { + $response = $this->response(); + + $this->invoke($this->listener($this->barWithException()), $response); + + $this->assertStringNotContainsString(self::BAR, $response->getContent()); + } + + public function testTheInnerListenerRunsFirst(): void + { + $bar = $this->barWithException(); + $response = $this->response(); + + $this->invoke($this->listener($bar), $response); + + $this->assertArrayHasKey('collectors', $bar->getData()['meta']); + $this->assertTrue($response->getHeaders()->has('X-Debug-Bar')); + } + + private function barWithException(): DebugBar + { + return (new DebugBar())->addException(new RuntimeException(self::MESSAGE)); + } + + private function invoke(DebugPageListener $listener, Response $response): void + { + $listener(new Event('application:afterHandle', $this), null, $response); + } + + /** + * @param list $allowedIps + */ + private function listener( + DebugBar $bar, + ?RequestInterface $request = null, + array $allowedIps = [], + ?Debug $debug = null + ): DebugPageListener { + $accessGate = new AccessGate($allowedIps); + + return new DebugPageListener( + new ResponseListener( + $bar, + new Renderer(), + new Injector(), + $accessGate, + $request, + new BarOptions(true, null) + ), + $bar, + $debug ?? new Debug(), + $accessGate, + $request + ); + } + + private function response(int $status = 500): Response + { + $response = new Response(); + $response->setStatusCode($status); + $response->setContentType('text/html'); + $response->setContent(self::APP_PAGE); + + return $response; + } +} diff --git a/tests/Unit/DebugBar/ADR/ProviderDebugPageTest.php b/tests/Unit/DebugBar/ADR/ProviderDebugPageTest.php new file mode 100644 index 0000000..666d3e2 --- /dev/null +++ b/tests/Unit/DebugBar/ADR/ProviderDebugPageTest.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Tests\Unit\DebugBar\ADR; + +use Phalcon\Debug; +use Phalcon\Debug\Exceptions\RequestHalted; +use Phalcon\DebugBar\Debug as BarFacade; +use Phalcon\DebugBar\DebugBarTypes; +use Phalcon\Http\ResponseInterface; +use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use Phalcon\Tests\Support\DebugBar\ADR\Action\Failure\GetFailure; +use Phalcon\Tests\Support\DebugBar\ADR\Front; +use Phalcon\Tests\Support\DebugBar\ADR\RecordingEmitter; +use PHPUnit\Framework\Attributes\BackupGlobals; + +use function bin2hex; +use function random_bytes; + +/** + * Real requests through a real ADR front controller, with the debug page on. + * + * @phpstan-import-type provider_config from DebugBarTypes + */ +#[BackupGlobals(true)] +final class ProviderDebugPageTest extends AbstractUnitTestCase +{ + /** + * The debug page lists the source of the files in the backtrace, this file + * also. It escapes the source, so these strings of raw markup do not match it. + */ + private const APP_PAGE = 'error page'; + + private const BAR = '
'; + + private const DEBUG_PAGE = "data-tab='backtrace'"; + + private const ENV_VAR = 'DEBUGBAR_TEST_ENV'; + + protected function setUp(): void + { + parent::setUp(); + + BarFacade::setBar(null); + + $_ENV[self::ENV_VAR] = 'dev'; + } + + protected function tearDown(): void + { + BarFacade::setBar(null); + + parent::tearDown(); + } + + public function testBlockedEnvironmentKeepsTheApplicationPage(): void + { + $_ENV[self::ENV_VAR] = 'production'; + + $content = $this->request('/failure', $this->config())->getContent(); + + $this->assertStringContainsString(self::APP_PAGE, $content); + $this->assertStringNotContainsString(self::DEBUG_PAGE, $content); + } + + public function testConfiguredDebugPageIsUsed(): void + { + $secret = bin2hex(random_bytes(16)); + $_SERVER['DEBUG_PAGE_SECRET'] = $secret; + + $default = $this->request('/failure', $this->config())->getContent(); + + $this->assertStringContainsString($secret, $default); + + $debug = (new Debug())->setBlacklist(['server' => ['DEBUG_PAGE_SECRET']]); + $configured = $this->request('/failure', $this->config($debug))->getContent(); + + $this->assertStringContainsString(GetFailure::MESSAGE, $configured); + $this->assertStringNotContainsString($secret, $configured); + } + + public function testDebugHaltInAnActionShowsTheDebugPage(): void + { + $response = $this->request('/halt', $this->config()); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertStringContainsString(self::DEBUG_PAGE, $response->getContent()); + $this->assertStringContainsString(RequestHalted::class, $response->getContent()); + $this->assertStringNotContainsString(self::APP_PAGE, $response->getContent()); + } + + public function testDebugPageIsOffByDefault(): void + { + $content = $this->request('/failure', ['env' => ['var' => self::ENV_VAR]])->getContent(); + + $this->assertStringContainsString(self::APP_PAGE, $content); + $this->assertStringContainsString(self::BAR, $content); + $this->assertStringNotContainsString(self::DEBUG_PAGE, $content); + } + + public function testDeniedClientKeepsTheApplicationPage(): void + { + $_SERVER['REMOTE_ADDR'] = '203.0.113.10'; + + $content = $this->request( + '/failure', + $this->config() + ['access' => ['allow_ips' => ['127.0.0.1']]] + )->getContent(); + + $this->assertStringContainsString(self::APP_PAGE, $content); + $this->assertStringNotContainsString(self::DEBUG_PAGE, $content); + } + + public function testExceptionInAnActionShowsTheDebugPage(): void + { + $response = $this->request('/failure', $this->config()); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertStringContainsString(self::DEBUG_PAGE, $response->getContent()); + $this->assertStringContainsString(GetFailure::MESSAGE, $response->getContent()); + $this->assertStringNotContainsString(self::APP_PAGE, $response->getContent()); + $this->assertStringNotContainsString(self::BAR, $response->getContent()); + } + + public function testFalseKeepsTheApplicationPage(): void + { + $content = $this->request('/failure', $this->config(false))->getContent(); + + $this->assertStringContainsString(self::APP_PAGE, $content); + $this->assertStringNotContainsString(self::DEBUG_PAGE, $content); + } + + public function testJsonErrorKeepsTheJsonBody(): void + { + $response = $this->request('/failure', $this->config(), false); + + $this->assertSame(500, $response->getStatusCode()); + $this->assertStringContainsString('Internal Server Error', $response->getContent()); + $this->assertStringNotContainsString(GetFailure::MESSAGE, $response->getContent()); + } + + public function testNormalPageKeepsTheBar(): void + { + $content = $this->request('/page', $this->config())->getContent(); + + $this->assertStringStartsWith('page', $content); + $this->assertStringContainsString(self::BAR, $content); + } + + public function testRouteNotFoundKeepsTheApplicationPageAndTheBar(): void + { + $response = $this->request('/missing', $this->config()); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertStringContainsString(self::APP_PAGE, $response->getContent()); + $this->assertStringContainsString(self::BAR, $response->getContent()); + } + + /** + * @return array{env: array{var: string}, debug_page: bool|Debug} + */ + private function config(bool | Debug $debugPage = true): array + { + return [ + 'env' => ['var' => self::ENV_VAR], + 'debug_page' => $debugPage, + ]; + } + + /** + * @param provider_config $config + */ + private function request(string $uri, array $config, bool $htmlErrors = true): ResponseInterface + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_SERVER['REQUEST_URI'] = $uri; + + $emitter = new RecordingEmitter(); + + $this->assertSame(0, (new Front($emitter, $config, $htmlErrors))->run()); + if (null === $emitter->response) { + $this->fail('Expected the front controller to emit a response.'); + } + + return $emitter->response; + } +} diff --git a/tests/Unit/DebugBar/DebugBarConvenienceTest.php b/tests/Unit/DebugBar/DebugBarConvenienceTest.php index deb6cc7..9970fe5 100644 --- a/tests/Unit/DebugBar/DebugBarConvenienceTest.php +++ b/tests/Unit/DebugBar/DebugBarConvenienceTest.php @@ -72,6 +72,31 @@ public function testConvenienceMethodsNoOpWhenCollectorsAbsent(): void ); } + public function testLastExceptionIsKeptWithNoExceptionsCollector(): void + { + $exception = new RuntimeException('boom'); + $bar = new DebugBar(); + + $bar->addException($exception); + + $this->assertSame($exception, $bar->getLastException()); + } + + public function testLastExceptionIsNullUntilAnExceptionIsAdded(): void + { + $this->assertNull((new DebugBar())->getLastException()); + } + + public function testLastExceptionIsTheMostRecentOne(): void + { + $last = new RuntimeException('second'); + $bar = new DebugBar(); + + $bar->addException(new RuntimeException('first'))->addException($last); + + $this->assertSame($last, $bar->getLastException()); + } + public function testMessageAndLevelsDelegateToMessagesCollector(): void { $messages = new RecordingMessagesCollector(); diff --git a/tests/support/DebugBar/ADR/Action/Halt/GetHalt.php b/tests/support/DebugBar/ADR/Action/Halt/GetHalt.php new file mode 100644 index 0000000..c01eeff --- /dev/null +++ b/tests/support/DebugBar/ADR/Action/Halt/GetHalt.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Tests\Support\DebugBar\ADR\Action\Halt; + +use Phalcon\Contracts\ADR\Action; +use Phalcon\Contracts\Http\AttributeRequest; +use Phalcon\Debug; +use Phalcon\Http\Response; +use Phalcon\Http\ResponseInterface; + +/** + * Calls `Debug::halt()` for `GET /halt`. + */ +final class GetHalt implements Action +{ + public function __invoke(AttributeRequest $request): ResponseInterface + { + (new Debug())->halt(); + + return new Response(); + } +} From e8b283e7018329b8d0d541f4b60bee0f881f4e6f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 4 Nov 2026 19:37:52 -0600 Subject: [PATCH 2/2] [#48] - updating composer, changelog and docs Assisted-by: Claude Code --- CHANGELOG.md | 1 + composer.lock | 220 +++++++++++++++++++++++++------------------------- docs/index.md | 32 +++++++- 3 files changed, 142 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6f3cb..94fe0da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas - `History\HistoryEndpoint` takes the request and the base URI with no DI container, and `Mvc\HistoryListener` selects the MVC controller. [#41](https://github.com/phalcon/debugbar/issues/41) - `History\HistoryHandler` lists, reads and clears the request history with no MVC types. [#42](https://github.com/phalcon/debugbar/issues/42) - Request history in ADR applications, with the `history.base_uri` option. [#43](https://github.com/phalcon/debugbar/issues/43) +- The `debug_page` option shows the debug page for a server error in ADR applications. [#48](https://github.com/phalcon/debugbar/issues/48) ### Fixed diff --git a/composer.lock b/composer.lock index d49d8cc..759be9c 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "matthiasmullie/minify", - "version": "1.3.75", + "version": "1.3.76", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "76ba4a5f555fd7bf4aa408af608e991569076671" + "reference": "bbd1844c257a49488d914f4c7c7ba74cc8861fdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671", - "reference": "76ba4a5f555fd7bf4aa408af608e991569076671", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/bbd1844c257a49488d914f4c7c7ba74cc8861fdc", + "reference": "bbd1844c257a49488d914f4c7c7ba74cc8861fdc", "shasum": "" }, "require": { @@ -66,7 +66,7 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.75" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.76" }, "funding": [ { @@ -74,8 +74,8 @@ "type": "github" } ], - "time": "2025-06-25T09:56:19+00:00", - "published-time": "2025-06-25T13:14:12+00:00" + "time": "2026-11-03T11:28:38+00:00", + "published-time": "2026-11-03T11:29:19+00:00" }, { "name": "matthiasmullie/path-converter", @@ -519,16 +519,16 @@ }, { "name": "ergebnis/agent-detector", - "version": "1.3.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/ergebnis/agent-detector.git", - "reference": "0cd815cec1207873c37624d33f0f2853523152f3" + "reference": "bcc63004284c61c6df6e9625acf2673e3be1889b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/0cd815cec1207873c37624d33f0f2853523152f3", - "reference": "0cd815cec1207873c37624d33f0f2853523152f3", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/bcc63004284c61c6df6e9625acf2673e3be1889b", + "reference": "bcc63004284c61c6df6e9625acf2673e3be1889b", "shasum": "" }, "require": { @@ -538,8 +538,8 @@ "ergebnis/composer-normalize": "^2.54.0", "ergebnis/license": "^2.8.0", "ergebnis/php-cs-fixer-config": "^6.60.2", - "ergebnis/phpstan-rules": "^2.14.0", - "ergebnis/phpunit-slow-test-detector": "^2.26.0", + "ergebnis/phpstan-rules": "^2.14.2", + "ergebnis/phpunit-slow-test-detector": "^2.27.0", "ergebnis/rector-rules": "^1.18.5", "fakerphp/faker": "^1.24.1", "infection/infection": "^0.26.6", @@ -554,7 +554,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.3-dev" + "dev-main": "2.0-dev" }, "composer-normalize": { "indent-size": 2, @@ -584,8 +584,8 @@ "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", "source": "https://github.com/ergebnis/agent-detector" }, - "time": "2026-10-15T11:13:32+00:00", - "published-time": "2026-10-15T11:15:00+00:00" + "time": "2026-10-29T17:44:55+00:00", + "published-time": "2026-10-29T17:49:41+00:00" }, { "name": "evenement/evenement", @@ -700,23 +700,23 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.95.27", + "version": "v3.95.28", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "1d0c68ee5f37446d8ad5970b82384cd1bb0a3317" + "reference": "de66c41256ad603e63cf033bd153fa4a14ebaca4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1d0c68ee5f37446d8ad5970b82384cd1bb0a3317", - "reference": "1d0c68ee5f37446d8ad5970b82384cd1bb0a3317", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/de66c41256ad603e63cf033bd153fa4a14ebaca4", + "reference": "de66c41256ad603e63cf033bd153fa4a14ebaca4", "shasum": "" }, "require": { "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", - "ergebnis/agent-detector": "^1.2", + "ergebnis/agent-detector": "^2.0.0", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", @@ -728,17 +728,17 @@ "react/socket": "^1.16", "react/stream": "^1.4", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0 || ^9.0", - "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.37", - "symfony/polyfill-php80": "^1.37", - "symfony/polyfill-php81": "^1.37", - "symfony/polyfill-php84": "^1.37", - "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", - "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" + "symfony/console": "^5.4.55 || ^6.4.50 || ^7.4.23 || ^8.1.11", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.44 || ^7.4.17 || ^8.1.5", + "symfony/filesystem": "^5.4.56 || ^6.4.50 || ^7.4.23 || ^8.1.11", + "symfony/finder": "^5.4.45 || ^6.4.50 || ^7.4.23 || ^8.1.11", + "symfony/options-resolver": "^5.4.45 || ^6.4.49 || ^7.4.22 || ^8.1.10", + "symfony/polyfill-mbstring": "^1.43.3", + "symfony/polyfill-php80": "^1.43", + "symfony/polyfill-php81": "^1.38.1", + "symfony/polyfill-php84": "^1.43", + "symfony/process": "^5.4.54 || ^6.4.49 || ^7.4.22 || ^8.1.10", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.4.8 || ^8.1" }, "require-dev": { "facile-it/paraunit": "^1.3.1 || ^2.11.0", @@ -749,9 +749,9 @@ "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", "phpunit/phpunit": "^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.35 || ^13.3.3", - "symfony/polyfill-php85": "^1.41", - "symfony/var-dumper": "^5.4.48 || ^6.4.45 || ^7.4.18 || ^8.1.7", - "symfony/yaml": "^5.4.53 || ^6.4.45 || ^7.4.18 || ^8.1.6" + "symfony/polyfill-php85": "^1.43", + "symfony/var-dumper": "^5.4.54 || ^6.4.49 || ^7.4.22 || ^8.1.10", + "symfony/yaml": "^5.4.56 || ^6.4.50 || ^7.4.23 || ^8.1.11" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -792,7 +792,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.27" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.28" }, "funding": [ { @@ -800,8 +800,8 @@ "type": "github" } ], - "time": "2026-09-22T09:54:55+00:00", - "published-time": "2026-09-22T09:55:06+00:00" + "time": "2026-11-04T13:04:49+00:00", + "published-time": "2026-11-04T13:04:58+00:00" }, { "name": "masterminds/html5", @@ -1567,11 +1567,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.3.4", + "version": "2.3.5", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f20ce6f3c82556cfcfc086b152445a12d1d64fff", - "reference": "f20ce6f3c82556cfcfc086b152445a12d1d64fff", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/645a1bd6bead15fc1b8474137467961d0fa30a79", + "reference": "645a1bd6bead15fc1b8474137467961d0fa30a79", "shasum": "" }, "require": { @@ -1627,8 +1627,8 @@ "type": "github" } ], - "time": "2026-10-28T22:38:53+00:00", - "published-time": "2026-10-28T22:39:06+00:00" + "time": "2026-11-03T12:14:22+00:00", + "published-time": "2026-11-03T12:14:36+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3794,16 +3794,16 @@ }, { "name": "symfony/browser-kit", - "version": "v6.4.49", + "version": "v6.4.50", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "93bd4fe459ad2850f4ba499dfa40253ce9891c2a" + "reference": "abc38d9d7bdc158a613d0ff9b36cc88e18084396" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/93bd4fe459ad2850f4ba499dfa40253ce9891c2a", - "reference": "93bd4fe459ad2850f4ba499dfa40253ce9891c2a", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/abc38d9d7bdc158a613d0ff9b36cc88e18084396", + "reference": "abc38d9d7bdc158a613d0ff9b36cc88e18084396", "shasum": "" }, "require": { @@ -3843,7 +3843,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v6.4.49" + "source": "https://github.com/symfony/browser-kit/tree/v6.4.50" }, "funding": [ { @@ -3863,21 +3863,21 @@ "type": "tidelift" } ], - "time": "2026-10-20T12:47:41+00:00", - "published-time": "2026-10-28T18:17:20+00:00" + "time": "2026-10-31T16:11:44+00:00", + "published-time": "2026-11-04T11:24:27+00:00" }, { "name": "symfony/console", - "version": "v6.4.49", + "version": "v6.4.50", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e918c95b70bdfa09148054a71c82c2858ae82efd" + "reference": "955771637c0a01d5e91730aa8f63edae5190e2c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e918c95b70bdfa09148054a71c82c2858ae82efd", - "reference": "e918c95b70bdfa09148054a71c82c2858ae82efd", + "url": "https://api.github.com/repos/symfony/console/zipball/955771637c0a01d5e91730aa8f63edae5190e2c7", + "reference": "955771637c0a01d5e91730aa8f63edae5190e2c7", "shasum": "" }, "require": { @@ -3942,7 +3942,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.49" + "source": "https://github.com/symfony/console/tree/v6.4.50" }, "funding": [ { @@ -3962,21 +3962,21 @@ "type": "tidelift" } ], - "time": "2026-10-28T10:54:25+00:00", - "published-time": "2026-10-28T18:17:12+00:00" + "time": "2026-11-03T21:19:33+00:00", + "published-time": "2026-11-04T11:24:42+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.1", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/feb1edf4e546af3b85c5caebe8359a20db230ca3", + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3", "shasum": "" }, "require": { @@ -3989,7 +3989,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.7-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -4014,7 +4014,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.8.1" }, "funding": [ { @@ -4034,8 +4034,8 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00", - "published-time": "2026-06-27T21:46:34+00:00" + "time": "2026-09-16T14:15:10+00:00", + "published-time": "2026-11-04T11:25:34+00:00" }, { "name": "symfony/dom-crawler", @@ -4196,16 +4196,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.7.1", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + "reference": "658e29751424da3ef4758c60f769e59d1b2981eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", - "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/658e29751424da3ef4758c60f769e59d1b2981eb", + "reference": "658e29751424da3ef4758c60f769e59d1b2981eb", "shasum": "" }, "require": { @@ -4219,7 +4219,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.7-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -4252,7 +4252,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.8.1" }, "funding": [ { @@ -4272,21 +4272,21 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00", - "published-time": "2026-06-27T21:46:35+00:00" + "time": "2026-09-16T14:15:10+00:00", + "published-time": "2026-11-04T11:25:36+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.48", + "version": "v6.4.50", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e3ff48b19ac71ee79f89021122211710b892ae0b" + "reference": "9c54f695d2fcfb0bb21c8003b7a038bc631aa2e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e3ff48b19ac71ee79f89021122211710b892ae0b", - "reference": "e3ff48b19ac71ee79f89021122211710b892ae0b", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9c54f695d2fcfb0bb21c8003b7a038bc631aa2e9", + "reference": "9c54f695d2fcfb0bb21c8003b7a038bc631aa2e9", "shasum": "" }, "require": { @@ -4323,7 +4323,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.48" + "source": "https://github.com/symfony/filesystem/tree/v6.4.50" }, "funding": [ { @@ -4343,21 +4343,21 @@ "type": "tidelift" } ], - "time": "2026-10-06T17:30:58+00:00", - "published-time": "2026-10-14T06:43:23+00:00" + "time": "2026-11-04T08:16:31+00:00", + "published-time": "2026-11-04T11:24:13+00:00" }, { "name": "symfony/finder", - "version": "v6.4.49", + "version": "v6.4.50", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5a6cbefbe4875520f1b36d42d2a172ed3bf994d4" + "reference": "02515ea78b3b17fdf7900e5abfdd5aad1d24a9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5a6cbefbe4875520f1b36d42d2a172ed3bf994d4", - "reference": "5a6cbefbe4875520f1b36d42d2a172ed3bf994d4", + "url": "https://api.github.com/repos/symfony/finder/zipball/02515ea78b3b17fdf7900e5abfdd5aad1d24a9f8", + "reference": "02515ea78b3b17fdf7900e5abfdd5aad1d24a9f8", "shasum": "" }, "require": { @@ -4392,7 +4392,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.49" + "source": "https://github.com/symfony/finder/tree/v6.4.50" }, "funding": [ { @@ -4412,21 +4412,21 @@ "type": "tidelift" } ], - "time": "2026-10-19T12:57:44+00:00", - "published-time": "2026-10-28T18:16:37+00:00" + "time": "2026-11-02T03:15:15+00:00", + "published-time": "2026-11-04T11:24:12+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.49", + "version": "v6.4.50", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "8f2cc53f1ce343d38c42abdd5e008b0ae92627a5" + "reference": "ede6aedd50f56088b6114038884a586a4c6c7054" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/8f2cc53f1ce343d38c42abdd5e008b0ae92627a5", - "reference": "8f2cc53f1ce343d38c42abdd5e008b0ae92627a5", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ede6aedd50f56088b6114038884a586a4c6c7054", + "reference": "ede6aedd50f56088b6114038884a586a4c6c7054", "shasum": "" }, "require": { @@ -4491,7 +4491,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.49" + "source": "https://github.com/symfony/http-client/tree/v6.4.50" }, "funding": [ { @@ -4511,21 +4511,21 @@ "type": "tidelift" } ], - "time": "2026-10-26T09:21:38+00:00", - "published-time": "2026-10-28T18:17:39+00:00" + "time": "2026-11-04T08:16:31+00:00", + "published-time": "2026-11-04T11:25:22+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.7.3", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" + "reference": "f9f94260f787446c6fd430b2ae1969fe41ae1ba8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", - "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/f9f94260f787446c6fd430b2ae1969fe41ae1ba8", + "reference": "f9f94260f787446c6fd430b2ae1969fe41ae1ba8", "shasum": "" }, "require": { @@ -4538,7 +4538,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.7-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -4574,7 +4574,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.3" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.8.1" }, "funding": [ { @@ -4594,8 +4594,8 @@ "type": "tidelift" } ], - "time": "2026-08-04T08:41:16+00:00", - "published-time": "2026-08-30T19:56:36+00:00" + "time": "2026-10-04T15:04:25+00:00", + "published-time": "2026-11-04T11:25:37+00:00" }, { "name": "symfony/mime", @@ -5582,16 +5582,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.7.3", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", - "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/052852a96fd5e5ebdb617dba22031e41a3300fb5", + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5", "shasum": "" }, "require": { @@ -5609,7 +5609,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.7-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -5645,7 +5645,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" + "source": "https://github.com/symfony/service-contracts/tree/v3.8.1" }, "funding": [ { @@ -5665,8 +5665,8 @@ "type": "tidelift" } ], - "time": "2026-07-27T15:39:01+00:00", - "published-time": "2026-08-30T19:56:37+00:00" + "time": "2026-10-20T17:38:25+00:00", + "published-time": "2026-11-04T11:25:33+00:00" }, { "name": "symfony/stopwatch", diff --git a/docs/index.md b/docs/index.md index 6f201fa..170483a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,6 +31,8 @@ use Phalcon\Debug; (new Debug())->listen(); ``` +`listen()` does not see the exceptions of an ADR application, because the application catches them first. For an ADR application, see [ADR](#adr). + The remainder of this document covers the debug bar. ## Registering the Debug Bar @@ -213,6 +215,33 @@ The provider decorates the router (`Phalcon\Contracts\ADR\Router\Router`) with ` Do not resolve the router during registration. `extend()` on a resolved service throws `CannotExtendResolved`. In a blocked environment, the provider does not change the router. +The `debug_page` key shows the debug page (`Phalcon\Debug`) for a server error. It is off by default. The provider reads the exception from the bar, so add `ExceptionAdapter` to your logger. + +```php + true]))->provide($container); +``` + +The debug page replaces the content of the response when the status is 500 or higher, the response is an HTML page and the access gate allows the client. The status does not change. A JSON response, an AJAX request and a 404 response keep their content. The bar is not in the debug page. If the debug page fails, the response keeps the error page of the application. + +!!! warning "NOTE" + + The debug page shows `$_SERVER`, which can hold passwords and keys. To remove these values, give a configured `Phalcon\Debug` to the provider. + +```php +setBlacklist(['server' => ['DB_PASSWORD', 'APP_CRYPT_SALT']]); + +(new DebugBarProvider(['debug_page' => $debug]))->provide($container); +``` + These limits apply in an ADR application: - **One request for each process.** `Phalcon\ADR\Application::handle()` supports a reused request in a long-lived worker, but collectors have no reset method. In a worker, their data grows from request to request. `HistoryCookie::fromGlobals()` also reads only at boot. @@ -252,7 +281,7 @@ if (null !== $listener) { ## Configuration -The second argument to `Provider` is a nested array. Every key is optional. `Phalcon\DebugBar\ADR\Provider` and `Phalcon\DebugBar\Core` take the same array as their only argument. `Core` does not read the `history` keys. +The second argument to `Provider` is a nested array. Every key is optional. `Phalcon\DebugBar\ADR\Provider` and `Phalcon\DebugBar\Core` take the same array as their only argument. `Core` does not read the `debug_page` and `history` keys. | Key | Type | Default | Purpose | |--------------------|---------------------------|--------------------------|------------------------------------------------------------------| @@ -260,6 +289,7 @@ The second argument to `Provider` is a nested array. Every key is optional. `Pha | `access.callback` | `(Closure(): bool)\|null` | `null` | Extra gate. When present, it must also return `true`. | | `assets.nonce` | `string\|null` | `null` | CSP nonce stamped on the injected `