ahmed-bhs/doctrine-doctor
Doctrine Doctor is a runtime analysis tool for Doctrine ORM integrated into the Symfony Web Profiler. It detects real-world issues like N+1 queries, slow queries, missing indexes, hydration overhead, and injection risks, with actionable backtraces and suggestions.
Installation:
composer require --dev ahmed-bhs/doctrine-doctor
Enable Backtraces (recommended for debugging):
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
profiling_collect_backtrace: true
First Use Case:
dev environment).Development Phase:
Code Review:
$users = $repository->findAll();
with:
$users = $repository
->createQueryBuilder('u')
->leftJoin('u.profile', 'p')
->addSelect('p')
->getResult();
Team Collaboration:
n_plus_one detection) via config/packages/dev/doctrine_doctor.yaml.doctrine_doctor:
analyzers:
n_plus_one:
threshold: 2 # Detect even minor N+1 issues
slow_query:
threshold: 50 # Flag queries over 50ms
Pre-Deployment Checklist:
prod environment).Custom Analyzers: Extend the package by creating custom analyzers. Refer to the Architecture Guide for details on how to hook into the analysis pipeline.
CI/CD Pipeline: Add a static analysis step (e.g., PHPStan) to catch issues early, then use Doctrine Doctor for runtime validation in your dev/staging environments.
Symfony Events:
Listen to kernel.request or kernel.controller events to trigger analysis for specific routes:
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
$eventDispatcher->addListener(KernelEvents::REQUEST, function (RequestEvent $event) {
if ($event->isMainRequest() && $event->getRequest()->getPathInfo() === '/admin') {
// Force analysis for admin routes
$this->doctrineDoctor->analyze();
}
});
Performance Overhead:
slow_query.threshold).Missing Backtraces:
profiling_collect_backtrace is enabled in Doctrine DBAL config.php bin/console debug:config doctrine
False Positives:
missing_index) may flag queries that are intentionally optimized for your specific database schema.ignore configuration to exclude specific queries:
doctrine_doctor:
ignored_queries:
- "SELECT u.*, p.* FROM user u JOIN profile p ON u.id = p.user_id WHERE u.active = :active"
Database Permissions:
EXPLAIN permissions. If you see permission errors, grant:
GRANT EXECUTE ON PROCEDURE `sys`.`pt_show_grants` TO 'your_user';
Disable Specific Analyzers:
Temporarily disable noisy analyzers (e.g., excessive_hydration) in doctrine_doctor.yaml:
doctrine_doctor:
analyzers:
excessive_hydration:
enabled: false
Log Raw Analysis Data: Enable debug logging to inspect raw analyzer output:
# config/packages/dev/monolog.yaml
monolog:
handlers:
main:
level: debug
channels: ["doctrine_doctor"]
Reset Cache: If issues persist after configuration changes, clear the Symfony cache:
php bin/console cache:clear
Custom Analyzers:
Create a class implementing AhmedBhs\DoctrineDoctor\Analyzer\AnalyzerInterface:
use AhmedBhs\DoctrineDoctor\Analyzer\AnalyzerInterface;
use AhmedBhs\DoctrineDoctor\Analyzer\Issue;
class CustomAnalyzer implements AnalyzerInterface {
public function analyze(array $queries): array {
foreach ($queries as $query) {
if (str_contains($query['sql'], 'SELECT *')) {
yield new Issue(
'Performance',
'Avoid SELECT * queries',
$query['backtrace'],
Issue::SEVERITY_WARNING
);
}
}
}
}
Register it in config/packages/dev/doctrine_doctor.yaml:
doctrine_doctor:
analyzers:
custom:
class: App\Analyzer\CustomAnalyzer
enabled: true
Modify Issue Severity: Override severity levels for specific analyzers:
doctrine_doctor:
analyzers:
n_plus_one:
severity: critical # Default is warning
Post-Analysis Hooks:
Listen to the doctrine_doctor.analysis.complete event to process results:
use AhmedBhs\DoctrineDoctor\Event\AnalysisCompleteEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class DoctrineDoctorSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
'doctrine_doctor.analysis.complete' => 'onAnalysisComplete',
];
}
public function onAnalysisComplete(AnalysisCompleteEvent $event) {
$issues = $event->getIssues();
// Custom logic (e.g., log to Slack, trigger alerts)
}
}
How can I help you explore Laravel packages today?