laramint/queryguard

Zero-config CI gate for Laravel: auto-instruments your test suite, baselines query counts, and fails PRs that introduce N+1s or query regressions.
200 5
Install
composer require laramint/queryguard
Latest Version:v0.1.0
PHP:^8.2
Laravel:9 – 13
License:MIT
Last Updated:May 19, 2026
Links: GitHub  ·  Packagist
Maintainer: mrmarchone

QueryGuard

Zero-config CI gate for Laravel query regressions.
Baseline query counts per test, surface N+1 patterns, and fail builds when tests drift — without manual per-test assertions.

Laravel PHP License Sponsor

Buy Me a Coffee

QueryGuard

Zero-config CI gate for Laravel. Auto-instruments your test suite, baselines query counts per test, and fails PRs that introduce N+1s or query regressions — without you adding a single assertion.

Existing Laravel query tools either run only in dev mode (beyondcode/laravel-query-detector, Debugbar) or require devs to opt-in per test with manual assertions (mattiasgeniar/phpunit-query-count-assertions). Neither catches regressions automatically in CI on an existing suite.

QueryGuard does. Install, baseline once, and from then on every PR that pushes a test's query count past its baseline (or introduces a new N+1 pattern) fails the build with a precise diff.

Install

composer require --dev laramint/queryguard

Register the PHPUnit extension in phpunit.xml:

<extensions>
    <bootstrap class="QueryGuard\PHPUnit\QueryGuardExtension"/>
</extensions>

Usage

# Record the baseline (do this once, commit the file).
php artisan queryguard:baseline

# In CI, this exits non-zero on regression:
php artisan queryguard:check

# Or run phpunit directly with the env var:
QUERYGUARD_MODE=check vendor/bin/phpunit

Commit tests/.queryguard-baseline.json to git — PR diffs naturally show "this test went from 4 to 17 queries."

Per-test budgets (optional)

use QueryGuard\Attributes\QueryBudget;

#[QueryBudget(max: 5)]
public function test_index_is_fast(): void { /* ... */ }

GitHub Actions

- name: QueryGuard
  run: |
    php artisan queryguard:check --markdown > queryguard.md || EXIT=$?
    gh pr comment "$PR_NUMBER" --body-file queryguard.md
    exit ${EXIT:-0}
  env:
    PR_NUMBER: ${{ github.event.pull_request.number }}
    GH_TOKEN: ${{ github.token }}

Configuration

php artisan vendor:publish --tag=queryguard-config

Then edit config/queryguard.php for tolerances, ignore patterns, slow-query threshold, and N+1 detection threshold.

How it works

  1. The PHPUnit extension hooks testPrepared / testFinished and registers a DB::listen callback that records every query per-test.
  2. Each query's SQL is normalized into a stable signature (literals stripped, IN (?,?,?) collapsed, keywords lowercased) so the same logical query matches across runs.
  3. At end of run, the recorded profiles are either written to tests/.queryguard-baseline.json (in baseline mode) or diffed against it (in check mode).
  4. Any of the following exits the run non-zero:
    • A test's query count exceeds baseline + tolerance
    • The same query signature is executed more than n_plus_one.threshold times in a single test
    • A #[QueryBudget] is exceeded

Slow queries and new query signatures are reported as warnings (non-fatal) by default.

License

MIT.

Related Packages

rawphp/warp

Warm-worker test engine for Laravel + Pest — boots the app once per process and...

732 1
ralder/phpunit-query-scaling

Controlled multi-scale query-count assertions for Laravel tests (PHPUnit and Pes...

1 0
sonypradana/best

Prefix for better naming

0 0
faisalmirza/diff-coverage

Calculate code coverage for git diff (changed lines only)

2,309 0
msfukui/php-affected

Lists the PHP files that depend on the files you specify. Static analysis with P...

1 0

Version History

Version Released PHP Laravel License
v0.1.0 ^8.2 ^9.0|^10.0|^11.0|^12.0|^13.0 MIT