Perf-Fix: User::accessibleCompanyIds() hydratisiert keine Company-Models mehr

Auf /admin/me/press-releases wurden bei einem User mit sehr vielen Firmen
~107.000 Company-Models geladen. Ursache: accessibleCompanyIds() nutzte
das Eloquent-Relation-pluck() (ownedCompanies()->pluck() / companies()->
pluck()), das pro ID ein vollstaendiges Model hydratisiert. Die Methode
wird ueber PressRelease::scopeAccessibleBy() pro Seitenaufruf ~2x
ausgewertet (Status-Count- + Listen-Query) -> ~2x 53k Models.

Fix:
- toBase()->pluck() statt Relation-pluck(): liefert nur die ID-Werte ohne
  Hydration, wendet die Global Scopes (PortalScope) weiterhin an.
- Ergebnis pro Request memoisiert (cachedAccessibleCompanyIds), da der Scope
  die Methode mehrfach aufruft.

Messung (User 14016, 26.772 Firmen): 107.238 -> 24 hydratisierte
Company-Models auf der PM-Liste. Ergebnis-IDs unveraendert.

Regressionstests: accessibleCompanyIds liefert owned+member ohne Dubletten
und hydratisiert 0 Company-Models.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-30 11:18:51 +00:00
parent c16b0b77e2
commit ed1aa07ae6
2 changed files with 43 additions and 2 deletions

View file

@ -67,6 +67,32 @@ test('accessibleBy excludes press releases of strangers and unrelated companies'
expect($ids)->not->toContain($foreignPr->id);
});
test('accessibleCompanyIds returns owned and member company ids without duplicates', function () {
$user = User::factory()->create();
$owned = Company::factory()->create(['owner_user_id' => $user->id]);
$member = Company::factory()->create();
$member->users()->attach($user->id, ['role' => 'member']);
Company::factory()->create(); // fremde Firma
$ids = $user->accessibleCompanyIds();
expect($ids)->toEqualCanonicalizing([$owned->id, $member->id]);
});
test('accessibleCompanyIds does not hydrate Company models', function () {
$user = User::factory()->create();
Company::factory()->count(3)->create(['owner_user_id' => $user->id]);
$hydrated = 0;
Company::retrieved(function () use (&$hydrated): void {
$hydrated++;
});
$user->accessibleCompanyIds();
expect($hydrated)->toBe(0);
});
test('accessibleBy bypasses the portal global scope so own PMs are visible cross-portal', function () {
$user = User::factory()->create();
$pr = PressRelease::factory()->create([