feat: Infrastructure multi-tenant avec isolation par sous-domaine

Une application SaaS éducative nécessite une séparation stricte des données
entre établissements scolaires. L'architecture multi-tenant par sous-domaine
(ecole-alpha.classeo.local) permet cette isolation tout en utilisant une
base de code unique.

Le choix d'une résolution basée sur les sous-domaines plutôt que sur des
headers ou tokens facilite le routage au niveau infrastructure (reverse proxy)
et offre une UX plus naturelle où chaque école accède à "son" URL dédiée.
This commit is contained in:
2026-01-30 23:34:10 +01:00
parent 6da5996340
commit 1fd256346a
71 changed files with 14390 additions and 37 deletions

View File

@@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View File

@@ -0,0 +1,18 @@
# Tenants de développement
# Ces tenants sont automatiquement chargés en environnement dev
parameters:
tenant.dev_configs:
- tenantId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
subdomain: 'ecole-alpha'
databaseUrl: '%env(DATABASE_URL)%'
- tenantId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901'
subdomain: 'ecole-beta'
databaseUrl: '%env(DATABASE_URL)%'
services:
App\Shared\Infrastructure\Tenant\TenantRegistry:
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromConfig']
arguments:
$configs: '%tenant.dev_configs%'

View File

@@ -0,0 +1,19 @@
# Configuration des tenants en production
#
# En production, les tenants peuvent être configurés de deux façons :
# 1. Via la variable d'environnement TENANT_CONFIGS (JSON)
# 2. Via une implémentation DatabaseTenantRegistry (à implémenter)
#
# Pour l'instant, on utilise InMemoryTenantRegistry avec configuration env.
# Si aucun tenant n'est configuré, toutes les requêtes retourneront 404.
parameters:
# Format JSON attendu: [{"tenantId":"uuid","subdomain":"ecole","databaseUrl":"postgres://..."}]
tenant.prod_configs_json: '%env(default::TENANT_CONFIGS)%'
services:
App\Shared\Infrastructure\Tenant\TenantRegistry:
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromEnv']
arguments:
$configsJson: '%tenant.prod_configs_json%'

View File

@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true

View File

@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null

View File

@@ -0,0 +1,9 @@
services:
# Tenant infrastructure event subscribers
App\Shared\Infrastructure\Tenant\TenantMiddleware:
tags:
- { name: kernel.event_subscriber }
App\Shared\Infrastructure\Security\TenantAccessDeniedHandler:
tags:
- { name: kernel.event_subscriber }

View File

@@ -0,0 +1,18 @@
# Tenants pour les tests
# Utilise les mêmes tenants que dev pour les tests d'intégration
parameters:
tenant.test_configs:
- tenantId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
subdomain: 'ecole-alpha'
databaseUrl: '%env(DATABASE_URL)%'
- tenantId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901'
subdomain: 'ecole-beta'
databaseUrl: '%env(DATABASE_URL)%'
services:
App\Shared\Infrastructure\Tenant\TenantRegistry:
class: App\Shared\Infrastructure\Tenant\InMemoryTenantRegistry
factory: ['@App\Shared\Infrastructure\Tenant\TenantRegistryFactory', 'createFromConfig']
arguments:
$configs: '%tenant.test_configs%'

View File

@@ -0,0 +1,13 @@
when@dev:
web_profiler:
toolbar: true
framework:
profiler:
collect_serializer_data: true
when@test:
framework:
profiler:
collect: false
collect_serializer_data: true