Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas
- `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)
- `Mvc\Bridge` and `ServiceBridge` feed the built-in collectors, which no longer hold framework event names. [#15](https://github.com/phalcon/debugbar/issues/15)
- `DebugBar::addCollector()` subscribes a collector that implements `Contracts\Subscriber`. [#15](https://github.com/phalcon/debugbar/issues/15)

### Changed

- **Breaking:** the built-in collectors no longer implement `Contracts\Subscriber` and have no `subscribe()` method. [#15](https://github.com/phalcon/debugbar/issues/15)

### Fixed

Expand Down
60 changes: 57 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ placement does not depend on request history.
A collector reads its data in one of four ways:

- **Snapshot** - reads state when the response is assembled.
- **Streamed** - subscribes to framework events and accumulates as they fire.
- **Streamed** - fed from framework events by a bridge, and accumulates as they fire.
- **Manual** - fed through the `Debug` facade.
- **Adapter** - captured from a `Phalcon\Logger` through the debug bar's logger adapter.

Expand All @@ -430,7 +430,61 @@ A collector reads its data in one of four ways:
| `time` | Total request time and named measures | manual + snapshot |
| `view` | Rendered view paths and timings | streamed |

Streamed collectors read their data off the event source (the connection, view, router, or cache handed to them by the event). They never resolve a service from the container.
The bridges read the data off the event source (the connection, view, router, or cache handed to them by the event). They never resolve a service from the container. `Phalcon\DebugBar\ServiceBridge` handles the `db:*`, `view:*` and `cache:*` events in all applications. `Phalcon\DebugBar\Mvc\Bridge` and `Phalcon\DebugBar\ADR\Bridge` handle the events of their application type.

## Custom Collectors

To add a panel, extend `Phalcon\DebugBar\Collector\AbstractCollector` and set `NAME`, `$label` and `$panel`. `collect()` returns the data of the panel and the badge. To get data from events, also implement `Phalcon\DebugBar\Contracts\Subscriber` and attach your listeners in `subscribe()`.

```php
<?php

use Phalcon\DebugBar\Collector\AbstractCollector;
use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;

final class OrdersCollector extends AbstractCollector implements Subscriber
{
public const NAME = 'orders';

protected string $label = 'Orders';

protected string $panel = 'list';

/**
* @var list<array{label: string, message: string}>
*/
private array $rows = [];

public function collect(): array
{
return ['panel' => $this->rows, 'badge' => count($this->rows)];
}

public function subscribe(ManagerInterface $eventsManager): void
{
$eventsManager->attach(
'orders:afterCreate',
function (EventInterface $event, mixed $source, mixed $id): void {
$this->rows[] = ['label' => 'Created', 'message' => is_string($id) ? $id : ''];
}
);
}
}
```

Add the collector after the provider boots and before the response event. `getBar()` returns `null` when the bar did not boot, for example in a blocked environment.

```php
<?php

use Phalcon\DebugBar\Debug;

