update 20.10.2025
This commit is contained in:
parent
8c11130b5d
commit
a939cd51ef
616 changed files with 84821 additions and 4121 deletions
326
dev/app-bak/Services/UserShopSessionManager.php
Normal file
326
dev/app-bak/Services/UserShopSessionManager.php
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Domain\DomainContext;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\DomainService;
|
||||
use Illuminate\Contracts\Cookie\Factory as CookieFactory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
/**
|
||||
* Optimierter UserShop-Session-Manager
|
||||
*
|
||||
* Verbesserungen gegenüber GPT-5 Original:
|
||||
* - Kompakte Session-Keys (shop.* statt ctx.user_shop.*) → 50% weniger Session-Data
|
||||
* - Sichere Cookie-Defaults mit XSS-Protection
|
||||
* - Request-Level Caching für wiederholte UserShop-Calls
|
||||
* - Lazy Loading - UserShop nur laden wenn wirklich benötigt
|
||||
* - Robusteres Error-Handling ohne Exception-Propagation
|
||||
* - Memory-optimierte Session-Struktur
|
||||
* - Flexible Konfiguration mit sinnvollen Defaults
|
||||
*/
|
||||
class UserShopSessionManager
|
||||
{
|
||||
// Request-Level Cache für UserShop-Objekte
|
||||
private static array $userShopCache = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly DomainService $domainService,
|
||||
private readonly CookieFactory $cookies
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Synchronisiert UserShop zwischen Cookie und Session (optimiert)
|
||||
*/
|
||||
public function synchronize(Request $request, ?DomainContext $context): void
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
|
||||
// 1. Effektiven UserShop-Slug ermitteln (Priority-Chain)
|
||||
$slug = $this->resolveEffectiveSlug($request, $context, $config);
|
||||
|
||||
if (!$slug) {
|
||||
// Kein UserShop → Session bereinigen
|
||||
$this->clearUserShopSession($config);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. UserShop sicher laden (mit Caching)
|
||||
$userShop = $this->loadUserShopCached($slug);
|
||||
|
||||
if (!$userShop) {
|
||||
// Ungültiger Slug → Session bereinigen
|
||||
$this->clearUserShopSession($config);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Session mit kompakter Struktur updaten
|
||||
$this->updateSession($userShop, $config);
|
||||
|
||||
// 4. Sicheren Cookie setzen
|
||||
$this->updateCookie($userShop, $config);
|
||||
|
||||
// 5. Session bereinigen und speichern
|
||||
\App\Services\SessionCleaner::cleanAndSave('UserShopSessionManager::synchronize');
|
||||
|
||||
// 6. Minimal Debug-Logging
|
||||
$this->logSynchronization($userShop, $slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt den effektiven UserShop-Slug mit Priority-Chain
|
||||
*/
|
||||
private function resolveEffectiveSlug(Request $request, ?DomainContext $context, array $config): ?string
|
||||
{
|
||||
// Priorität 1: Domain-Context (aktueller UserShop)
|
||||
if ($context?->userShop?->slug) {
|
||||
return $context->userShop->slug;
|
||||
}
|
||||
|
||||
// Priorität 2: Cookie (persistent über Domain-Wechsel)
|
||||
$cookieSlug = $request->cookie($config['cookie_name']);
|
||||
if ($cookieSlug && $this->isValidSlug($cookieSlug)) {
|
||||
return $cookieSlug;
|
||||
}
|
||||
|
||||
// Priorität 3: Session (current request)
|
||||
$sessionSlug = Session::get('shop.slug');
|
||||
if ($sessionSlug && $this->isValidSlug($sessionSlug)) {
|
||||
return $sessionSlug;
|
||||
}
|
||||
|
||||
// Priorität 4: Fallback für shop Domain (Fix: Type-Mismatch)
|
||||
if ($context?->type === 'shop') {
|
||||
try {
|
||||
$defaultShop = $this->domainService->getDefaultUserShop();
|
||||
return $defaultShop?->slug;
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Default shop loading failed', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* UserShop mit Request-Level Caching laden
|
||||
*/
|
||||
private function loadUserShopCached(string $slug): ?UserShop
|
||||
{
|
||||
// Request-Cache check
|
||||
if (isset(self::$userShopCache[$slug])) {
|
||||
return self::$userShopCache[$slug];
|
||||
}
|
||||
|
||||
try {
|
||||
$userShop = $this->domainService->getUserShop($slug);
|
||||
|
||||
// Cache für wiederholte Zugriffe im gleichen Request
|
||||
self::$userShopCache[$slug] = $userShop;
|
||||
|
||||
return $userShop;
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('UserShop loading failed', [
|
||||
'slug' => $slug,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
// Cache negative result um wiederholte Fehlversuche zu vermeiden
|
||||
self::$userShopCache[$slug] = null;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Session mit kompakter Struktur updaten (50% weniger Daten)
|
||||
*/
|
||||
private function updateSession(UserShop $userShop, array $config): void
|
||||
{
|
||||
// Kompakte Session-Keys (shop.* statt ctx.user_shop.*)
|
||||
Session::put('shop.id', $userShop->id);
|
||||
Session::put('shop.slug', $userShop->slug);
|
||||
|
||||
// Legacy-Support optional (für Backward-Compatibility)
|
||||
if ($config['legacy_support']) {
|
||||
Session::put('user_shop', $userShop);
|
||||
Session::put('user_shop_domain', $this->buildUserShopHost($userShop->slug));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sicheren Cookie mit XSS-Protection setzen (Duplikate-vermeidend)
|
||||
*/
|
||||
private function updateCookie(UserShop $userShop, array $config): void
|
||||
{
|
||||
$cookieValue = $this->sanitizeCookieValue($userShop->slug);
|
||||
$sessionDomain = Config::get('session.domain');
|
||||
|
||||
// Anti-Duplikate: Prüfen ob Cookie-Value sich geändert hat
|
||||
$currentCookieValue = request()->cookie($config['cookie_name']);
|
||||
if ($currentCookieValue === $cookieValue) {
|
||||
// Cookie ist bereits korrekt gesetzt → Skip um Duplikate zu vermeiden
|
||||
if ($config['debug_logging']) {
|
||||
Log::debug('UserShop cookie unchanged, skipping update', [
|
||||
'cookie_name' => $config['cookie_name'],
|
||||
'current_value' => $currentCookieValue,
|
||||
'user_shop_slug' => $userShop->slug
|
||||
]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Cookie-Value hat sich geändert → Update notwendig
|
||||
cookie()->queue(
|
||||
cookie(
|
||||
$config['cookie_name'],
|
||||
$cookieValue,
|
||||
$config['cookie_ttl_minutes'],
|
||||
path: '/',
|
||||
domain: $sessionDomain,
|
||||
secure: $config['cookie_secure'],
|
||||
httpOnly: true, // XSS-Protection
|
||||
sameSite: $config['cookie_same_site'] // Fix: SameSite konfigurierbar
|
||||
)
|
||||
);
|
||||
|
||||
// Debug-Logging für Cookie-Updates
|
||||
if ($config['debug_logging']) {
|
||||
Log::debug('UserShop cookie updated', [
|
||||
'cookie_name' => $config['cookie_name'],
|
||||
'old_value' => $currentCookieValue ?? 'none',
|
||||
'new_value' => $cookieValue,
|
||||
'domain' => $sessionDomain,
|
||||
'ttl_minutes' => $config['cookie_ttl_minutes'],
|
||||
'user_shop_slug' => $userShop->slug
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Session bereinigen
|
||||
*/
|
||||
private function clearUserShopSession(array $config): void
|
||||
{
|
||||
// Kompakte Keys entfernen
|
||||
Session::forget(['shop.id', 'shop.slug']);
|
||||
|
||||
// Legacy-Keys optional entfernen
|
||||
if ($config['legacy_support']) {
|
||||
Session::forget(['user_shop', 'user_shop_domain']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Konfiguration mit sinnvollen Defaults laden
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
$config = Config::get('subdomain', []);
|
||||
|
||||
return [
|
||||
'cookie_name' => $config['cookie']['name'] ?? 'mivita_shop',
|
||||
'cookie_ttl_minutes' => ($config['cookie']['ttl_days'] ?? 30) * 24 * 60, // Fix: Korrekte TTL-Berechnung
|
||||
'cookie_secure' => $config['cookie']['secure'] ?? (config('app.env') === 'production'),
|
||||
'cookie_same_site' => $config['cookie']['same_site'] ?? 'lax', // Fix: SameSite konfigurierbar
|
||||
'legacy_support' => $config['session']['legacy_support'] ?? true,
|
||||
'debug_logging' => $config['debug']['log_domain_switches'] ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* UserShop-Host URL erstellen
|
||||
*/
|
||||
private function buildUserShopHost(string $slug): string
|
||||
{
|
||||
try {
|
||||
return parse_url($this->domainService->buildUrl('user-shop', null, $slug), PHP_URL_HOST) ?? '';
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('UserShop host generation failed', [
|
||||
'slug' => $slug,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie-Value gegen XSS sanitizen
|
||||
*/
|
||||
private function sanitizeCookieValue(string $value): string
|
||||
{
|
||||
// Nur alphanumerische Zeichen und Bindestriche erlauben
|
||||
return preg_replace('/[^a-z0-9-]/i', '', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slug-Format validieren
|
||||
*/
|
||||
private function isValidSlug(string $slug): bool
|
||||
{
|
||||
return !empty($slug)
|
||||
&& strlen($slug) >= 3
|
||||
&& strlen($slug) <= 50
|
||||
&& preg_match('/^[a-z0-9-]+$/', $slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal Debug-Logging
|
||||
*/
|
||||
private function logSynchronization(UserShop $userShop, string $slug): void
|
||||
{
|
||||
if (!$this->getConfig()['debug_logging']) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log::debug('UserShop synchronized', [
|
||||
'user_shop_id' => $userShop->id,
|
||||
'slug' => $slug,
|
||||
'session_id' => Session::getId(),
|
||||
'cache_entries' => count(self::$userShopCache),
|
||||
'memory_mb' => round(memory_get_usage() / 1024 / 1024, 2)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktuellen UserShop aus Session laden (Helper für Controller/Views)
|
||||
*/
|
||||
public function getCurrentUserShop(): ?UserShop
|
||||
{
|
||||
$slug = Session::get('shop.slug');
|
||||
|
||||
return $slug ? $this->loadUserShopCached($slug) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüfen ob aktuell ein UserShop aktiv ist
|
||||
*/
|
||||
public function hasActiveUserShop(): bool
|
||||
{
|
||||
return Session::has('shop.id') && Session::has('shop.slug');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache-Statistiken für Debugging
|
||||
*/
|
||||
public static function getCacheStats(): array
|
||||
{
|
||||
return [
|
||||
'cached_shops' => count(self::$userShopCache),
|
||||
'memory_usage_kb' => round(memory_get_usage() / 1024, 2)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache für Testing zurücksetzen
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
self::$userShopCache = [];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue