- 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>
133 lines
4.1 KiB
PHP
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(),
|
|
];
|
|
}
|
|
}
|