Bridges ADP Kernel and API into Symfony. Second adapter after Yii 3 (Yii3); reference implementation for creating new adapters.
- Composer:
app-dev-panel/adapter-symfony - Namespace:
AppDevPanel\Adapter\Symfony\ - PHP: 8.4+
- Symfony: 6.4+ / 7.x / 8.x
- Dependencies:
app-dev-panel/kernel,app-dev-panel/api,app-dev-panel/cli,nyholm/psr7,nyholm/psr7-server,guzzlehttp/guzzle,guzzlehttp/psr7, Symfony components (config,dependency-injection,event-dispatcher,http-kernel,console,var-dumper) - Optional:
doctrine/dbal(database inspector),twig/twig,symfony/security-bundle,symfony/mailer,symfony/messenger
src/
├── AppDevPanelBundle.php # Symfony Bundle entry point, registers CompilerPass
├── DependencyInjection/
│ ├── AppDevPanelExtension.php # Loads config, registers services, collectors, API
│ ├── CollectorProxyCompilerPass.php # Decorates PSR services, builds Debugger, collects params
│ └── Configuration.php # Bundle config tree (app_dev_panel.yaml)
├── EventSubscriber/
│ ├── HttpSubscriber.php # kernel.request/response/exception/terminate → Debugger
│ ├── ConsoleSubscriber.php # console.command/error/terminate → Debugger
│ ├── AuthorizationSubscriber.php # Symfony Security events → AuthorizationCollector
│ └── CorsSubscriber.php # CORS headers for debug API responses
├── Proxy/
│ ├── SymfonyEventDispatcherProxy.php # Wraps event_dispatcher, implements Component interface
│ └── SymfonyTranslatorProxy.php # Wraps TranslatorInterface, feeds TranslatorCollector
├── Collector/
│ ├── TwigCollector.php # Twig template renders, timing (feeds Kernel TemplateCollector)
│ ├── CacheCollector.php # Cache hits/misses, operations
│ ├── QueueCollector.php # Queue/message bus operations (Symfony Messenger)
│ ├── SymfonyRequestCollector.php # Legacy: Symfony HttpFoundation collector (unused)
│ └── SymfonyExceptionCollector.php # Legacy: Symfony exception collector (unused)
├── Inspector/
│ ├── SymfonyConfigProvider.php # 'config' alias: params, events, services, bundles
│ ├── SymfonyAuthorizationConfigProvider.php # Live auth config: firewalls, role hierarchy, voters (when security-bundle available)
│ ├── DoctrineSchemaProvider.php # Database schema via DBAL (when doctrine available)
│ ├── NullSchemaProvider.php # Fallback when no database configured
│ ├── SymfonyRouteCollectionAdapter.php # Route inspection adapter
│ ├── SymfonyRouteAdapter.php # Single route adapter
│ ├── SymfonyUrlMatcherAdapter.php # URL matching adapter
│ └── SymfonyMatchResult.php # Match result DTO
├── Controller/
│ └── AdpApiController.php # Symfony controller bridging to ADP ApiApplication
└── Command/
└── AssetsInstallCommand.php # `app-dev-panel:assets:install` — copies/symlinks FrontendAssets into public/bundles/appdevpanel/ for the web server
tests/
├── Integration/
│ ├── BundleBootstrapTest.php # Full container compilation + lifecycle
│ ├── LoggerProxyIntegrationTest.php # Logger/event proxy wiring in compiled container
│ └── ConsoleProcessIntegrationTest.php # Runs bin/console, verifies storage output
└── Unit/
├── DependencyInjection/ # Extension, CompilerPass, Configuration tests
├── EventSubscriber/ # HttpSubscriber, ConsoleSubscriber tests
├── Collector/ # Symfony-specific collector tests
├── Inspector/ # ConfigProvider, SchemaProvider, Route tests
└── Proxy/ # SymfonyEventDispatcherProxy tests
AppDevPanel\Adapter\Symfony\AppDevPanelBundle::class => ['dev' => true, 'test' => true],Configuration defines the config tree. AppDevPanelExtension processes it and registers all services.
Registers in order:
- Core services:
DebuggerIdGenerator,StorageInterface(FileStorage),TimelineCollector - All enabled collectors (tagged
app_dev_panel.collector) - Event subscribers:
HttpSubscriber,ConsoleSubscriber,CorsSubscriber - API services: middleware stack, controllers, inspector endpoints
- Inspector:
SymfonyConfigProviderasconfigalias,DoctrineSchemaProviderorNullSchemaProvider - Bridge:
AdpApiControllermaps Symfony routing toApiApplication - Console:
AssetsInstallCommand(app-dev-panel:assets:install) taggedconsole.command— copies/symlinksFrontendAssets::path()intopublic/bundles/appdevpanel/so the web server (nginx/Apache) serves the panel + toolbar bundle directly
Static files are served by the web server, not by PHP. AppDevPanelExtension::load() picks panel.static_url in this order when the user leaves it empty:
<projectDir>/public/bundles/appdevpanel/bundle.jsexists (afterbin/console app-dev-panel:assets:install) →/bundles/appdevpanelResources/public/bundle.jsexists (legacymake build-panelflow before Symfony'sassets:install) →/bundles/appdevpanelPanelConfig::DEFAULT_STATIC_URL(GitHub Pages CDN) — fallback when nothing has been published yet
Runs after all extensions. Responsibilities:
- Collects all
app_dev_panel.collectortagged services - Builds
Debuggerwith all collector references +StorageInterface - Decorates
loggerorLoggerInterface→LoggerInterfaceProxy(checksloggerfirst, then FQCN) - Decorates
event_dispatcher→SymfonyEventDispatcherProxy(implementsSymfony\Component\EventDispatcher\EventDispatcherInterface) - Decorates
ClientInterface(PSR-18) →HttpClientInterfaceProxy - Decorates
translator→SymfonyTranslatorProxy(feedsTranslatorCollector) - Collects all container parameters (excluding
app_dev_panel.*) → passes toInspectControllerandSymfonyConfigProvider
Web events (HttpSubscriber):
| Symfony Event | Priority | ADP Action |
|---|---|---|
kernel.request |
1024 | Convert Symfony Request → PSR-7, Debugger::startup(), RequestCollector, WebAppInfoCollector |
kernel.response |
-1024 | Convert Symfony Response → PSR-7, RequestCollector captures response, adds X-Debug-Id header |
kernel.exception |
0 | ExceptionCollector captures throwable |
kernel.terminate |
-2048 | Debugger::shutdown() flushes data to storage |
Console events (ConsoleSubscriber):
| Symfony Event | Priority | ADP Action |
|---|---|---|
console.command |
1024 | Debugger::startup(), CommandCollector, ConsoleAppInfoCollector |
console.error |
0 | ExceptionCollector, CommandCollector |
console.terminate |
-2048 | CommandCollector, Debugger::shutdown() |
Security events (AuthorizationSubscriber, requires symfony/security-http):
| Symfony Event | ADP Action |
|---|---|
LoginSuccessEvent |
AuthorizationCollector: user identity, roles, firewall, token, impersonation (if SwitchUserToken) |
LoginFailureEvent |
AuthorizationCollector: failed auth event with exception details |
LogoutEvent |
AuthorizationCollector: logout auth event |
SwitchUserEvent |
AuthorizationCollector: impersonation data + switch_user auth event |
VoteEvent |
AuthorizationCollector: access decisions with voter results |
Conditionally registered only when symfony/security-http is installed and the security collector is enabled.
Logger: Uses Symfony setDecoratedService(). Checks for logger service ID first (Symfony canonical), falls back to Psr\Log\LoggerInterface FQCN. Wraps with Kernel's LoggerInterfaceProxy.
Event dispatcher: Decorates event_dispatcher service with SymfonyEventDispatcherProxy. Must implement Symfony\Component\EventDispatcher\EventDispatcherInterface (not PSR-14) because:
- Symfony's
dispatch()has a second?string $eventNameparameter SymfonyConfigProviderchecksinstanceof EventDispatcherInterfaceto callgetListeners()- Symfony container passes lazy subscriber arrays
[$service, 'method']toaddListener()— requirescallable|arraysignature
HTTP client: Standard PSR-18 decoration via HttpClientInterfaceProxy.
SymfonyConfigProvider is registered as the config service alias:
| Group | Method | Source |
|---|---|---|
params / parameters |
Returns $containerParameters |
Collected by compiler pass |
events / events-web |
Calls $dispatcher->getListeners() |
Introspects event_dispatcher |
di / services |
Calls $container->getServiceIds() |
Lists all DI services |
bundles |
Returns $bundleConfig |
Bundle configuration |
InspectController receives container parameters as 3rd constructor argument for the /inspect/api/params endpoint.
DoctrineSchemaProvider implements SchemaProviderInterface for /inspect/api/table endpoints when doctrine.dbal.default_connection is available. Falls back to NullSchemaProvider.
SymfonyAuthorizationConfigProvider implements AuthorizationConfigProviderInterface for /inspect/api/authorization. Registered by CollectorProxyCompilerPass::upgradeAuthorizationProvider() when security.firewalls is present (SecurityBundle installed); otherwise NullAuthorizationConfigProvider remains in effect.
| Section | Source |
|---|---|
guards |
Container parameter security.firewalls (list of firewall names) + all security.firewall.map.config.{name}.* params merged into each firewall's config |
roleHierarchy |
Container parameter security.role_hierarchy.roles |
voters |
Services tagged security.voter (injected via TaggedIteratorArgument) |
config |
security.access_control, security.access.decision_manager.strategy, security.user.provider.concrete.*, firewall names list |
| Collector | Proxy | Data |
|---|---|---|
RequestCollector |
— (fed by HttpSubscriber) |
PSR-7 request/response |
LogCollector |
LoggerInterfaceProxy |
PSR-3 log entries |
EventCollector |
SymfonyEventDispatcherProxy |
Dispatched events with timing |
ExceptionCollector |
— (fed by HttpSubscriber/ConsoleSubscriber) |
Uncaught exceptions |
HttpClientCollector |
HttpClientInterfaceProxy |
PSR-18 HTTP requests/responses |
ServiceCollector |
— | Service method calls |
VarDumperCollector |
— | dump() calls |
TimelineCollector |
— | Cross-collector timing data |
FilesystemStreamCollector |
— | File I/O operations |
HttpStreamCollector |
— | Stream HTTP operations |
CommandCollector |
— (fed by ConsoleSubscriber) |
Console commands |
WebAppInfoCollector |
— (fed by HttpSubscriber) |
Web app metadata |
ConsoleAppInfoCollector |
— (fed by ConsoleSubscriber) |
Console app metadata |
| Collector | Fed by | Data |
|---|---|---|
DatabaseCollector (Kernel) |
DBAL middleware calling logQuery() |
SQL queries, params, time, backtrace |
TwigCollector |
Profiler extension calling logRender() |
Template names, render times |
AuthorizationCollector (Kernel) |
AuthorizationSubscriber listening to Symfony Security events |
User, roles, firewall, token, impersonation, auth events, access decisions with voters |
CacheCollector |
Decorated cache adapter calling logCacheOperation() |
Cache operations, hits/misses |
MailerCollector (Kernel) |
Mailer MessageEvent listener calling collectMessage() |
Emails sent |
QueueCollector (Kernel) |
Messenger middleware calling logMessage() |
Messages dispatched/handled/failed |
TranslatorCollector (Kernel) |
SymfonyTranslatorProxy decorating TranslatorInterface |
Translation lookups, missing translations |
RedisCollector (Kernel) |
Predis plugin or phpredis decorator calling logCommand() |
Redis commands, timing, errors |
app_dev_panel:
enabled: true # Master switch
storage:
path: '%kernel.project_dir%/var/debug'
history_size: 50
collectors:
request: true # RequestCollector (Kernel, PSR-7)
exception: true # ExceptionCollector (Kernel)
log: true # LogCollector (Kernel)
event: true # EventCollector (Kernel)
service: true # ServiceCollector
http_client: true # HttpClientCollector
timeline: true # TimelineCollector
var_dumper: true # VarDumperCollector
filesystem_stream: true # FilesystemStreamCollector
http_stream: true # HttpStreamCollector
command: true # CommandCollector
doctrine: true # DatabaseCollector (requires doctrine/dbal)
twig: true # TwigCollector (requires twig/twig)
security: true # AuthorizationCollector (requires symfony/security-bundle)
cache: true # CacheCollector
mailer: true # MailerCollector from Kernel (requires symfony/mailer)
queue: true # QueueCollector from Kernel (requires symfony/messenger)
translator: true # TranslatorCollector (Kernel, via SymfonyTranslatorProxy)
code_coverage: false # CodeCoverageCollector (opt-in, requires pcov or xdebug)
ignored_requests:
- '/_wdt/*'
- '/_profiler/*'
- '/debug/api/**'
ignored_commands:
- 'completion'
- 'help'
- 'list'
- 'debug:*'
- 'cache:*'
dumper:
excluded_classes: []
api:
enabled: true
allowed_ips: ['127.0.0.1', '::1']
auth_token: ''| Aspect | Yii 3 Adapter | Symfony Adapter |
|---|---|---|
| Registration | Config plugin auto-load | Bundle + Extension |
| DI wiring | di.php + di-providers.php |
AppDevPanelExtension + CompilerPass |
| Proxy decoration | Service provider | setDecoratedService() in compiler pass |
| Event proxy | Kernel's EventDispatcherInterfaceProxy (PSR-14) |
SymfonyEventDispatcherProxy (Symfony Component) |
| Event mapping | events-web.php / events-console.php |
EventSubscriberInterface classes |
| Config | params.php (flat array) |
Configuration tree builder |
| Request collector | Kernel's RequestCollector (native PSR-7) |
Kernel's RequestCollector (converted from HttpFoundation) |
| PSR-7 bridge | Native (Yii uses PSR-7) | nyholm/psr7-server conversion for request + response |
| Inspector config | Application config service | SymfonyConfigProvider as config alias |
| Database inspector | DbSchemaProvider in Yii3 adapter |
DoctrineSchemaProvider (DBAL) or NullSchemaProvider |
Use this Symfony adapter as a reference. A new adapter must:
- Register the Debugger with all collectors and storage
- Wire event lifecycle: map framework's request/command start/end events to
Debugger::startup()/shutdown() - Decorate PSR services with Kernel proxies:
LoggerInterfaceProxy,HttpClientInterfaceProxy, and a framework-specific event dispatcher proxy if needed - Convert to PSR-7: if framework doesn't use PSR-7 natively, convert request/response for
RequestCollectorandStartupContext::forRequest() - Register inspector services:
SymfonyConfigProvider-equivalent asconfigalias,SchemaProviderInterfacefor database - Pass container parameters to
InspectController(3rd constructor arg) - Bridge API: route
/debug/api/*and/inspect/api/*toApiApplication