Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
LaravelPHPJune 24, 20264 min read

Laravel Eloquent Query Optimization via AST-based Static Analysis

Laravel Eloquent query optimization is difficult at runtime. Learn how to use Abstract Syntax Tree (AST) analysis to detect inefficient relationship loading.

LaravelPHPEloquentStatic AnalysisPerformanceASTBackend

Last month, our team spent about two days tracking down a memory leak in a dashboard report service that only triggered under a specific set of filter permutations. It turned out to be a classic N+1 query issue hidden inside a nested loop, which bypassed our standard integration tests because the test dataset was too small to trigger the OOM killer.

We realized then that relying on runtime debugging or simple logging isn't enough when you're managing complex domain models. To solve this, we moved toward Laravel Eloquent query optimization using static analysis and Abstract Syntax Tree (AST) parsing.

Why Runtime Monitoring Fails

We first tried adding a custom QueryExecuted listener to log duplicate queries in development. It worked for simple cases, but it failed to catch issues in conditional logic or background jobs that weren't being hit during manual testing. We even looked into Laravel Proxy Pattern for Eloquent Lazy Loading Optimization to mitigate the damage, but that's a reactive fix.

Static analysis allows us to inspect the code structure itself. By parsing the PHP source code into an AST, we can detect calls to Eloquent relationships within loops or iterators without ever executing a single line of the suspect code.

Building the AST Analyzer

We started by using the nikic/php-parser library, which is the industry standard for parsing PHP code into an AST. The goal was to write a visitor that identifies property access on Eloquent models inside foreach or while blocks.

Here is a simplified version of what our visitor looks like:

PHP
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;

class EloquentRelationshipVisitor extends NodeVisitorAbstract
{
    public function enterNode(Node $node)
    {
        #6A9955">// Detect loops
        if ($node instanceof Node\Stmt\Foreach_) {
            $this->isInsideLoop = true;
        }

        #6A9955">// Detect property access(e.g., $user->posts)
        if ($this->isInsideLoop && $node instanceof Node\Expr\PropertyFetch) {
            $this->reportPotentialNPlusOne($node);
        }
    }
}

This approach is powerful because it's deterministic. Unlike runtime tests, which depend on your input data, the AST analysis covers 100% of your code paths.

Implementing Static Analysis for Eloquent

To make this practical for a team, we integrated this check into our CI pipeline using a custom PHPStan rule. PHPStan already builds an AST, so we don't have to manage the parser ourselves.

When we detect an Eloquent relationship being accessed inside a loop, we flag it as an error. We also cross-reference the class against our Mastering Laravel Eloquent Scopes: Writing Reusable Query Constraints to ensure that if a developer is fetching data, they are at least using the correct scopes.

However, static analysis has limitations. It can’t always tell if a collection has already been eager-loaded via ->load() earlier in the controller method. We’ve found that we have to pair this with Laravel Eloquent Hydration Optimization: Reducing Reflection Overhead to ensure the overhead of these checks doesn't slow down our build times too significantly.

Lessons Learned from the Implementation

The biggest hurdle wasn't the AST logic; it was the "noise." Initially, our tool flagged thousands of false positives because it couldn't distinguish between a standard property and an Eloquent relationship.

We had to implement a whitelist strategy:

  1. Model Mapping: We defined a configuration file that maps models to their known relationships.
  2. Context Awareness: The analyzer now checks if the variable being accessed is an instance of Illuminate\Database\Eloquent\Model.
  3. Thresholds: We allowed a "grace period" for legacy code, focusing only on new PRs.

We’re still refining the analyzer. Sometimes, a loop should lazy-load a relationship if the logic is rare or memory-constrained. We’re currently experimenting with docblock annotations like /** @no-n-plus-one-check */ to suppress warnings where the developer has made an intentional architectural choice.

If you’re struggling with similar performance issues, stop relying on your eyes to catch them in code review. Implementing static analysis to enforce Eloquent performance standards might seem like overkill for a small app, but for a high-scale Laravel system, it’s the only way to sleep through the night.

Frequently Asked Questions

Does AST analysis slow down CI builds? It adds roughly 10-15 seconds to our total build time. We run the analysis only on changed files during pre-commit hooks to keep the developer experience fast.

Can this detect N+1 issues in closures? Yes, but it's complex. You have to track the scope of the variable through the AST nodes. We currently only support simple foreach loops to keep the complexity manageable.

Is this better than using a package like laravel-query-detector? They serve different purposes. laravel-query-detector is great for local development and catching issues at runtime. AST analysis is for prevention—it stops the code from ever reaching the repository if it violates your performance constraints.

I’m still not 100% satisfied with our current false-positive rate on polymorphic relationships. It’s a work in progress, but the peace of mind is worth the effort.

Back to Blog

Similar Posts

LaravelPHPJune 24, 20264 min read

Laravel Eloquent Hydration Optimization: Reducing Reflection Overhead

Laravel Eloquent hydration often creates hidden bottlenecks under heavy load. Learn to implement custom hydrators to bypass reflection and scale your app.

Read more
LaravelPHP
June 23, 2026
4 min read

Laravel Eloquent withCount: A Guide to Efficient Counting

Laravel Eloquent withCount helps you avoid N+1 issues when counting related models. Learn how to use it to boost your database performance today.

Read more
LaravelPHPJune 23, 20264 min read

Laravel Database Performance: Mastering B-Tree Indexing Strategies

Laravel database performance depends on smart indexing. Learn how to use B-Tree rebalancing and partial indexes to scale Eloquent models under high concurrency.

Read more