Optimierung 4.2: Published-Release-Query ueber PressRelease-Scopes zentralisiert
Vier komponierbare Scopes auf PressRelease:
- published() Status freigegeben + published_at gesetzt (ohne Zeitschranke,
damit Statistik-Fenster frei einschraenken koennen)
- visibleInArchive() published() + published_at <= jetzt (oeffentlich sichtbar)
- forPortalValues() whereIn portal
- forLanguage() where language
~19 duplizierte Query-Bloecke in routes/web.php (Home, Kategorie, Detail,
Veroeffentlichen, Suche, Newsrooms, Newsroom-Detail, Legacy-Redirect) nutzen
jetzt diese Scopes. Jedes withoutGlobalScope(PortalScope) bleibt an seiner
Stelle erhalten; die Klausel-Reihenfolge ist fuers Ergebnis irrelevant (reine
AND-Verknuepfung), daher verhaltensgleich.
Test: Web/PublishedReleaseScopeTest; die 89 bestehenden Web/-Tests bleiben gruen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4c80b934ab
commit
987a5a2c42
4 changed files with 137 additions and 83 deletions
|
|
@ -184,6 +184,41 @@ class PressRelease extends Model
|
|||
return $query->whereHas('boosts', fn (Builder $q) => $q->active());
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine veröffentlichte PM: freigegebener Status mit gesetztem Datum. Bewusst
|
||||
* OHNE Zeitschranke, damit Statistik-Fenster (z. B. „letzte 7 Tage") frei auf
|
||||
* `published_at` einschränken können. Single Source of Truth für die
|
||||
* öffentlichen Web-Abfragen.
|
||||
*/
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', PressReleaseStatus::Published)
|
||||
->whereNotNull('published_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffentlich sichtbare PM im Archiv: veröffentlicht UND der Termin liegt nicht
|
||||
* in der Zukunft (`published_at <= jetzt`). Basis für alle Listen-/Detail-/
|
||||
* Lead-Abfragen der Web-Seiten.
|
||||
*/
|
||||
public function scopeVisibleInArchive(Builder $query): Builder
|
||||
{
|
||||
return $query->published()->where('published_at', '<=', now());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $portalValues
|
||||
*/
|
||||
public function scopeForPortalValues(Builder $query, array $portalValues): Builder
|
||||
{
|
||||
return $query->whereIn('portal', $portalValues);
|
||||
}
|
||||
|
||||
public function scopeForLanguage(Builder $query, string $language): Builder
|
||||
{
|
||||
return $query->where('language', $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt auf PMs, die dem User gehören (als Autor) oder zu einer ihm
|
||||
* zugeordneten Firma. Bewusst ohne Portal-GlobalScope: ein Kunde darf
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ Keine Fachlogik-Änderung. Reduziert erreichbare Demo-Komponenten und Altlasten.
|
|||
`routes/web.php` > 1100 Zeilen mit kompletter Datenaufbereitung. Schrittweise, gut absicherbar.
|
||||
|
||||
- [ ] **4.1 Portal-/Domain-Kontext extrahieren** (baut auf 3.3 `DomainContext` auf).
|
||||
- [ ] **4.2 Published-Release-Query zentralisieren** — gleiche Bedingung (Portal `both`, `status=published`, Sprache, `published_at <= now()`) mehrfach, teils mit/ohne `withoutGlobalScope(PortalScope)`. → Scopes `scopePublished`, `scopeForPortalValues`, `scopeForLanguage`, `scopeVisibleInArchive`.
|
||||
- [x] **4.2 Published-Release-Query zentralisieren** — vier komponierbare Scopes auf `PressRelease`: `scopePublished()` (Status + gesetztes Datum, ohne Zeitschranke → für Statistik-Fenster), `scopeVisibleInArchive()` (`published()` + `published_at <= now()`), `scopeForPortalValues(array)`, `scopeForLanguage(string)`. ~19 dupliziierte Query-Blöcke in `routes/web.php` (Home, Kategorie, Detail, Veröffentlichen, Suche, Newsrooms, Newsroom-Detail, Legacy-Redirect) darüber zentralisiert; jedes `withoutGlobalScope(PortalScope)` an seiner Stelle erhalten (Klausel-Reihenfolge ist für das Ergebnis irrelevant, da reine AND-Verknüpfung). Test: `Web/PublishedReleaseScopeTest`; die 89 bestehenden `Web/`-Tests bleiben grün.
|
||||
- [ ] **4.3 Page-Data in Services** — `HomePageData`/`CategoryPageData`/`NewsroomPageData` für Home/Kategorie/Newsroom/Detail/Suche/Preise.
|
||||
- [ ] **4.4 Routen auf schlanke (Invokable) Controller umstellen** — schrittweise je Seite.
|
||||
|
||||
|
|
|
|||
138
routes/web.php
138
routes/web.php
|
|
@ -56,11 +56,9 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
|
|||
$previousSince = $referenceNow->copy()->subDays(14);
|
||||
|
||||
$publishedQuery = static fn (): Builder => PressRelease::query()
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive();
|
||||
|
||||
$with = [
|
||||
'company',
|
||||
|
|
@ -98,26 +96,24 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
|
|||
->where('is_active', true)
|
||||
->whereHas('pressReleases', function (Builder $query) use ($portalValues, $recentSince, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $recentSince);
|
||||
})
|
||||
->withCount([
|
||||
'pressReleases as recent_releases_count' => function (Builder $query) use ($portalValues, $recentSince, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $recentSince);
|
||||
},
|
||||
'pressReleases as today_releases_count' => function (Builder $query) use ($portalValues, $referenceNow, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->whereDate('published_at', $referenceNow->toDateString());
|
||||
},
|
||||
])
|
||||
|
|
@ -138,17 +134,16 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
|
|||
->withCount([
|
||||
'pressReleases as recent_count' => function (Builder $query) use ($portalValues, $recentSince, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->where('published_at', '>=', $recentSince)
|
||||
->whereNotNull('published_at');
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $recentSince);
|
||||
},
|
||||
'pressReleases as previous_count' => function (Builder $query) use ($portalValues, $recentSince, $previousSince, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $previousSince)
|
||||
->where('published_at', '<', $recentSince);
|
||||
},
|
||||
|
|
@ -182,10 +177,9 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
|
|||
->withCount([
|
||||
'pressReleases as window_count' => function (Builder $query) use ($portalValues, $recentSince, $referenceNow, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $recentSince)
|
||||
->where('published_at', '<=', $referenceNow);
|
||||
},
|
||||
|
|
@ -200,11 +194,9 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
|
|||
|
||||
if ($spotlightCategory && $spotlightTranslation = $spotlightCategory->translations->first()) {
|
||||
$spotlightBase = static fn (): Builder => PressRelease::query()
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive()
|
||||
->where('category_id', $spotlightCategory->id);
|
||||
|
||||
$spotlightReleases = $spotlightBase()
|
||||
|
|
@ -314,11 +306,9 @@ $categoryPage = static function (string $edition, string $slug) use ($applyWebDo
|
|||
|
||||
$base = static fn (): Builder => PressRelease::query()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive()
|
||||
->whereIn('category_id', $categoryIds);
|
||||
|
||||
// Kategoriespezifischer Stichtag: jüngste Meldung dieser Branche.
|
||||
|
|
@ -373,10 +363,9 @@ $categoryPage = static function (string $edition, string $slug) use ($applyWebDo
|
|||
|
||||
$newsroomFilter = static function (Builder $query) use ($portalValues, $categoryIds, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->whereIn('category_id', $categoryIds);
|
||||
};
|
||||
|
||||
|
|
@ -410,10 +399,9 @@ $categoryPage = static function (string $edition, string $slug) use ($applyWebDo
|
|||
]);
|
||||
|
||||
$publishedCount = static fn (Builder $query) => $query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at');
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published();
|
||||
|
||||
$subCategories = $childCategories
|
||||
->map(function (Category $child) use ($recentSince, $previousSince, $publishedCount): ?array {
|
||||
|
|
@ -501,11 +489,9 @@ $releaseDetailPage = static function (string $edition, string $slug) use ($apply
|
|||
$portalValues = [$portal->value, Portal::Both->value];
|
||||
|
||||
$publishedScope = static fn (Builder $query): Builder => $query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive();
|
||||
|
||||
$release = PressRelease::query()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
|
|
@ -584,11 +570,9 @@ $veroeffentlichenPage = static function (string $edition) use ($applyWebDomainCo
|
|||
|
||||
$publishedBase = static fn (): Builder => PressRelease::query()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive();
|
||||
|
||||
$archiveTotal = $publishedBase()->count();
|
||||
$publishedToday = $publishedBase()->whereDate('published_at', $referenceNow->toDateString())->count();
|
||||
|
|
@ -612,10 +596,9 @@ $veroeffentlichenPage = static function (string $edition) use ($applyWebDomainCo
|
|||
|
||||
$newsroomFilter = static function (Builder $query) use ($portalValues, $language, $recentSince): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published()
|
||||
->where('published_at', '>=', $recentSince);
|
||||
};
|
||||
|
||||
|
|
@ -702,11 +685,9 @@ $suchePage = static function (string $edition) use ($applyWebDomainConfig) {
|
|||
|
||||
$mostReadReleases = PressRelease::query()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive()
|
||||
->orderByDesc('hits')
|
||||
->orderByDesc('published_at')
|
||||
->limit(5)
|
||||
|
|
@ -739,11 +720,9 @@ $newsroomsIndex = static function (string $edition) use ($applyWebDomainConfig)
|
|||
|
||||
$published = static function (Builder $query) use ($portalValues, $language): void {
|
||||
$query
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive();
|
||||
};
|
||||
|
||||
$activeCompanies = static fn (): Builder => Company::query()
|
||||
|
|
@ -817,11 +796,9 @@ $newsroomDetail = static function (string $edition, string $slug) use ($applyWeb
|
|||
$base = static fn (): Builder => PressRelease::query()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->where('company_id', $company->id)
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now());
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->visibleInArchive();
|
||||
|
||||
// Keine öffentliche Newsroom-Seite ohne veröffentlichte Meldung.
|
||||
if (! $base()->exists()) {
|
||||
|
|
@ -861,10 +838,9 @@ $newsroomDetail = static function (string $edition, string $slug) use ($applyWeb
|
|||
$query
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->where('company_id', $company->id)
|
||||
->whereIn('portal', $portalValues)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->where('language', $language)
|
||||
->whereNotNull('published_at');
|
||||
->forPortalValues($portalValues)
|
||||
->forLanguage($language)
|
||||
->published();
|
||||
};
|
||||
|
||||
$categories = Category::query()
|
||||
|
|
@ -975,9 +951,7 @@ $htmlRedirect = static function (string $edition, string $path) {
|
|||
&& $company->is_active
|
||||
&& $company->pressReleases()
|
||||
->withoutGlobalScope(PortalScope::class)
|
||||
->where('status', PressReleaseStatus::Published)
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '<=', now())
|
||||
->visibleInArchive()
|
||||
->exists();
|
||||
|
||||
return redirect(
|
||||
|
|
|
|||
45
tests/Feature/Web/PublishedReleaseScopeTest.php
Normal file
45
tests/Feature/Web/PublishedReleaseScopeTest.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Portal;
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\PressRelease;
|
||||
|
||||
test('published includes published releases with a date but excludes drafts, reviews and dateless', function () {
|
||||
$live = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value]);
|
||||
$future = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value, 'published_at' => now()->addDays(3)]);
|
||||
$draft = PressRelease::factory()->create(['status' => PressReleaseStatus::Draft->value, 'portal' => Portal::Presseecho->value]);
|
||||
$review = PressRelease::factory()->inReview()->create(['portal' => Portal::Presseecho->value]);
|
||||
$dateless = PressRelease::factory()->create(['status' => PressReleaseStatus::Published->value, 'published_at' => null, 'portal' => Portal::Presseecho->value]);
|
||||
|
||||
$ids = PressRelease::withoutGlobalScopes()->published()->pluck('id');
|
||||
|
||||
expect($ids)->toContain($live->id)
|
||||
->toContain($future->id)
|
||||
->not->toContain($draft->id)
|
||||
->not->toContain($review->id)
|
||||
->not->toContain($dateless->id);
|
||||
});
|
||||
|
||||
test('visibleInArchive excludes future-dated releases', function () {
|
||||
$live = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value]);
|
||||
$future = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value, 'published_at' => now()->addDay()]);
|
||||
|
||||
$ids = PressRelease::withoutGlobalScopes()->visibleInArchive()->pluck('id');
|
||||
|
||||
expect($ids)->toContain($live->id)->not->toContain($future->id);
|
||||
});
|
||||
|
||||
test('forPortalValues and forLanguage filter as expected', function () {
|
||||
$match = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value, 'language' => 'de']);
|
||||
$otherPortal = PressRelease::factory()->published()->create(['portal' => Portal::Businessportal24->value, 'language' => 'de']);
|
||||
$otherLang = PressRelease::factory()->published()->create(['portal' => Portal::Presseecho->value, 'language' => 'en']);
|
||||
|
||||
$ids = PressRelease::withoutGlobalScopes()
|
||||
->forPortalValues([Portal::Presseecho->value, Portal::Both->value])
|
||||
->forLanguage('de')
|
||||
->pluck('id');
|
||||
|
||||
expect($ids)->toContain($match->id)
|
||||
->not->toContain($otherPortal->id)
|
||||
->not->toContain($otherLang->id);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue