From ab2a4d84385b2fd857cf9f8fb9e950f208898d22 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Wed, 22 Apr 2026 14:32:53 +0200 Subject: [PATCH] Fix chaining `withoutPending()` with `where()` (#1457) At the moment, `where()` cannot be used correctly while using `withoutPending()`. For example, if we have a single non-pending tenant in our DB (with ID 'foo'), queries like `Tenant::withoutPending()->where('id', 'nonexistent')->first()`will incorrectly return the non-pending tenant ('foo'). This is because `withoutPending()` does `$builder->whereNull('data->pending_since')->orWhereNull('data')`. These two aren't grouped, so `withoutPending()->where('id', 'nonexistent')` basically translates to "WHERE data->pending_since IS NULL **OR (data IS NULL AND id = 'nonexistent')**". So the query will include all tenants whose `pending_since` is null (= all non-pending tenants). Grouping `->whereNull('data->pending_since')->orWhereNull('data')` in a closure passed to a separate `where()` fixes this issue. --- src/Database/Concerns/PendingScope.php | 6 ++++-- tests/PendingTenantsTest.php | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Database/Concerns/PendingScope.php b/src/Database/Concerns/PendingScope.php index 52b8eb19..99a5ef59 100644 --- a/src/Database/Concerns/PendingScope.php +++ b/src/Database/Concerns/PendingScope.php @@ -58,8 +58,10 @@ class PendingScope implements Scope { $builder->macro('withoutPending', function (Builder $builder) { $builder->withoutGlobalScope(static::class) - ->whereNull($builder->getModel()->getColumnForQuery('pending_since')) - ->orWhereNull($builder->getModel()->getDataColumn()); + ->where(function (Builder $query) { + $query->whereNull($query->getModel()->getColumnForQuery('pending_since')) + ->orWhereNull($query->getModel()->getDataColumn()); + }); return $builder; }); diff --git a/tests/PendingTenantsTest.php b/tests/PendingTenantsTest.php index a90aceed..433b85fb 100644 --- a/tests/PendingTenantsTest.php +++ b/tests/PendingTenantsTest.php @@ -111,6 +111,18 @@ test('a new tenant gets created while pulling a pending tenant if the pending po expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants }); +test('withoutPending chained with where clauses returns correct results', function () { + $tenant = Tenant::create(); + $pendingTenant = Tenant::createPending(); + + // The query returned the correct tenant + expect(Tenant::withoutPending()->where('id', $tenant->id)->first()->id)->toBe($tenant->id); + // No tenant with this ID exists, the query returns null + expect(Tenant::withoutPending()->where('id', Str::random(8) . 'nonexistent-id')->first())->toBeNull(); + // withoutPending() correctly excludes the pending tenant from the query + expect(Tenant::withoutPending()->where('id', $pendingTenant->id)->first())->toBeNull(); +}); + test('pending tenants are included in all queries based on the include_in_queries config', function () { Tenant::createPending();