presseportale/app/Services/Admin/AdminPerformanceCache.php
Kevin Adametz 54bab7eff5 Optimierung 5.1: Tabellen-Concerns (Sortierung, Volltext-Heuristik, Stats)
- HasSortableTable::sort() ersetzt 5 byte-identische sort()-Kopien in den
  Admin-Listen (Benutzer, Firmen, Kategorien, Kontakte, Pressemitteilungen).
  $sortBy/$sortDir-Defaults bleiben je Komponente, da sie divergieren.
- HasFullTextSearch::supportsFullTextSearch() ersetzt 9 byte-identische Kopien
  (4 Index-Listen + 5 Firmen-Comboboxen); ungenutzte DB-Imports entfernt.
- AdminPerformanceCache::activeInactiveStats(Builder) buendelt den identischen
  SUM(CASE WHEN is_active)-Block der Benutzer- und Firmenliste.

Zurueckgestellt (real divergent): HasResettableFilters (updatedXxxFilter
normalisieren teils zusaetzlich) und eine gemeinsame hydrateRelationCounts().

Tests: erweiterte AdminPerformanceCacheTest; 99 Admin-Tests bleiben gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:36:48 +00:00

133 lines
4.1 KiB
PHP

<?php
namespace App\Services\Admin;
use App\Enums\Portal;
use App\Enums\PressReleaseStatus;
use App\Models\PressRelease;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Cache;
class AdminPerformanceCache
{
public const DashboardStats = 'admin.dashboard.stats';
public const PressReleaseStats = 'admin.press-releases.stats';
public const PressReleaseReviewCount = 'admin.press-releases.review-count';
public const PressReleaseCategoryOptions = 'admin.press-releases.category-options';
public const ActiveCategoryOptions = 'admin.categories.active-options';
public const RoleOptions = 'admin.roles.options';
public const UserStats = 'admin.users.stats';
public const NewsletterStats = 'admin.newsletter.stats';
public const PresetAreas = 'admin.presets.areas';
public const PresetTypes = 'admin.presets.types';
public const StatsTtl = 60;
public const OptionsTtl = 300;
public function remember(string $key, int $seconds, callable $callback): mixed
{
if (app()->runningUnitTests()) {
return $callback();
}
return Cache::memo()->remember($key, $seconds, $callback);
}
public function flush(): void
{
foreach ($this->keys() as $key) {
Cache::memo()->forget($key);
}
}
/**
* Aktiv-/Inaktiv-Kennzahlen einer `is_active`-Tabelle in einer einzigen
* Aggregat-Abfrage. Single Source of Truth für die identischen Stat-Blöcke
* der Benutzer- und Firmen-Liste.
*
* @param Builder<covariant \Illuminate\Database\Eloquent\Model> $query
* @return array{total: int, active: int, inactive: int}
*/
public function activeInactiveStats(Builder $query): array
{
$stats = $query->toBase()
->selectRaw('COUNT(*) as total')
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as active', [true])
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as inactive', [false])
->first();
return [
'total' => (int) ($stats->total ?? 0),
'active' => (int) ($stats->active ?? 0),
'inactive' => (int) ($stats->inactive ?? 0),
];
}
public function pressReleaseReviewCount(): int
{
return (int) $this->remember(self::PressReleaseReviewCount, self::StatsTtl, function (): int {
return PressRelease::query()
->withoutGlobalScopes()
->where('status', PressReleaseStatus::Review->value)
->count();
});
}
public function companiesStatsKey(?string $portal): string
{
return 'admin.companies.stats.'.($portal ?? 'all');
}
public function contactsStatsKey(?string $portal): string
{
return 'admin.contacts.stats.'.($portal ?? 'all');
}
public function categoriesStatsKey(?string $portal): string
{
return 'admin.categories.stats.'.($portal ?? 'all');
}
public function permissionGroupsKey(string $guard): string
{
return "admin.permissions.groups.{$guard}";
}
/**
* @return list<string>
*/
private function keys(): array
{
$portalKeys = collect([null, ...array_map(
static fn (Portal $portal): string => $portal->value,
Portal::cases(),
)]);
return [
self::DashboardStats,
self::PressReleaseStats,
self::PressReleaseReviewCount,
self::PressReleaseCategoryOptions,
self::ActiveCategoryOptions,
self::RoleOptions,
self::UserStats,
self::NewsletterStats,
self::PresetAreas,
self::PresetTypes,
$this->permissionGroupsKey('web'),
...$portalKeys->map(fn (?string $portal): string => $this->companiesStatsKey($portal))->all(),
...$portalKeys->map(fn (?string $portal): string => $this->contactsStatsKey($portal))->all(),
...$portalKeys->map(fn (?string $portal): string => $this->categoriesStatsKey($portal))->all(),
];
}
}