Debug::getBar()?->addCollector(new OrdersCollector());
```

`addCollector()` calls `subscribe()` when the bar has an events manager. A collector with no events does not implement `Subscriber`, and you add it the same way. A collector with the name of a built-in collector replaces it. The bridges do not feed the replacement, so it must attach its own listeners.

## Sharing the Events Manager

Expand Down Expand Up @@ -488,7 +542,7 @@ try {
| `startMeasure(string $name, ?string $label = null)` | `time` | Opens a named measure. |
| `stopMeasure(string $name)` | `time` | Closes the named measure. |

The `exceptions` collector also captures throwables automatically. It subscribes to `dispatch:beforeException`, records the throwable, and leaves it to bubble - the bar never handles or swallows it. A throwable appears in the panel only when the request still finishes with a rendered response; a caught exception, or one the application forwards to an error page, qualifies, while a fully unhandled one aborts before the bar renders.
The `exceptions` collector also captures throwables automatically. In an MVC application, `Phalcon\DebugBar\Mvc\Bridge` listens to `dispatch:beforeException`, records the throwable, and leaves it to bubble - the bar never handles or swallows it. A throwable appears in the panel only when the request still finishes with a rendered response; a caught exception, or one the application forwards to an error page, qualifies, while a fully unhandled one aborts before the bar renders.

## Capturing Logs

Expand Down
29 changes: 2 additions & 27 deletions src/DebugBar/Collector/CacheCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,17 @@

namespace Phalcon\DebugBar\Collector;

use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\DebugBar\DebugBarTypes;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;

use function count;
use function is_string;

/**
* Records cache operations by subscribing to the `cache:after*` events. The key
* is the event data, so it is read straight off the event - the `cache` service
* is never resolved. `addOperation()` records an operation with no events manager.
* Records cache operations. `addOperation()` takes the operation and the key.
*
* @phpstan-import-type list_envelope from DebugBarTypes
* @phpstan-import-type list_panel from DebugBarTypes
*/
final class CacheCollector extends AbstractCollector implements Subscriber
final class CacheCollector extends AbstractCollector
{
public const NAME = 'cache';

Expand Down Expand Up @@ -62,23 +56,4 @@ public function collect(): array
'badge' => count($this->operations),
];
}

public function subscribe(ManagerInterface $eventsManager): void
{
$events = [
'cache:afterGet' => 'Get',
'cache:afterSet' => 'Set',
'cache:afterHas' => 'Has',
'cache:afterDelete' => 'Delete',
];

foreach ($events as $eventName => $operation) {
$eventsManager->attach(
$eventName,
function (EventInterface $event, mixed $cache, mixed $key) use ($operation): void {
$this->addOperation($operation, is_string($key) ? $key : '');
}
);
}
}
}
34 changes: 3 additions & 31 deletions src/DebugBar/Collector/DatabaseCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@

namespace Phalcon\DebugBar\Collector;

use Phalcon\Db\Adapter\AbstractAdapter;
use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\DebugBar\DebugBarTypes;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;

use function count;
use function hrtime;
Expand All @@ -26,15 +22,13 @@
use function trim;

/**
* Records SQL queries by subscribing to `db:beforeQuery`/`db:afterQuery`. The
* connection is the event's source, so the statement, bindings, and timing are
* read straight off it - no `db` service is resolved. `startQuery()` and
* `finishQuery()` record a query with no events manager.
* Records SQL queries. `startQuery()` starts the time, and `finishQuery()` takes
* the statement and the bindings.
*
* @phpstan-import-type db_query from DebugBarTypes
* @phpstan-import-type database_envelope from DebugBarTypes
*/
final class DatabaseCollector extends AbstractCollector implements Subscriber
final class DatabaseCollector extends AbstractCollector
{
use EncodesJson;
use FormatsDuration;
Expand Down Expand Up @@ -113,28 +107,6 @@ public function startQuery(): void
$this->started = hrtime(true);
}

public function subscribe(ManagerInterface $eventsManager): void
{
$eventsManager->attach(
'db:beforeQuery',
function (): void {
$this->startQuery();
}
);

$eventsManager->attach(
'db:afterQuery',
function (EventInterface $event, mixed $connection): void {
if ($connection instanceof AbstractAdapter) {
$this->finishQuery(
$connection->getSQLStatement(),
$connection->getSQLVariables()
);
}
}
);
}

/**
* Appends the bound parameters (e.g. `:APL0`) to the statement so the panel
* shows the actual values sent to the database.
Expand Down
28 changes: 4 additions & 24 deletions src/DebugBar/Collector/ExceptionsCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,22 @@
namespace Phalcon\DebugBar\Collector;

use Phalcon\DebugBar\Contracts\ExceptionAware;
use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\DebugBar\DebugBarTypes;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;
use Throwable;

use function count;
use function strrpos;
use function substr;

/**
* Records throwables - automatically via `dispatch:beforeException` (the
* throwable is only recorded, never swallowed) and manually through the `Debug`
* facade / DebugBar `addException()`. Each entry keeps a compact summary plus the
* stack trace; the full report remains the Debug page's job.
* Records throwables that `addThrowable()` receives, from a bridge or through the
* `Debug` facade / DebugBar `addException()`. Each entry keeps a compact summary
* plus the stack trace; the full report remains the Debug page's job.
*
* @phpstan-import-type exception_envelope from DebugBarTypes
* @phpstan-import-type exception_panel from DebugBarTypes
*/
final class ExceptionsCollector extends AbstractCollector implements ExceptionAware, Subscriber
final class ExceptionsCollector extends AbstractCollector implements ExceptionAware
{
public const NAME = 'exceptions';

Expand Down Expand Up @@ -69,22 +65,6 @@ public function collect(): array
];
}

/**
* Auto-captures dispatch exceptions. The throwable is recorded and left to
* bubble - the bar never handles or swallows it.
*/
public function subscribe(ManagerInterface $eventsManager): void
{
$eventsManager->attach(
'dispatch:beforeException',
function (EventInterface $event, mixed $dispatcher, mixed $exception): void {
if ($exception instanceof Throwable) {
$this->addThrowable($exception);
}
}
);
}

private function shortClass(string $class): string
{
$position = strrpos($class, '\\');
Expand Down
35 changes: 3 additions & 32 deletions src/DebugBar/Collector/RouteCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,17 @@
namespace Phalcon\DebugBar\Collector;

use Closure;
use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\DebugBar\DebugBarTypes;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;
use Phalcon\Mvc\RouterInterface;

/**
* Reports the matched route. The router is the source of `router:matchedRoute`,
* so it is captured there and read at `collect()` - by response time it has been
* fully resolved (module/controller/action/params), which is not yet true when
* the event fires. Nothing is resolved from the container. `setRoute()` gives the
* panel values with no events manager.
* Reports the matched route. `setRoute()` takes the panel values, or a closure
* that returns them when the values are not final until `collect()`.
*
* @phpstan-import-type grid_envelope from DebugBarTypes
* @phpstan-import-type grid_panel from DebugBarTypes
*/
final class RouteCollector extends AbstractCollector implements Subscriber
final class RouteCollector extends AbstractCollector
{
use EncodesJson;

public const NAME = 'route';

protected string $icon = 'icon-route';
Expand Down Expand Up @@ -68,24 +59,4 @@ public function setRoute(array | Closure $route): void
{
$this->route = $route;
}

public function subscribe(ManagerInterface $eventsManager): void
{
$eventsManager->attach(
'router:matchedRoute',
function (EventInterface $event, mixed $router): void {
if ($router instanceof RouterInterface) {
$this->setRoute(
fn (): array => [
'Module' => $router->getModuleName(),
'Namespace' => $router->getNamespaceName(),
'Controller' => $router->getControllerName(),
'Action' => $router->getActionName(),
'Params' => $this->jsonOrEmpty($router->getParams()),
]
);
}
}
);
}
}
31 changes: 4 additions & 27 deletions src/DebugBar/Collector/ViewCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,22 @@

namespace Phalcon\DebugBar\Collector;

use Phalcon\DebugBar\Contracts\Subscriber;
use Phalcon\DebugBar\DebugBarTypes;
use Phalcon\Events\EventInterface;
use Phalcon\Events\ManagerInterface;

use function array_pop;
use function count;
use function hrtime;
use function is_string;

/**
* Records rendered views by subscribing to `view:beforeRenderView`/
* `view:afterRenderView`. The path is the event data on the "before" event, so
* nothing is resolved from the container. A stack pairs each "before" with its
* matching "after", so nested partials are timed correctly. `startRender()` and
* `finishRender()` record a render with no events manager.
* Records rendered views. `startRender()` takes the path and `finishRender()` ends
* the last render that started. A stack pairs each start with its finish, so nested
* partials are timed correctly.
*
* @phpstan-import-type list_envelope from DebugBarTypes
* @phpstan-import-type view_pending from DebugBarTypes
* @phpstan-import-type view_render from DebugBarTypes
*/
final class ViewCollector extends AbstractCollector implements Subscriber
final class ViewCollector extends AbstractCollector
{
use FormatsDuration;

Expand Down Expand Up @@ -99,21 +93,4 @@ public function startRender(string $path): void
'start' => hrtime(true),
];
}

public function subscribe(ManagerInterface $eventsManager): void
{
$eventsManager->attach(
'view:beforeRenderView',
function (EventInterface $event, mixed $view, mixed $path): void {
$this->startRender(is_string($path) ? $path : '');
}
);

$eventsManager->attach(
'view:afterRenderView',
function (): void {
$this->finishRender();
}
);
}
}
6 changes: 3 additions & 3 deletions src/DebugBar/Contracts/Subscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
use Phalcon\Events\ManagerInterface;

/**
* Implemented by a collector that gathers its data from framework events. At
* boot the provider hands each such collector the application's EventsManager
* so it can attach its own listeners.
* A custom collector implements this to attach its own listeners. `DebugBar::addCollector()`
* calls `subscribe()` when the bar has an events manager. The built-in collectors
* do not use it: the bridges feed them.
*/
interface Subscriber
{
Expand Down
Loading
Loading