martinkup / symfony-profiler-optimization-advisor-bundle
Symfony Profiler bundle that analyzes per-request signals and produces scored, actionable optimization opportunities with ready-made AI agent prompts.
Package info
github.com/martinkup/symfony-profiler-optimization-advisor-bundle
Type:symfony-bundle
pkg:composer/martinkup/symfony-profiler-optimization-advisor-bundle
Fund package maintenance!
Requires
- php: >=8.3
- symfony/framework-bundle: ^7.2|^8.0
- symfony/twig-bundle: ^7.2|^8.0
Requires (Dev)
- doctrine/dbal: ^4.0
- doctrine/sql-formatter: ^1.1
- php-parallel-lint/php-parallel-lint: ^1.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.0
- roave/security-advisories: dev-latest
- slevomat/coding-standard: ^8.4
- squizlabs/php_codesniffer: ^4.0
- symfony/ai-symfony-mate-extension: ^0.5
- symfony/cache: ^7.2|^8.0
- symfony/doctrine-bridge: ^7.2|^8.0
- symfony/event-dispatcher: ^7.2|^8.0
- symfony/http-client: ^7.2|^8.0
- symfony/messenger: ^7.2|^8.0
- symfony/stopwatch: ^7.2|^8.0
- symfony/web-profiler-bundle: ^7.2|^8.0
Suggests
- doctrine/sql-formatter: Required for SQL syntax highlighting in the Database tab
- symfony/ai-symfony-mate-extension: Required for AI Mate MCP integration (optimization tools and collector formatter)
- symfony/doctrine-bridge: Required for Database query analysis
- symfony/http-client: Required for HTTP client call analysis
- symfony/messenger: Required for automatic synchronous Messenger handler analysis
- symfony/stopwatch: Required for Stopwatch performance breakdown
README
Detect N+1 queries, slow SQL, low cache hit rates, hot Twig templates, and 10 more performance anti-patterns — automatically, on every request. Each finding is scored by impact, effort, and confidence, with a ready-to-use AI prompt for guided fixes. Queryable directly from Claude Code and other MCP-compatible AI agents via AI Mate integration.
A meta data collector for Symfony Profiler — it reads data from other profiler collectors (Doctrine, Cache, Twig, Events, HTTP Client, Messenger, Stopwatch), analyzes per-request signals across 7 categories, and produces scored, actionable optimization opportunities with impact/effort/confidence ratings, ROI ranking, and ready-to-use AI agent prompts.
Table of Contents
- Features
- Why This Bundle?
- How It Works
- Requirements
- Installation
- Configuration
- Signal Categories
- Detected Opportunity Codes
- Opportunity Scoring Model
- Origin Classification
- Optional Integrations
- AI Mate Integration
- Architecture
- Testing
- Documentation
- Contributing
- License
Features
- 7 signal categories — Database, Cache, Twig, Events, HTTP Client, Messenger, and Stopwatch
- 14 detection rules — N+1 queries, duplicate queries, slow templates, low cache hit rates, heavy sync handlers, and more
- Scored opportunities — Each finding carries impact (1-5), effort (1-5), confidence (1-5), ROI, and risk level
- AI agent prompts — Every opportunity includes a ready-to-use prompt for AI coding assistants
- MCP tool — Queryable directly from Claude Code, Cursor, and other MCP-compatible AI agents via AI Mate
- Origin classification — Separates your app code signals from framework/infrastructure noise
- Zero configuration — Works out of the box with sensible defaults; every threshold is customizable
- Late collector — Runs at priority
-100to ensure all other profiler collectors have finished first
Why This Bundle?
Unlike generic APM tools, this bundle:
- Runs inside the Symfony Profiler — no external service needed
- Analyzes 7 signal categories in a single per-request pass
- Scores each finding (impact/effort/confidence/ROI) instead of just listing warnings
- Generates AI-ready prompts for every opportunity
- Directly queryable from Claude Code, Cursor, and other MCP-compatible agents via AI Mate
- Classifies signals by origin (app vs. framework noise)
- Works with zero configuration out of the box
How It Works
- The
OptimizationAdvisorDataCollectorruns as a late data collector (priority-100) after all other Symfony profiler collectors have completed. - Seven specialized analyzers process raw profiling data from Doctrine, Cache, Twig Profiler, EventDispatcher, HttpClient, Messenger, and Stopwatch.
- Each analyzer classifies signals by origin (app, infrastructure, or profiler) so you only see findings relevant to your code.
- The AdvisorEngine evaluates all signals against configurable thresholds and produces scored optimization opportunities.
- Opportunities are deduplicated by fingerprint, sorted by ROI descending, and capped at
max_items. - The profiler panel renders a rich dashboard with summary scores, quick wins, high-impact findings, and detailed signal breakdowns.
- Every opportunity includes a pre-filled AI agent prompt — copy it into your AI coding assistant for guided, step-by-step implementation.
Requirements
| Dependency | Version |
|---|---|
| PHP | >= 8.3 |
| Symfony | 7.2+ or 8.0+ |
Installation
composer require --dev martinkup/symfony-profiler-optimization-advisor-bundle
If you use Symfony Flex, the bundle is registered automatically. Otherwise, add it manually:
// config/bundles.php return [ // ... MartinKup\OptimizationAdvisorBundle\OptimizationAdvisorBundle::class => ['dev' => true, 'test' => true], ];
Note: Register the bundle only in
devandtestenvironments. It depends on debug services (twig.profile,data_collector.cache) that are not available in production. Optional services (doctrine.debug_data_holder,data_collector.http_client,debug.stopwatch) are wired withnullOnInvalid()and degrade gracefully when absent.
Configuration
All options have sensible defaults. No configuration file is needed to get started.
To customize, create config/packages/optimization_advisor.yaml:
optimization_advisor: # Detection thresholds thresholds: slow_query_ms: 30.0 # Query groups slower than this trigger PG_SLOW_QUERY_GROUP n_plus_one_count: 10 # Minimum identical fast SELECTs to suspect N+1 slow_listener_ms: 10.0 # Event listeners slower than this trigger EVENTS_SLOW_LISTENER max_items: 200 # Maximum number of opportunities reported per request # Your application's root namespace prefix (used to classify signals as "app" vs "infra") app_namespace_prefix: 'App\' # Database tables considered infrastructure (excluded from app signal analysis) infra_db_tables: - doctrine_migration_versions # Cache pool name prefixes classified as "app" pools app_cache_pool_prefixes: - cache.app - cache.doctrine.result - cache.doctrine.orm # Cache pool name prefixes classified as "profiler" pools (excluded from totals) profiler_cache_pool_prefixes: - cache.profiler # Twig template name prefixes classified as "profiler" templates profiler_template_prefixes: - '@WebProfiler/' # Event listener namespace prefixes classified as "profiler" listeners profiler_event_namespace_prefixes: - 'Symfony\Bundle\WebProfilerBundle\' # Specific listener classes classified as "profiler" profiler_event_classes: - 'Symfony\Component\HttpKernel\EventListener\ProfilerListener' # Security redaction for AI Mate / MCP output (enabled by default) redact_sensitive_data: true # Parameter key substrings that trigger redaction (case-insensitive substring match) sensitive_param_patterns: - email - password - passwd - token - secret - auth - credential - phone - address - ssn - card - iban # Regex patterns matched against parameter values (case-insensitive) sensitive_value_patterns: - '[^@\s]+@[^@\s]+\.[^@\s]+' # email addresses # Query parameter names redacted from URIs (case-insensitive exact match) sensitive_query_params: - token - api_key - apikey - secret - password - auth - access_token - refresh_token - session_id - _token - email - session - cookie
Security Redaction
When AI Mate integration is active, all profiler data passes through SecurityRedactor before being exposed via MCP channels (both the collector formatter and the MCP tool apply redaction for defense-in-depth). The redactor uses pattern-based matching to selectively redact sensitive values while preserving non-sensitive context (pagination, sorting, IDs).
Fail-closed regex behavior: Invalid regex patterns in sensitive_value_patterns cause values to be redacted rather than skipped. This ensures misconfigured patterns cannot leak sensitive data.
What gets redacted:
| Data path | Redaction rule |
|---|---|
origin.uri path segments |
Segments matching a sensitive_value_patterns regex (e.g., email in path) |
origin.uri query params |
Params matching sensitive_query_params (exact), sensitive_param_patterns (key substring), or sensitive_value_patterns (value regex) |
signals.db.query_groups[].example_sql |
Always replaced with normalized pattern (parameterized SQL) |
signals.db.query_groups[].example_params |
Values where the key contains a sensitive_param_patterns substring, or the value matches a sensitive_value_patterns regex |
Example:
# Input
origin.uri: /api/users/john@example.com/profile?token=abc123&page=2&sort=name
example_params: {email: 'admin@ex.com', 0: 'admin@ex.com', 1: 42, name: 'John'}
example_sql: "SELECT * FROM users WHERE email = 'admin@example.com'"
# Output (with default patterns)
origin.uri: /api/users/***REDACTED***/profile?token=***REDACTED***&page=2&sort=name
example_params: {email: ***REDACTED***, 0: ***REDACTED***, 1: 42, name: 'John'}
example_sql: "SELECT * FROM users WHERE email = ?" ← pattern
emailkey → redacted bysensitive_param_patterns(substring match)0: 'admin@ex.com'→ redacted bysensitive_value_patterns(email regex)1: 42→ preserved (no pattern match)name: 'John'→ preserved (no pattern match)tokenquery param → redacted bysensitive_query_params(exact match)page,sortquery params → preserved
To add custom patterns (e.g. for SSN or national ID numbers):
optimization_advisor: sensitive_param_patterns: - email - password - token - ssn - national_id sensitive_value_patterns: - '[^@\s]+@[^@\s]+\.[^@\s]+' # email addresses - '\d{3}-\d{2}-\d{4}' # US SSN format sensitive_query_params: - token - api_key - secret - session_id
Note: Setting any of these arrays replaces the defaults entirely. Include all patterns you need.
In query strings, redacted values are URL-encoded (
%2A%2A%2AREDACTED%2A%2A%2A). The examples above show the decoded form for readability.
Configuration Reference
Thresholds
| Option | Type | Default | Description |
|---|---|---|---|
thresholds.slow_query_ms |
float |
30.0 |
Min ms for slow query group |
thresholds.n_plus_one_count |
int |
10 |
Min identical SELECTs for N+1 |
thresholds.slow_listener_ms |
float |
10.0 |
Min ms for slow listener |
thresholds.max_items |
int |
200 |
Max opportunities per request |
Origin classification
| Option | Type | Default | Description |
|---|---|---|---|
app_namespace_prefix |
string |
App\ |
Root namespace for "app" origin |
infra_db_tables |
string[] |
[doctrine_migration_versions] |
Infrastructure DB tables |
app_cache_pool_prefixes |
string[] |
[cache.app, ...] |
App cache pool prefixes |
profiler_cache_pool_prefixes |
string[] |
[cache.profiler] |
Profiler cache pool prefixes |
profiler_template_prefixes |
string[] |
[@WebProfiler/] |
Profiler Twig template prefixes |
profiler_event_namespace_prefixes |
string[] |
[...WebProfilerBundle\] |
Profiler listener namespaces |
profiler_event_classes |
string[] |
[...\ProfilerListener] |
Profiler listener classes |
Security redaction (AI Mate / MCP output)
| Option | Type | Default | Description |
|---|---|---|---|
redact_sensitive_data |
bool |
true |
Enable redaction |
sensitive_param_patterns |
string[] |
[email, password, ...] |
Param key substrings to redact |
sensitive_value_patterns |
string[] |
[email regex] |
Regex patterns on param values |
sensitive_query_params |
string[] |
[token, api_key, ...] |
URI query params to redact |
Signal Categories
Database (DatabaseAnalyzer)
Analyzes Doctrine DBAL debug data. Groups queries by SQL fingerprint (normalized pattern), classifies origin, and computes per-group metrics.
Collected metrics: query count, total/max/min/avg ms, query kind (SELECT/INSERT/UPDATE/DELETE), tables, connection name, fingerprint, app vs. infrastructure split.
Cache (CacheAnalyzer)
Analyzes Symfony Cache pool statistics. Computes per-pool hit rates, read/write/delete counts, and classifies pools by origin.
Collected metrics: calls, reads, writes, deletes, hits, misses, hit rate, total ms, app/infra/profiler split.
Twig (TwigAnalyzer)
Walks the Twig Profile tree and aggregates per-template render counts and durations.
Collected metrics: render count, total/max ms per template, app/infra/profiler render split.
Events (EventAnalyzer)
Analyzes called and not-called event listeners from TraceableEventDispatcher. Groups by event + listener class.
Collected metrics: call count, total/max/avg ms per listener, unique events, unique listeners, not-called count, app/infra/profiler split.
HTTP Client (HttpClientAnalyzer)
Analyzes HttpClientDataCollector traces. Groups by endpoint fingerprint (method + host + path pattern with numeric segments replaced by {id}).
Collected metrics: call count, total/max ms, status code buckets (2xx/3xx/4xx/5xx) per endpoint.
Messenger (OtherSignalsAnalyzer)
Analyzes synchronous Messenger handler executions from TraceRegistry records.
Collected metrics: handler class, message name, duration ms, app/infra split, total sync ms.
Stopwatch (PerformanceAnalyzer)
Analyzes Symfony Stopwatch section events for a performance timeline breakdown.
Collected metrics: event name, category, duration, memory, start/end time, period count, percent of total request duration, top 3 events.
Detected Opportunity Codes
| Code | Category | Trigger | Default Threshold |
|---|---|---|---|
PG_SLOW_QUERY_GROUP |
DB | Query group total time exceeds threshold | 30 ms |
PG_N_PLUS_ONE_SUSPECTED |
DB | Many identical fast SELECTs (avg < 5 ms) | 10 queries |
PG_DUPLICATE_QUERY |
DB | Same query pattern executed multiple times | 3 executions |
PG_LARGE_RESULTSET |
DB | Single slow SELECT (> 100 ms) with low repetition | 100 ms, <= 3 times |
CACHE_CANDIDATE_DB_RESULTS |
Cache | Repeated identical SELECTs suitable for caching | 3 executions |
CACHE_LOW_HITRATE_POOL |
Cache | Cache pool with low hit rate and significant volume | < 50% hit rate, >= 10 calls |
DOCTRINE_2LC_OPPORTUNITY |
DB | Many fast identical SELECTs suitable for 2nd-level cache | 5 queries, avg < 2 ms |
TWIG_HOT_TEMPLATE |
Twig | Template with high cumulative render time | 50 ms total |
TWIG_DUP_RENDER |
Twig | Template rendered many times per request | 10 renders |
EVENTS_TOO_MANY_LISTENERS |
Events | Listener invoked excessively in a single request | > 50 calls |
EVENTS_SLOW_LISTENER |
Events | Event listener with high max execution time | 10 ms |
HTTP_SLOW_ENDPOINT |
HTTP | External HTTP call with high latency | 500 ms |
HTTP_DUP_CALL |
HTTP | Same external endpoint called multiple times | 2 calls |
MESSENGER_SYNC_HEAVY |
Messenger | Synchronous Messenger handler with high duration | 50 ms |
Opportunity Scoring Model
Each detected opportunity is scored on three dimensions (1-5 scale):
| Dimension | Meaning |
|---|---|
| Impact | How much performance improvement is expected |
| Effort | How much work is required to implement the fix |
| Confidence | How certain the detection is (higher = fewer false positives) |
Derived metrics:
- ROI =
impact * confidence / effort— used for sorting (highest ROI first) - Quick Win =
effort <= 2 AND confidence >= 4 - High Impact =
impact >= 4 - Risk =
low,med, orhigh— safety risk of implementing the recommendation - Fingerprint =
md5(code + evidence)— used for deduplication
Optimization Score
A 0-100 score summarizing the overall request health. Starts at 100 and deducts impact * confidence * 0.5 per opportunity. Lower scores indicate more optimization potential.
AI Agent Prompt
Every opportunity includes a structured ai_prompt field containing:
- Problem description
- Evidence references
- Numbered recommended actions
- Safety notes
This prompt can be copied directly into an AI coding assistant (Claude, ChatGPT, Copilot) for guided implementation.
Origin Classification
All signals are classified into three origins to separate your application code from framework noise:
| Origin | Meaning | Example |
|---|---|---|
| app | Your application code | Queries on users table, App\Listener\... listeners |
| infra | Framework/library infrastructure | Queries on doctrine_migration_versions, pg_catalog |
| profiler | Profiler overhead itself | cache.profiler pool, @WebProfiler/ templates |
Only app signals are evaluated for opportunities. Infrastructure and profiler signals are collected for informational display but do not generate recommendations.
The app_namespace_prefix configuration option controls how event listeners and Messenger handlers are classified. Set it to your project's root namespace (e.g., Acme\ for a project using Acme\App\... namespaces).
Optional Integrations
The bundle gracefully degrades when optional components are not installed:
| Package | What it enables | Constructor behavior |
|---|---|---|
symfony/doctrine-bridge |
Database query analysis | ?DebugDataHolder via nullOnInvalid() — panel shows empty DB section |
doctrine/sql-formatter |
SQL syntax highlighting in Database tab | Falls back to SqlFormatterFallbackExtension with basic formatting |
symfony/http-client |
HTTP Client call analysis | ?HttpClientDataCollector via nullOnInvalid() — panel shows empty HTTP section |
symfony/stopwatch |
Stopwatch performance breakdown | ?Stopwatch via nullOnInvalid() — panel shows empty performance section |
symfony/messenger |
Synchronous handler analysis | MessageTracingMiddleware auto-registered via CompilerPass; removed when absent |
symfony/ai-symfony-mate-extension |
AI Mate MCP tool + collector formatter | Enables optimization-advisor-opportunities MCP tool and profiler resource URI |
Messenger Integration
When symfony/messenger is installed, the bundle automatically registers its
MessageTracingMiddleware on all configured Messenger buses via a CompilerPass.
The middleware is inserted right after Symfony's traceable middleware (in debug mode)
or as the outermost wrapper in the chain for accurate timing.
This approach works reliably even when the host project defines environment-specific
middleware overrides (e.g., when@dev sections in messenger.yaml).
No manual configuration required — the middleware is auto-registered for every bus
tagged with messenger.bus regardless of how middleware lists are defined in configuration files.
The middleware populates the bundle's TraceRegistry with per-handler trace records:
is_handled_sync— whether the message was handled synchronously (HandledStamppresent)handler_class— FQCN of the handler (fromHandledStamp::getHandlerName())handler_duration_ms— wall-clock handler execution timemessage_short— short class name of the dispatched message
When symfony/messenger is not installed, the middleware definition is conditionally removed and the "Other" tab shows an empty Messenger section.
AI Mate Integration
When symfony/ai-symfony-mate-extension is installed, the bundle registers as an AI Mate MCP extension, exposing optimization data to AI assistants via two channels:
MCP Tool: optimization-advisor-opportunities
A dedicated tool that returns scored optimization opportunities with filtering support:
optimization-advisor-opportunities(
token?: string, # Profiler token (null = latest request)
category?: string, # Filter: db, cache, twig, events, http, messenger
type?: string, # Filter: quick_wins, high_impact, risky
limit?: int = 20 # Max opportunities returned
)
Each opportunity includes ai_prompt, recommended_actions, evidence_refs, and safety_notes — ready for AI-guided implementation.
Profiler Resource URI
The collector formatter makes optimization data available at:
symfony-profiler://profile/{token}/optimization_advisor
This returns the full collector output: origin, opportunities, summary, and signals with sanitized data objects.
Setup
- Install the extension as a dev dependency:
composer require --dev symfony/ai-symfony-mate-extension
- Discover the extension:
bin/mate discover
- Use the tool from any MCP-compatible AI assistant (e.g., Claude Code):
Use AI Mate to analyze optimization opportunities for
https://app.local/apple-iphone-18-pro and suggest their implementation.
The agent will automatically query the profiler for the matching request, evaluate all detected opportunities, and propose concrete code changes.
No additional configuration is required. The bundle's composer.json contains the extra.ai-mate section for automatic discovery.
Architecture
Key components: DataCollector (late collector, priority -100) → 7 Analyzers (Database, Cache, Twig, Events, HTTP Client, Messenger, Stopwatch) → AdvisorEngine (14 detection rules, scoring, deduplication). See the full component catalog for details.
Data Flow
Request → Symfony Profiler Collectors (Doctrine, Cache, Twig, Events, ...)
↓
OptimizationAdvisorDataCollector::lateCollect()
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
DatabaseAnalyzer CacheAnalyzer TwigAnalyzer ... (7 analyzers)
↓ ↓ ↓
└───────────────┼───────────────┘
↓
AdvisorEngine::evaluate()
↓
Scored Opportunities (ROI-sorted)
↓
Profiler Panel (Twig template)
Documentation
Full documentation is available in the docs/ directory.
| Document | Description |
|---|---|
| Architecture Overview | Data flow, request lifecycle, origin classification, scoring model |
| Component Catalog | All 26 source classes with API reference and diagrams |
| Configuration Reference | All 16 parameters with types, defaults, and examples |
| Integration Guide | Installation, optional dependencies, extension points |
| ADRs | Architectural decision records (late collector, origin classification, messenger pass, security redaction) |
Testing & Development
# Install dependencies composer install # Run all quality gates (lint → fix → cs → stan → test) composer check
All available commands via composer run --list:
| Command | Description |
|---|---|
composer cs |
Check coding standards (PHP_CodeSniffer + Slevomat) |
composer fix |
Auto-fix coding standard violations |
composer stan |
Run PHPStan static analysis (level max) |
composer test |
Run PHPUnit test suite |
composer test:filter -- ClassName |
Run filtered tests (e.g. composer test:filter -- DatabaseAnalyzer) |
composer test:unit |
Run only unit tests |
composer test:coverage |
Run tests with text coverage output (requires pcov) |
composer test:coverage-html |
Generate HTML coverage report in coverage/ |
composer check |
Run full QA pipeline: lint → fix → cs → stan → test |
Contributing
See CONTRIBUTING.md for guidelines.
License
This bundle is released under the MIT License.
