update 20.10.2025
This commit is contained in:
parent
8c11130b5d
commit
a939cd51ef
616 changed files with 84821 additions and 4121 deletions
84
dev/subdomain-optimization-gemini/DomainServiceProvider.php
Normal file
84
dev/subdomain-optimization-gemini/DomainServiceProvider.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Domain\DomainContext;
|
||||
use App\Services\DomainService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* ## Vorschlag für einen überarbeiteten DomainServiceProvider
|
||||
*
|
||||
* In dieser Version wird die Middleware **nicht mehr** hier registriert.
|
||||
* Die Registrierung der Middleware sollte zentral in `app/Http/Kernel.php`
|
||||
* in der korrekten Reihenfolge erfolgen, um Session-Konflikte zu vermeiden.
|
||||
*/
|
||||
class DomainServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Registriert die Domain-Dienste im Service-Container.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// 1. DomainService als Singleton registrieren.
|
||||
// Diese Logik ist gut und bleibt unverändert.
|
||||
$this->app->singleton(DomainService::class, function ($app) {
|
||||
$domainService = new DomainService($app['config']['domains']);
|
||||
|
||||
// Validiere Konfiguration in der Development-Umgebung
|
||||
if (config('app.debug')) {
|
||||
$configErrors = $domainService->validateConfiguration();
|
||||
if (!empty($configErrors)) {
|
||||
\Log::channel('domain')->warning('Domain configuration errors detected', ['errors' => $configErrors]);
|
||||
}
|
||||
}
|
||||
|
||||
return $domainService;
|
||||
});
|
||||
|
||||
// 2. DomainContext als Singleton registrieren.
|
||||
// Die Logik hier wird nur einmal pro Anfrage ausgeführt (lazy-loaded),
|
||||
// wenn der Context das erste Mal benötigt wird. Das ist effizient und korrekt.
|
||||
$this->app->singleton(DomainContext::class, function ($app) {
|
||||
/** @var DomainService $domainService */
|
||||
$domainService = $app->make(DomainService::class);
|
||||
$request = $app->make('request');
|
||||
|
||||
// Analysiere den Host der aktuellen Anfrage
|
||||
$domainInfo = $domainService->parseDomain($request->getHost());
|
||||
|
||||
$userShop = null;
|
||||
|
||||
// Wenn es sich um eine User-Shop-Domain handelt, versuche das Shop-Objekt zu finden.
|
||||
if ($domainInfo['type'] === 'user-shop' && $domainInfo['subdomain']) {
|
||||
$userShop = $domainService->getUserShop($domainInfo['subdomain']);
|
||||
// Wenn der Shop ungültig ist, wird der Typ auf 'unknown' gesetzt.
|
||||
if (!$userShop) {
|
||||
$domainInfo['type'] = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn es sich um die Haupt-Shop-Domain handelt (z.B. mivita.shop),
|
||||
// lade den konfigurierten Fallback-Shop.
|
||||
if ($domainInfo['type'] === 'main-shop' && !empty($domainInfo['default_user_shop'])) {
|
||||
$userShop = $domainService->getUserShop($domainInfo['default_user_shop']);
|
||||
}
|
||||
|
||||
return DomainContext::fromArray($domainInfo, $userShop);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt Aktionen nach der Registrierung aller Provider aus.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Die Middleware-Registrierung wird von hier entfernt.
|
||||
// $kernel = $this->app->make(\Illuminate\Contracts\Http\Kernel::class);
|
||||
// $kernel->prependMiddlewareToGroup('web', DomainResolver::class);
|
||||
}
|
||||
}
|
||||
157
dev/subdomain-optimization-gemini/HandleDomainLogic.php
Normal file
157
dev/subdomain-optimization-gemini/HandleDomainLogic.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use App\Services\Util;
|
||||
use App\Models\UserShop;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Domain\DomainContext;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
/**
|
||||
* ## Vorschlag für eine neue Middleware: HandleDomainLogic
|
||||
*
|
||||
* Diese Middleware ersetzt den alten `DomainResolver`.
|
||||
* Sie läuft NACH `StartSession` und kann daher sicher auf die Session zugreifen.
|
||||
* Ihre Hauptaufgaben sind die Validierung des Domain-Kontexts und die
|
||||
* intelligente Persistierung des User-Shops in der Session.
|
||||
*/
|
||||
class HandleDomainLogic
|
||||
{
|
||||
/**
|
||||
* Behandelt eine eingehende Anfrage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
// Die Prüfung, ob die Middleware ausgeführt werden soll, bleibt gleich.
|
||||
if (!$this->shouldHandleRequest($request)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/** @var DomainContext $context */
|
||||
$context = app(DomainContext::class);
|
||||
|
||||
// Die Konfiguration der Session-Domain ist hier weiterhin sinnvoll.
|
||||
$this->configureSessionDomain($context);
|
||||
|
||||
// 1. Umgang mit unbekannten Domains
|
||||
// Wenn der DomainContext nicht aufgelöst werden konnte, sicher umleiten.
|
||||
if ($context->isUnknown()) {
|
||||
\Log::channel('domain')->warning('Unknown domain accessed', [
|
||||
'host' => $request->getHost(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'ip' => $request->ip(),
|
||||
'path' => $request->getPathInfo()
|
||||
]);
|
||||
$mainUrl = app(\App\Services\DomainService::class)->buildUrl('main');
|
||||
return redirect()->away($mainUrl, 301);
|
||||
}
|
||||
|
||||
// 2. Validierung für User-Shop-Domains
|
||||
if ($context->isUserShop()) {
|
||||
$this->validateUserShop($context);
|
||||
|
||||
// Der `RouteCleanup` sollte als eigene Middleware nach dem Routing laufen
|
||||
// und nicht mehr hier angedeutet werden.
|
||||
if ($request->route('subdomain')) {
|
||||
$request->route()->forgetParameter('subdomain');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Persistierung des Kontexts in der Session (vereinfachte Logik)
|
||||
$this->persistUserShopContext($context);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfiguriert die `session.domain` basierend auf dem Domain-Typ.
|
||||
*/
|
||||
private function configureSessionDomain(DomainContext $context): void
|
||||
{
|
||||
if ($context->type === 'shop' || $context->type === 'main-shop' || $context->type === 'user-shop') {
|
||||
Config::set('session.domain', '.' . config('app.domain') . config('app.tld_shop'));
|
||||
} else {
|
||||
Config::set('session.domain', '.' . config('app.domain') . config('app.tld_care'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt die notwendigen Validierungen für eine User-Shop-Domain durch.
|
||||
*/
|
||||
private function validateUserShop(DomainContext $context): void
|
||||
{
|
||||
if (!$context->userShop) {
|
||||
\Log::channel('domain')->warning('UserShop not found for subdomain', ['subdomain' => $context->subdomain]);
|
||||
abort(503, 'Shop not available');
|
||||
}
|
||||
|
||||
if (!$context->userShop->active) {
|
||||
\Log::channel('domain')->info('Inactive UserShop accessed', ['shop_id' => $context->userShop->id]);
|
||||
abort(503, 'Shop temporarily unavailable');
|
||||
}
|
||||
|
||||
if (!$context->userShop->user || !$context->userShop->user->isActiveShop()) {
|
||||
\Log::channel('domain')->info('UserShop with expired payment accessed', ['shop_id' => $context->userShop->id]);
|
||||
abort(503, 'Shop access denied');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert den User-Shop-Kontext in der Session.
|
||||
*
|
||||
* Diese Methode verfolgt eine einfachere, robustere Strategie:
|
||||
* - Wenn ein User-Shop-Kontext vorhanden ist (`user-shop` oder `main-shop`),
|
||||
* wird er in die Session geschrieben.
|
||||
* - Auf allen anderen Domains (`main`, `crm`, `portal` etc.) wird die
|
||||
* Session NICHT mehr angefasst. Ein einmal gesetzter `user_shop` bleibt
|
||||
* also erhalten, bis der Benutzer einen anderen Shop besucht.
|
||||
* Das `Session::forget('user_shop')` wird bewusst entfernt.
|
||||
*/
|
||||
private function persistUserShopContext(DomainContext $context): void
|
||||
{
|
||||
// Setze die `app.url` zur Laufzeit für korrekte URL-Generierung.
|
||||
Config::set('app.url', 'https://' . $context->host);
|
||||
|
||||
if ($context->userShop) {
|
||||
// Ein UserShop ist im aktuellen Kontext aktiv (entweder via Subdomain oder als Fallback).
|
||||
// Wir aktualisieren die Session mit diesem Shop.
|
||||
Session::put('user_shop', $context->userShop);
|
||||
Session::put('user_shop_domain', $context->host);
|
||||
|
||||
// Kompatibilität mit der alten Util-Klasse.
|
||||
Util::setPostRoute('user/');
|
||||
}
|
||||
// WICHTIG: Es gibt keinen `else`-Block mehr.
|
||||
// Wenn kein `userShop` im Kontext ist (z.B. auf mivita.care, in.mivita.care),
|
||||
// wird ein eventuell vorhandener `user_shop` aus einem früheren Besuch
|
||||
// in der Session belassen. Dies löst das Problem des Kontextverlusts.
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob die Middleware für den Request relevant ist.
|
||||
* (Logik aus dem alten DomainResolver übernommen)
|
||||
*/
|
||||
private function shouldHandleRequest(Request $request): bool
|
||||
{
|
||||
if ($request->is('api/*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($request->isMethod('GET') && preg_match('/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/i', $request->path())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($request->path(), ['_debugbar/!*', 'health', 'status', 'ping'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
73
dev/subdomain-optimization-gemini/ProposedKernel.php
Normal file
73
dev/subdomain-optimization-gemini/ProposedKernel.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
/**
|
||||
* ## Vorschlag für `app/Http/Kernel.php`
|
||||
*
|
||||
* Diese Datei zeigt die empfohlene Anordnung der Middleware, um das Session-Problem zu lösen.
|
||||
* Die `DomainResolver`-Middleware wird durch `HandleDomainLogic` ersetzt und an die richtige Stelle verschoben.
|
||||
*/
|
||||
class ProposedKernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
// ... andere globale Middleware
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
// ...
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class, // Die Session wird hier gestartet.
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
|
||||
// ===================================================================
|
||||
// NEUE ANORDNUNG:
|
||||
// Die Domain-Logik wird NACH `StartSession` ausgeführt.
|
||||
// Der alte `DomainResolver` wird durch `HandleDomainLogic` ersetzt.
|
||||
// `InitializeDomainContext` könnte hier oder sogar global laufen,
|
||||
// wenn der Kontext noch früher gebraucht wird.
|
||||
// ===================================================================
|
||||
// \App\Http\Middleware\InitializeDomainContext::class, // Optional, falls benötigt
|
||||
\App\Http\Middleware\HandleDomainLogic::class, // <-- HIER
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
// ... andere Route-Middleware
|
||||
];
|
||||
}
|
||||
75
dev/subdomain-optimization-gemini/README.md
Normal file
75
dev/subdomain-optimization-gemini/README.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Optimierung des Domain-Routings und Session-Handlings
|
||||
|
||||
Dieses Dokument beschreibt eine vorgeschlagene Optimierung für die bestehende Domain-Routing- und Session-Verwaltungslogik.
|
||||
|
||||
## 1. Analyse der aktuellen Architektur
|
||||
|
||||
Die Anwendung nutzt eine komplexe, aber leistungsstarke Multi-Domain-Architektur, um verschiedene Anwendungsbereiche (Hauptseite, CRM, Partner-Portal, Checkout, User-Shops) voneinander zu trennen.
|
||||
|
||||
- **Konfiguration**: Die Domain-Struktur wird zentral in `config/domains.php` definiert.
|
||||
- **Service Provider (`DomainServiceProvider`)**:
|
||||
- Registriert den `DomainService` (zuständig für Domain-Analyse und UserShop-Logik).
|
||||
- Registriert den `DomainContext` als Singleton, der pro Request Informationen über die aktuelle Domain enthält (Typ, Host, Subdomain, UserShop-Objekt).
|
||||
- **Wichtig**: Er registriert die `DomainResolver`-Middleware und fügt sie an den _Anfang_ der `web`-Middleware-Gruppe hinzu.
|
||||
- **Middleware (`DomainResolver`)**:
|
||||
- Nutzt den `DomainContext`, um die Anfrage zu validieren.
|
||||
- Leitet ungültige Domains auf die Hauptdomain um.
|
||||
- Prüft den Status von User-Shops (aktiv, Zahlung etc.).
|
||||
- **Problembereich**: Schreibt `UserShop`-Daten aktiv in die Session (`Session::put`, `Session::save`).
|
||||
- **Routing (`RouteServiceProvider`)**: Lädt die passenden Routen-Dateien basierend auf dem `type` im `DomainContext`.
|
||||
|
||||
## 2. Identifiziertes Problem: Konflikte bei der Session-Erstellung
|
||||
|
||||
Das Kernproblem liegt in der Reihenfolge, in der die Middleware ausgeführt wird.
|
||||
|
||||
1. Der `DomainServiceProvider` registriert die `DomainResolver`-Middleware mit `prependMiddlewareToGroup('web', ...)`.
|
||||
2. Dadurch wird `DomainResolver` **vor** der Standard-Laravel-Middleware `StartSession` ausgeführt, die ebenfalls Teil der `web`-Gruppe ist.
|
||||
3. Die `DomainResolver`-Middleware versucht, auf die Session zuzugreifen und sie zu modifizieren (`Session::put`, `Session::save`). Da die `StartSession`-Middleware noch nicht gelaufen ist, wird hier möglicherweise eine "provisorische" Session gestartet.
|
||||
4. Anschließend läuft die `StartSession`-Middleware, die die "offizielle" Session aus dem Cookie lädt.
|
||||
5. Dies kann zu einem Konflikt führen, bei dem zwei verschiedene Session-Instanzen pro Anfrage existieren oder die in `DomainResolver` gespeicherten Daten nicht in der finalen Session-Response an den Client gesendet werden. Das Resultat ist, dass der `UserShop`-Kontext beim Wechsel zwischen Domains (z.B. vom Shop zum `in.`-Portal) verloren geht.
|
||||
|
||||
## 3. Vorgeschlagene Lösung: Saubere Trennung und korrekte Reihenfolge
|
||||
|
||||
Die vorgeschlagene Lösung zielt darauf ab, die Logik zu entzerren und die Middleware in der korrekten, von Laravel vorgesehenen Reihenfolge auszuführen.
|
||||
|
||||
### Schritt 1: Korrektur der Middleware-Reihenfolge
|
||||
|
||||
Die `DomainResolver`-Middleware darf nicht mehr per `prependMiddlewareToGroup` im Service Provider registriert werden. Stattdessen sollte sie im `app/Http/Kernel.php` innerhalb der `web`-Gruppe **nach** der `\Illuminate\Session\Middleware\StartSession`-Middleware platziert werden.
|
||||
|
||||
Dadurch wird sichergestellt, dass die Session immer korrekt initialisiert ist, bevor unsere anwendungsspezifische Logik darauf zugreift.
|
||||
|
||||
### Schritt 2: Aufteilung der Middleware-Verantwortlichkeiten
|
||||
|
||||
Die `DomainResolver`-Middleware hat aktuell zu viele Aufgaben. Eine Aufteilung verbessert die Lesbarkeit, Wartbarkeit und Einhaltung des Single-Responsibility-Prinzips.
|
||||
|
||||
**Vorschlag für neue Middleware-Struktur:**
|
||||
|
||||
1. **`InitializeDomainContext` (Neue Middleware)**:
|
||||
|
||||
- Diese Middleware läuft sehr früh (kann global oder am Anfang der `web`-Gruppe stehen).
|
||||
- Ihre **einzige** Aufgabe ist es, den `DomainContext` zu initialisieren, indem sie `app(DomainContext::class)` aufruft. Sie führt **keine** Session-Operationen oder Umleitungen durch. Dies stellt sicher, dass der Kontext für alle nachfolgenden Teile der Anwendung (auch andere Middleware) verfügbar ist.
|
||||
|
||||
2. **`HandleDomainLogic` (Umbenannter `DomainResolver`)**:
|
||||
- Diese Middleware läuft **nach** `StartSession`.
|
||||
- Sie nutzt den bereits initialisierten `DomainContext`.
|
||||
- **Aufgaben**:
|
||||
- Umleitung bei `isUnknown()`.
|
||||
- Validierung des `UserShop` (aktiv, bezahlt etc.).
|
||||
- Persistierung des `UserShop`-Kontexts in der Session. Die Logik hierfür wird verfeinert, um den Shop-Kontext über Domain-Grenzen hinweg intelligent zu erhalten. Zum Beispiel wird der `user_shop` nicht mehr gelöscht, nur weil man die `main`-Domain besucht. Er wird nur überschrieben, wenn ein neuer Shop-Kontext explizit gesetzt wird.
|
||||
|
||||
### Schritt 3: Vereinfachung des Session-Handlings
|
||||
|
||||
Die `setupLegacyContext`-Methode wird überarbeitet. Das Ziel ist ein klares und vorhersehbares Verhalten:
|
||||
|
||||
- **Auf einer User-Shop-Domain**: Der `UserShop` wird in die Session geschrieben.
|
||||
- **Auf einer Nicht-Shop-Domain (`crm`, `portal`, `checkout`, `main`)**: Die Session wird **nicht angerührt**. Der bestehende `UserShop` aus der Session bleibt erhalten, sodass eine Rückkehr zum Shop jederzeit möglich ist.
|
||||
- **Wechsel zu einem anderen User-Shop**: Der neue `UserShop` überschreibt den alten Wert in der Session.
|
||||
|
||||
## 4. Vorteile der neuen Architektur
|
||||
|
||||
- **Stabilität**: Das Session-Problem wird an der Wurzel behoben, indem die Middleware-Reihenfolge korrigiert wird.
|
||||
- **Klarheit**: Die Verantwortlichkeiten sind sauber getrennt. Eine Middleware für die Kontext-Initialisierung, eine für die Domain-Logik.
|
||||
- **Wartbarkeit**: Einfacher zu verstehen und zu erweitern. Das `TECH-DEBT` in `setupLegacyContext` wird reduziert.
|
||||
- **Performance**: Die Logik zur Domain-Analyse im `DomainServiceProvider` wird nur einmal ausgeführt, wenn der Kontext das erste Mal benötigt wird (lazy-loading), was beibehalten wird.
|
||||
|
||||
Die folgenden Dateien in diesem Ordner enthalten eine beispielhafte Implementierung dieses Vorschlags.
|
||||
Loading…
Add table
Add a link
Reference in a new issue