qdenka / laravel-query-doctor
Detect N+1 queries, missing indexes, and slow queries in your Laravel app before they reach production.
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/qdenka/laravel-query-doctor
Requires
- php: ^8.2
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
README
Detect N+1 queries, missing indexes, and slow queries in your Laravel app before they reach production.
- Catches 5 types of problems: N+1, duplicate queries, slow queries, missing indexes, unnecessary
SELECT *. - Works in dev and CI: Web dashboard for browsing, CLI commands for reporting, exit codes for CI pipelines.
- Zero config to start: Install, open
/query-doctor, see results.
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
- SQLite PHP extension (included in most PHP installations)
Installation
composer require --dev qdenka/laravel-query-doctor php artisan vendor:publish --tag=query-doctor-config
That's it. The package auto-discovers and starts capturing queries.
Quick Start
- Install the package (see above).
- Browse your app — make some requests, trigger some pages.
- Open
/query-doctorin your browser.
Or generate a report from the command line:
php artisan doctor:report
CLI Commands
| Command | Description |
|---|---|
php artisan doctor:report |
Show detected issues |
php artisan doctor:report --format=json |
Output as JSON |
php artisan doctor:report --format=md --output=report.md |
Save markdown report to file |
php artisan doctor:baseline |
Snapshot current issues as baseline |
php artisan doctor:baseline --clear |
Remove the baseline |
php artisan doctor:ci-report --fail-on=high |
CI mode: exit 1 if high+ severity issues exist |
php artisan doctor:ci-report --baseline |
CI mode: exclude baselined issues |
CI Integration
# .github/workflows/ci.yml - name: Run tests run: vendor/bin/phpunit - name: Check query performance run: php artisan doctor:ci-report --fail-on=high --output=report.md - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: query-doctor-report path: report.md
Configuration
Publish and edit the config file:
php artisan vendor:publish --tag=query-doctor-config
Key options:
// config/query-doctor.php return [ 'enabled' => env('QUERY_DOCTOR_ENABLED', true), 'allowed_environments' => ['local', 'staging'], 'analyzers' => [ 'n_plus_one' => ['min_repetitions' => 5], 'slow' => ['threshold_ms' => 100], 'duplicate' => ['min_count' => 3], ], ];
See docs/CONFIGURATION.md for the full reference.
Dashboard
The web dashboard is available at /query-doctor (only in allowed environments).
It shows:
- Issues grouped by severity
- Slow queries with timing details
- N+1 candidates with query counts
- Filters by period, route, severity, and type
Security
Query bindings are sanitized before storage:
- Column-based masking: Bindings for columns like
password,token,api_keyare replaced with[MASKED]. - Pattern-based masking: Strings matching email, phone, or SSN patterns are masked regardless of column.
- Hash-only storage: Bindings are stored as SHA-256 hashes for duplicate detection, not raw values.
You can add your own columns and patterns in the config:
'masking' => [ 'columns' => ['password', 'secret', 'token', 'date_of_birth'], 'value_patterns' => ['/^[A-Z]{2}\d{6}$/'], // passport numbers ],
How It Works
- The package hooks into Laravel's
DB::listen()to capture every SQL query. - Queries are fingerprinted (normalized) to group structurally identical queries.
- Five analyzers run against captured queries to detect problems.
- Results are stored in a dedicated SQLite file (separate from your app's database).
- You view results via the dashboard, CLI, or exported reports.
See docs/ARCHITECTURE.md for the full architecture overview.
Extending
Add custom analyzers:
use QDenka\QueryDoctor\Domain\Contracts\AnalyzerInterface; final class MyCustomAnalyzer implements AnalyzerInterface { public function analyze(array $events): array { /* ... */ } public function type(): IssueType { /* ... */ } }
See docs/EXTENSION_API.md for details on custom analyzers, reporters, and event hooks.
Contributing
See CONTRIBUTING.md.
License
MIT. See LICENSE.