alex-frolov/query-guard
Query Guard adds configurable safeguards to Laravel database queries, helping detect and block dangerous or unintended SQL before it reaches production. Enforce safety rules, catch destructive statements, and protect critical data with minimal setup.
Performance as a shippable gate, not a post-launch scramble. This package turns "the app feels slow" into reviewable findings (query-in-loop, no-possible-index, tier 2 plan checks) that can fail CI on a threshold. That makes "ship date vs. query budget" an explicit product tradeoff instead of an argument after the fact.
Roadmap sequencing for data-growth features. Any upcoming work that multiplies rows or fan-outs — reporting dashboards, exports, paginated admin tables, bulk imports, notification digests — is exactly what query-in-loop catches in tests before customers see it. Use it to argue that a feature needs an extra sprint for query work, with evidence rather than intuition.
Build vs. buy: buy, and keep buying. The v1.0.1 notes are the argument. Distinguishing a Doctrine flush() of five entities from five separate queries, excluding the test harness's savepoint statements, skipping index advice on unfiltered lookup tables, handling JOIN (SELECT ...) and the paginator's dctrn_result, and classifying plans that read no table — that is deep ORM-and-driver expertise. A homegrown lint rule or a one-off profiling pass does not get there, and you would be maintaining it forever.
A measurable quality bar for the platform team. Because findings land in report-json, you can track finding counts per release as a product-health metric — the same way you track error rates. Fewer real findings over time is a defensible "we are getting faster" statement to leadership.
Test-environment configuration as a decision with a deadline. The new notice about DBAL 3 without savepoints means query-in-loop silently under-reports under a rolled-back test transaction unless doctrine.dbal.use_savepoints: true. That is a platform decision (and a DBAL 4 migration argument), not a tooling detail, and it should be on someone's roadmap.
Adopt when most of the following hold:
VALUES, so existing baselines must be regenerated with QUERY_GUARD_GENERATE_BASELINE=1, and findings will shift in both directions.fail-on="error" plus tier 2 enabled, the first run after this upgrade should be treated as an information-gathering run, not a red build.flush(), dctrn_result, DBAL 3/4 savepoints. Before committing, verify coverage against how your app actually talks to the database. If the codebase is Eloquent-first, treat the value proposition as unproven and run a spike first.Look elsewhere when:
--no-output, so findings exist only in report-json. If you cannot upload or parse that artifact, you will not see results.To executives: This is a guardrail that catches the database mistakes that make features slow, in CI, before release. The v1.0.1 release is the strongest signal we have about its maturity: the maintainers ran the tool against real production-codebase test suites, found that a large share of what it reported was the tool misreading its input, and removed it — including 825 of 927 missing-index findings on one suite that were unfiltered lookup tables no index could help. Cutting that noise is the difference between a tool engineers trust and one they mute. Realistic ask: a short adoption window, one named owner for the baseline, and a tuning period before we gate builds.
To engineering: Expect two things from the upgrade. First, re-baseline: the query fingerprint now collapses multi-row VALUES, so previously known findings will reappear as new and old entries will match nothing — regenerate with QUERY_GUARD_GENERATE_BASELINE=1. Second, expect counts to move both ways: query-in-loop and no-possible-index report substantially less (Doctrine flush() counts as one unit of work, harness savepoints are out of the trace, derived tables and paginator results are excluded), while tier 2 now checks tables inside subqueries and can surface findings it previously missed. Run with tier 2 and fail-on="error" in report-only mode first. Also: if you are on DBAL 3 without savepoints, query-in-loop cannot separate adjacent flushes inside a test transaction and will miss real problems — the summary names the connection and the fix, doctrine.dbal.use_savepoints: true. And remember parallel runs produce no console summary; findings live only in report-json, so wire up artifact collection.
To the platform/DX team: Budget a half-day for the upgrade, a baseline regeneration, and one triage session on the first post-upgrade run — plus the savepoint configuration change if you are on DBAL 3. That configuration work is also the natural trigger to plan DBAL 4.
How can I help you explore Laravel packages today?