Skip to content

Add withoutDetection() method for suppressing N+1 detection#116

Open
dingyaguang117 wants to merge 1 commit intobeyondcode:masterfrom
dingyaguang117:feature/add-without-detection
Open

Add withoutDetection() method for suppressing N+1 detection#116
dingyaguang117 wants to merge 1 commit intobeyondcode:masterfrom
dingyaguang117:feature/add-without-detection

Conversation

@dingyaguang117
Copy link
Copy Markdown

Motivation

The existing except config requires whitelisting by model + relation pair, which is too coarse-grained. In practice, developers often need to suppress detection for a specific code block (e.g. admin pages with small datasets, legacy endpoints, or background jobs) rather than globally whitelisting a relation.

What I did

  • Add QueryDetector::withoutDetection(callable $callback) method that temporarily disables N+1 detection for the duration of the closure
  • Uses a simple $disabled flag — no reflection or backtrace scanning overhead
  • The closure's return value is passed through
  • Detection resumes automatically after the closure, even if it throws an exception

Usage

  app(QueryDetector::class)->withoutDetection(function () {
      // N+1 queries here will not be reported
      foreach (Author::all() as $author) {
          $author->posts;
      }
  });

@dingyaguang117
Copy link
Copy Markdown
Author

Hi @mpociot @mechelon,

This PR is ready for review. When you have a chance, could you please take a look and let me know if you’d like any changes? If it looks good to you, I’d really appreciate a merge.

I’ve tried to keep the change minimal and aligned with the existing behavior/style, and I’m happy to adjust or add tests/docs if needed.

Thank you!

@mechelon
Copy link
Copy Markdown
Member

If someone nests withoutDetection() (inner call inside the outer closure’s callback), the inner finally sets $disabled back to false while the outer closure is still running, so queries after the inner call can be detected again. Is that something you have thought about when wiring this up? 🤔 To handle this cleanly, we would need a counter or something.

Bit hard to describe, here's a code example of what I mean:

$detector = app(\BeyondCode\QueryDetector\QueryDetector::class);

$detector->withoutDetection(function () use ($detector) {
    // Inner block: still "off"
    $detector->withoutDetection(function () {
        $authors = Author::all();
        foreach ($authors as $author) {
            $author->profile; // suppressed — OK
        }
    });

    // Outer block *should* still be "off", but it is NOT:
    // Inner's `finally` already set $disabled = false.
    $posts = Post::all();
    foreach ($posts as $post) {
        $post->comments; // N+1 is NOT suppressed here — likely reported
    }
});

@dingyaguang117 dingyaguang117 force-pushed the feature/add-without-detection branch from 1ecd13e to cecfc81 Compare March 25, 2026 08:53
@dingyaguang117
Copy link
Copy Markdown
Author

Thanks for the insight — I hadn’t considered that scenario. I’ve fixed the issue. Please take another look when you have a chance.

@dingyaguang117 dingyaguang117 force-pushed the feature/add-without-detection branch from cecfc81 to 4a63d20 Compare March 25, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants