Skip to content

Commit e7485c9

Browse files
authored
Add support for debug_kit pane (#1)
* feat: debugkit panel * tests: add trait to simplify registry tests * Pass CSRF token in panel ajax request * Add missing test trait * Properly extend debugkit controller * Clean up panel name * Use proper event listeners * Condtionally load debugkit route when debugkit is loaded * Add docs for debug_kit panel
1 parent 407aa4e commit e7485c9

17 files changed

+990
-189
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ A powerful CakePHP plugin for discovering, caching, and querying PHP 8 attribute
3232
- [Discover Attributes](#discover-attributes)
3333
- [List Attributes](#list-attributes)
3434
- [Inspect Attributes](#inspect-attributes)
35+
- [DebugKit Panel](#debugkit-panel)
3536
- [Testing](#testing)
3637
- [Contributing](#contributing)
3738
- [License](#license)
@@ -49,6 +50,7 @@ The CakePHP Attribute Registry Plugin provides a centralized system for discover
4950
- 🔌 **Plugin Support** - Automatically scans all loaded CakePHP plugins
5051
- 🖥️ **CLI Tools** - Console commands for discovery, listing, and inspection
5152
- 🏗️ **Service-Oriented** - Clean architecture with dependency injection via CakePHP's container
53+
- 🐛 **DebugKit Panel** - Visual panel for browsing discovered attributes during development
5254

5355
## Requirements
5456

@@ -394,6 +396,17 @@ Found 2 attributes for attribute "Route":
394396
- method: GET
395397
```
396398

399+
## DebugKit Panel
400+
401+
When [DebugKit](https://github.com/cakephp/debug_kit) is installed, the plugin automatically registers a panel for browsing discovered attributes.
402+
403+
![DebugKit Panel](docs/debug_kit_screenshot.png)
404+
405+
The panel provides:
406+
- Overview of all discovered attributes grouped by type or file
407+
- Search functionality to filter attributes
408+
- Re-discover button to refresh the attribute cache
409+
397410
## Testing
398411

399412
Run the test suite:

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
"cakephp/cakephp": "^5.2"
1616
},
1717
"require-dev": {
18-
"phpunit/phpunit": "^11.1.3 || ^12.0",
19-
"cakephp/plugin-installer": "^2.0.1",
2018
"cakephp/cakephp-codesniffer": "^5.2",
19+
"cakephp/debug_kit": "^5.2",
20+
"cakephp/plugin-installer": "^2.0.1",
2121
"phpstan/phpstan": "^2.1",
22+
"phpunit/phpunit": "^11.1.3 || ^12.0",
2223
"rector/rector": "^2.1"
2324
},
2425
"autoload": {
@@ -28,7 +29,7 @@
2829
},
2930
"autoload-dev": {
3031
"psr-4": {
31-
"AttributeRegistry\\": "tests/test_app/src/",
32+
"TestApp\\": "tests/test_app/src/",
3233
"AttributeRegistry\\Test\\": "tests/"
3334
}
3435
},

docs/debug_kit_screenshot.png

422 KB
Loading

src/AttributeRegistryPlugin.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use Cake\Core\BasePlugin;
1313
use Cake\Core\Configure;
1414
use Cake\Core\ContainerInterface;
15+
use Cake\Core\Plugin;
1516
use Cake\Core\PluginApplicationInterface;
17+
use Cake\Routing\RouteBuilder;
1618

1719
/**
1820
* Plugin class for AttributeRegistry.
@@ -34,6 +36,7 @@ public function bootstrap(PluginApplicationInterface $app): void
3436
}
3537

3638
$this->registerCacheConfig();
39+
$this->registerDebugKitPanel();
3740
}
3841

3942
/**
@@ -57,6 +60,42 @@ private function registerCacheConfig(): void
5760
]);
5861
}
5962

63+
/**
64+
* Register the DebugKit panel if DebugKit is loaded.
65+
*/
66+
private function registerDebugKitPanel(): void
67+
{
68+
if (!Plugin::isLoaded('DebugKit')) {
69+
return;
70+
}
71+
72+
$panels = Configure::read('DebugKit.panels', []);
73+
$panels['AttributeRegistry.AttributeRegistry'] = true;
74+
Configure::write('DebugKit.panels', $panels);
75+
}
76+
77+
/**
78+
* @inheritDoc
79+
*/
80+
public function routes(RouteBuilder $routes): void
81+
{
82+
if (Plugin::isLoaded('DebugKit')) {
83+
$routes->plugin(
84+
'AttributeRegistry',
85+
['path' => '/attribute-registry'],
86+
function (RouteBuilder $builder): void {
87+
$builder->setExtensions(['json']);
88+
$builder->connect(
89+
'/debug-kit/discover',
90+
['controller' => 'DebugKit', 'action' => 'discover'],
91+
);
92+
},
93+
);
94+
}
95+
96+
parent::routes($routes);
97+
}
98+
6099
/**
61100
* @inheritDoc
62101
*/
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace AttributeRegistry\Controller;
5+
6+
use AttributeRegistry\AttributeRegistry;
7+
use AttributeRegistry\ValueObject\AttributeInfo;
8+
use Cake\Http\Response;
9+
use Cake\View\JsonView;
10+
use DebugKit\Controller\DebugKitController as BaseDebugKitController;
11+
12+
/**
13+
* Controller for DebugKit panel AJAX actions.
14+
*
15+
* Provides endpoints for re-discovering attributes from the panel.
16+
*/
17+
class DebugKitController extends BaseDebugKitController
18+
{
19+
/**
20+
* @inheritDoc
21+
*/
22+
public function initialize(): void
23+
{
24+
parent::initialize();
25+
26+
$this->viewBuilder()->setClassName(JsonView::class);
27+
}
28+
29+
/**
30+
* Re-discover all attributes.
31+
*
32+
* Clears the cache and performs a fresh discovery scan.
33+
*
34+
* @return \Cake\Http\Response|null JSON response with discovery results
35+
*/
36+
public function discover(): ?Response
37+
{
38+
$this->request->allowMethod(['POST']);
39+
40+
$registry = AttributeRegistry::getInstance();
41+
$registry->clearCache();
42+
43+
$attributes = $registry->discover();
44+
45+
$this->set([
46+
'success' => true,
47+
'count' => count($attributes),
48+
'attributes' => array_map(
49+
fn(AttributeInfo $attr): array => $attr->toArray(),
50+
$attributes,
51+
),
52+
]);
53+
$this->viewBuilder()
54+
->setOption('serialize', ['success', 'count', 'attributes']);
55+
56+
return null;
57+
}
58+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace AttributeRegistry\Panel;
5+
6+
use AttributeRegistry\AttributeRegistry;
7+
use Cake\Core\Configure;
8+
use DebugKit\DebugPanel;
9+
10+
/**
11+
* DebugKit panel for viewing discovered PHP attributes.
12+
*
13+
* Displays all attributes discovered by the AttributeRegistry plugin
14+
* with grouping by attribute class and target file.
15+
*/
16+
class AttributeRegistryPanel extends DebugPanel
17+
{
18+
/**
19+
* Plugin name for template resolution.
20+
*/
21+
public string $plugin = 'AttributeRegistry';
22+
23+
/**
24+
* Get data for panel display.
25+
*
26+
* @return array<string, mixed>
27+
*/
28+
public function data(): array
29+
{
30+
$registry = AttributeRegistry::getInstance();
31+
$attributes = $registry->discover();
32+
33+
return [
34+
'attributes' => $attributes,
35+
'count' => count($attributes),
36+
'groupedByAttribute' => $this->groupByAttribute($attributes),
37+
'groupedByTarget' => $this->groupByTarget($attributes),
38+
'config' => (array)Configure::read('AttributeRegistry'),
39+
];
40+
}
41+
42+
/**
43+
* Get summary text shown in the toolbar.
44+
*/
45+
public function summary(): string
46+
{
47+
return (string)count(AttributeRegistry::getInstance()->discover());
48+
}
49+
50+
/**
51+
* Get panel title.
52+
*/
53+
public function title(): string
54+
{
55+
return 'Attributes';
56+
}
57+
58+
/**
59+
* Group attributes by their attribute class name.
60+
*
61+
* @param array<\AttributeRegistry\ValueObject\AttributeInfo> $attributes Attributes to group
62+
* @return array<string, array<\AttributeRegistry\ValueObject\AttributeInfo>>
63+
*/
64+
private function groupByAttribute(array $attributes): array
65+
{
66+
$grouped = [];
67+
foreach ($attributes as $attribute) {
68+
$name = $attribute->attributeName;
69+
$grouped[$name] ??= [];
70+
$grouped[$name][] = $attribute;
71+
}
72+
73+
ksort($grouped);
74+
75+
return $grouped;
76+
}
77+
78+
/**
79+
* Group attributes by their target file.
80+
*
81+
* @param array<\AttributeRegistry\ValueObject\AttributeInfo> $attributes Attributes to group
82+
* @return array<string, array<\AttributeRegistry\ValueObject\AttributeInfo>>
83+
*/
84+
private function groupByTarget(array $attributes): array
85+
{
86+
$grouped = [];
87+
foreach ($attributes as $attribute) {
88+
$file = $attribute->filePath;
89+
$grouped[$file] ??= [];
90+
$grouped[$file][] = $attribute;
91+
}
92+
93+
ksort($grouped);
94+
95+
return $grouped;
96+
}
97+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Single attribute item element for DebugKit panel
4+
*
5+
* @var \AttributeRegistry\ValueObject\AttributeInfo $attr
6+
* @var bool $showFile
7+
*/
8+
9+
$showFile ??= false;
10+
$targetType = $attr->target->type->value;
11+
?>
12+
<div class="attribute-item" data-attribute="<?= h($attr->attributeName) ?>" data-class="<?= h($attr->className) ?>">
13+
<div>
14+
<span class="target-type target-type-<?= h($targetType) ?>"><?= h($targetType) ?></span>
15+
<strong><?= h($attr->attributeName) ?></strong>
16+
on <code><?= h($attr->className) ?><?= $attr->target->targetName ? '::' . h($attr->target->targetName) : '' ?></code>
17+
</div>
18+
<?php if ($showFile): ?>
19+
<div class="file-path"><?= h($attr->filePath) ?>:<?= $attr->lineNumber ?></div>
20+
<?php else: ?>
21+
<div class="file-path">Line <?= $attr->lineNumber ?></div>
22+
<?php endif; ?>
23+
<?php if (!empty($attr->arguments)): ?>
24+
<div class="arguments">
25+
<?php foreach ($attr->arguments as $key => $value): ?>
26+
<div><strong><?= h($key) ?>:</strong> <?= h(is_string($value) ? $value : json_encode($value)) ?></div>
27+
<?php endforeach; ?>
28+
</div>
29+
<?php endif; ?>
30+
</div>

0 commit comments

Comments
 (0)