Optimierung 4.3/4.4 (Kategorie): Page-Data-Service + Invokable Controller

Kategorie-/Branchenseite in CategoryPageData + Web\CategoryController.
Verhalten unveraendert, 92 Web-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-19 12:09:58 +00:00
parent 179dd8e004
commit ee74126b89
3 changed files with 239 additions and 200 deletions

View file

@ -2,6 +2,7 @@
use App\Enums\Portal;
use App\Http\Controllers\PressReleasePreviewController;
use App\Http\Controllers\Web\CategoryController;
use App\Http\Controllers\Web\NewsroomDetailController;
use App\Http\Controllers\Web\NewsroomsController;
use App\Http\Controllers\Web\PreiseController;
@ -253,204 +254,6 @@ $webHomeData = static function (Portal $primaryPortal, string $language = 'de'):
];
};
// Kategorie-/Branchenseite je Ausgabe.
$categoryPage = static function (string $edition, string $slug) {
$context = WebPortalContext::fromRequest();
$language = $context->language;
$portalValues = $context->portalValues;
$category = Category::query()
->whereIn('portal', $portalValues)
->where('is_active', true)
->whereHas('translations', fn (Builder $query) => $query->where('locale', $language)->where('slug', $slug))
->with([
'translations' => fn ($query) => $query->where('locale', $language),
'parent.translations' => fn ($query) => $query->where('locale', $language),
])
->first();
if (! $category) {
abort(404);
}
$childCategories = Category::query()
->where('parent_id', $category->id)
->where('is_active', true)
->with(['translations' => fn ($query) => $query->where('locale', $language)])
->get();
$categoryIds = collect([$category->id])->merge($childCategories->pluck('id'))->unique()->values()->all();
$base = static fn (): Builder => PressRelease::query()
->withoutGlobalScope(PortalScope::class)
->forPortalValues($portalValues)
->forLanguage($language)
->visibleInArchive()
->whereIn('category_id', $categoryIds);
// Kategoriespezifischer Stichtag: jüngste Meldung dieser Branche.
$latest = $base()->max('published_at');
$referenceNow = $latest ? Carbon::parse($latest) : now();
if ($referenceNow->gt(now())) {
$referenceNow = now();
}
$recentSince = $referenceNow->copy()->subDays(7);
$previousSince = $referenceNow->copy()->subDays(14);
$monthSince = $referenceNow->copy()->subDays(30);
$with = [
'company',
'category.translations' => fn ($query) => $query->where('locale', $language),
'images' => fn ($query) => $query->orderByDesc('is_preview')->orderBy('sort_order')->limit(1),
];
$leadRelease = $base()->with($with)->orderByDesc('published_at')->first();
$topReleases = $base()
->with($with)
->when($leadRelease, fn (Builder $query) => $query->whereKeyNot($leadRelease->getKey()))
->orderByDesc('published_at')
->limit(2)
->get();
$excludeIds = collect([$leadRelease?->getKey()])->merge($topReleases->modelKeys())->filter()->all();
$feedReleases = $base()
->with($with)
->when($excludeIds !== [], fn (Builder $query) => $query->whereNotIn('id', $excludeIds))
->orderByDesc('published_at')
->limit(12)
->get();
$mostReadReleases = $base()
->orderByDesc('hits')
->orderByDesc('published_at')
->limit(5)
->get(['id', 'slug', 'title', 'hits', 'portal', 'language']);
$stats = [
'today' => $base()->whereDate('published_at', $referenceNow->toDateString())->count(),
'week' => $base()->where('published_at', '>=', $recentSince)->count(),
'previousWeek' => $base()->where('published_at', '>=', $previousSince)->where('published_at', '<', $recentSince)->count(),
'month' => $base()->where('published_at', '>=', $monthSince)->count(),
'total' => $base()->count(),
'activeCompanies' => $base()->where('published_at', '>=', $recentSince)->distinct()->count('company_id'),
'totalCompanies' => $base()->distinct()->count('company_id'),
];
$newsroomFilter = static function (Builder $query) use ($portalValues, $categoryIds, $language): void {
$query
->forPortalValues($portalValues)
->forLanguage($language)
->published()
->whereIn('category_id', $categoryIds);
};
$topNewsrooms = Company::query()
->withoutGlobalScope(PortalScope::class)
->whereIn('portal', $portalValues)
->where('is_active', true)
->whereHas('pressReleases', function (Builder $query) use ($newsroomFilter, $recentSince): void {
$newsroomFilter($query);
$query->where('published_at', '>=', $recentSince);
})
->withCount([
'pressReleases as recent_releases_count' => function (Builder $query) use ($newsroomFilter, $recentSince): void {
$newsroomFilter($query);
$query->where('published_at', '>=', $recentSince);
},
'pressReleases as today_releases_count' => function (Builder $query) use ($newsroomFilter, $referenceNow): void {
$newsroomFilter($query);
$query->whereDate('published_at', $referenceNow->toDateString());
},
])
->orderByDesc('recent_releases_count')
->limit(6)
->get()
->map(fn (Company $company): array => [
'name' => $company->name,
'slug' => $company->slug,
'initial' => mb_strtoupper(mb_substr((string) $company->name, 0, 1)),
'count' => (int) $company->recent_releases_count,
'today' => (int) $company->today_releases_count > 0,
]);
$publishedCount = static fn (Builder $query) => $query
->forPortalValues($portalValues)
->forLanguage($language)
->published();
$subCategories = $childCategories
->map(function (Category $child) use ($recentSince, $previousSince, $publishedCount): ?array {
$translation = $child->translations->first();
if (! $translation) {
return null;
}
$total = PressRelease::query()->withoutGlobalScope(PortalScope::class)
->where('category_id', $child->id)->tap($publishedCount)->count();
$recent = PressRelease::query()->withoutGlobalScope(PortalScope::class)
->where('category_id', $child->id)->tap($publishedCount)->where('published_at', '>=', $recentSince)->count();
$previous = PressRelease::query()->withoutGlobalScope(PortalScope::class)
->where('category_id', $child->id)->tap($publishedCount)
->where('published_at', '>=', $previousSince)->where('published_at', '<', $recentSince)->count();
return [
'name' => $translation->name,
'href' => $translation->slug ? route('kategorie', ['slug' => $translation->slug]) : route('web.home'),
'total' => $total,
'recent' => $recent,
'delta' => $recent - $previous,
];
})
->filter()
->sortByDesc('total')
->values();
$relatedCategories = Category::query()
->whereIn('portal', $portalValues)
->where('is_active', true)
->whereNull('parent_id')
->whereKeyNot($category->id)
->when($category->parent_id, fn (Builder $query) => $query->whereKeyNot($category->parent_id))
->with(['translations' => fn ($query) => $query->where('locale', $language)])
->withCount([
'pressReleases as total_count' => fn (Builder $query) => $publishedCount($query),
])
->orderByDesc('total_count')
->limit(8)
->get()
->map(function (Category $related): ?array {
$translation = $related->translations->first();
if (! $translation) {
return null;
}
return [
'name' => $translation->name,
'href' => $translation->slug ? route('kategorie', ['slug' => $translation->slug]) : route('web.home'),
'count' => (int) ($related->total_count ?? 0),
];
})
->filter()
->values();
return view('web.kategorie', [
'category' => $category,
'referenceNow' => $referenceNow,
'leadRelease' => $leadRelease,
'topReleases' => $topReleases,
'feedReleases' => $feedReleases,
'mostReadReleases' => $mostReadReleases,
'categoryStats' => $stats,
'topNewsrooms' => $topNewsrooms,
'subCategories' => $subCategories,
'relatedCategories' => $relatedCategories,
]);
};
// Detailseite einer Pressemitteilung je Ausgabe.
$releaseDetailPage = static function (string $edition, string $slug) {
$context = WebPortalContext::fromRequest();
@ -664,7 +467,7 @@ Route::get('{path}', function (string $path) use ($htmlRedirect) {
// ---------------------------------------------------------------------------
// Öffentliche Inhalte je Ausgabe (Sprache): /de/... und /en/...
// ---------------------------------------------------------------------------
Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(function () use ($applyWebDomainConfig, $webHomeData, $categoryPage, $releaseDetailPage) {
Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(function () use ($applyWebDomainConfig, $webHomeData, $releaseDetailPage) {
// Startseite je Ausgabe
Route::get('/', function (string $edition) use ($applyWebDomainConfig, $webHomeData) {
$domain = request()->getHost();
@ -684,7 +487,7 @@ Route::prefix('{edition}')->whereIn('edition', SetEdition::EDITIONS)->group(func
})->name('web.home');
// Kategorie / Branchenseite (z.B. /de/category/energie-klima)
Route::get('category/{slug}', $categoryPage)->name('kategorie');
Route::get('category/{slug}', CategoryController::class)->name('kategorie');
// Detailseite einer Pressemitteilung (z.B. /de/press-release/ki-revolution)
Route::get('press-release/{slug}', $releaseDetailPage)->name('release.detail');