refactor(container): simplify Redis pool initialization flow

- Remove redundant `$container` parameter in `RedisPoolInitializer` instantiation.
- Streamline container interactions for improved clarity and maintainability.
This commit is contained in:
2025-11-04 02:43:45 +01:00
parent 315b54a209
commit 12afbe874d
13 changed files with 1216 additions and 95 deletions

View File

@@ -272,8 +272,8 @@ docker network inspect traefik-public
### View Logs ### View Logs
```bash ```bash
# Application logs # Application logs (Production)
docker compose -f stacks/application/docker-compose.yml logs -f app docker compose -f docker-compose.base.yml -f docker-compose.production.yml logs -f php
# Traefik logs # Traefik logs
docker compose -f stacks/traefik/docker-compose.yml logs -f docker compose -f stacks/traefik/docker-compose.yml logs -f

View File

@@ -13,5 +13,6 @@ all:
# Only override-specific variables should be here # Only override-specific variables should be here
# Override system_* defaults here when Wartungsfenster abweichen # Override system_* defaults here when Wartungsfenster abweichen
# Legacy compose_file reference (deprecated - stacks now use deployment/stacks/) # Legacy compose_file reference (deprecated)
compose_file: "{{ stacks_base_path }}/application/docker-compose.yml" # Production now uses docker-compose.base.yml + docker-compose.production.yml from repository root
# compose_file: "{{ stacks_base_path }}/application/docker-compose.yml"

View File

@@ -126,11 +126,11 @@ Das Ansible Playbook führt folgende Schritte auf dem Production-Server aus:
**1. Backup aktueller Deployment-Status** **1. Backup aktueller Deployment-Status**
```bash ```bash
# Speichert aktuellen Container-Status # Speichert aktuellen Container-Status
docker compose -f ~/deployment/stacks/application/docker-compose.yml \ docker compose -f docker-compose.base.yml -f docker-compose.production.yml \
ps --format json > backups/<timestamp>/current_containers.json ps --format json > backups/<timestamp>/current_containers.json
# Speichert aktuelle docker-compose.yml Konfiguration # Speichert aktuelle docker-compose.yml Konfiguration
docker compose -f ~/deployment/stacks/application/docker-compose.yml \ docker compose -f docker-compose.base.yml -f docker-compose.production.yml \
config > backups/<timestamp>/docker-compose-config.yml config > backups/<timestamp>/docker-compose-config.yml
``` ```
@@ -186,7 +186,7 @@ replace: '\1{{ app_image }}:{{ image_tag }}'
**5. Application Stack neu starten** **5. Application Stack neu starten**
```bash ```bash
docker compose -f ~/deployment/stacks/application/docker-compose.yml \ docker compose -f docker-compose.base.yml -f docker-compose.production.yml \
up -d \ up -d \
--pull always \ --pull always \
--force-recreate \ --force-recreate \
@@ -547,7 +547,7 @@ docker pull registry.michaelschiemer.de/framework:<tag>
```bash ```bash
# Prüfe ob Regex korrekt ist # Prüfe ob Regex korrekt ist
grep -E "image:\s+registry.michaelschiemer.de/framework" \ grep -E "image:\s+registry.michaelschiemer.de/framework" \
~/deployment/stacks/application/docker-compose.yml docker-compose.base.yml docker-compose.production.yml
# Prüfe Backup für vorherige Version # Prüfe Backup für vorherige Version
ls -la ~/deployment/backups/ ls -la ~/deployment/backups/

View File

@@ -0,0 +1,33 @@
# Shared PostgreSQL Connection Override for Production
#
# This file provides network configuration for connecting to the PostgreSQL Stack.
# Used by Production Stack.
#
# Usage:
# Production Stack:
# docker compose -f docker-compose.base.yml \
# -f docker-compose.production.yml \
# -f docker-compose.postgres-override.yml up
#
# Prerequisites:
# - PostgreSQL Stack must be running (creates app-internal network)
# - app-internal network must exist as external network
# - PostgreSQL Stack: deployment/stacks/postgresql/docker-compose.yml
services:
# Production Stack Services
# These services will be merged with existing definitions from base.yml + production.yml
php:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access (merged with existing networks)
queue-worker:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access (merged with existing networks)
scheduler:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access
networks:
app-internal:
external: true
name: app-internal

View File

@@ -151,48 +151,8 @@ services:
# Mount .env file from shared directory (production environment variables) # Mount .env file from shared directory (production environment variables)
- /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro - /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro
db: # Database service removed - using external PostgreSQL Stack (deployment/stacks/postgresql/)
# Production restart policy # Connection via app-internal network using docker-compose.postgres-override.yml
restart: always
# Use Docker Secrets for database password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_user_password
secrets:
- db_user_password
# Use production PostgreSQL configuration
volumes:
- db_data:/var/lib/postgresql/data
- ./docker/postgres/postgresql.production.conf:/etc/postgresql/postgresql.conf:ro
- ./docker/postgres/init:/docker-entrypoint-initdb.d:ro
# Production resource limits
deploy:
resources:
limits:
memory: 2G
cpus: '2.0'
reservations:
memory: 1G
cpus: '1.0'
# Stricter health checks
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_DATABASE:-michaelschiemer}"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30s
# JSON logging
logging:
driver: json-file
options:
max-size: "20m"
max-file: "10"
compress: "true"
labels: "service,environment"
redis: redis:
# Production restart policy # Production restart policy
@@ -333,12 +293,91 @@ services:
# Wait for dependencies to be healthy before starting # Wait for dependencies to be healthy before starting
depends_on: depends_on:
db:
condition: service_healthy
redis: redis:
condition: service_healthy condition: service_healthy
php: php:
condition: service_healthy condition: service_healthy
# Note: PostgreSQL (postgres) is external service, connection via app-internal network
# Scheduler (Cron Jobs)
scheduler:
# Use same build as php service (has application code copied)
image: git.michaelschiemer.de:5000/framework:latest
container_name: scheduler
# Production restart policy
restart: always
# Override user setting - container must start as root for gosu to work
# The entrypoint script will use gosu to switch to appuser after setup
user: "root"
# Scheduler command - direct PHP execution
command: php console.php scheduler:run
# Production volumes
volumes:
# Mount application code from rsync deployment (read-only)
- /home/deploy/michaelschiemer/current:/var/www/html:ro
# Mount storage directory as writable volume (overlays the read-only code mount)
- storage:/var/www/html/storage:rw
# Mount var directory as writable volume for cache and logs (overlays read-only code mount)
- var-data:/var/www/html/var:rw
# Mount .env file from shared directory (production environment variables)
- /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro
environment:
- TZ=Europe/Berlin
- APP_ENV=production
- APP_DEBUG=false
# Use Docker Secrets via *_FILE pattern (Framework supports this automatically)
- DB_PASSWORD_FILE=/run/secrets/db_user_password
- REDIS_PASSWORD_FILE=/run/secrets/redis_password
- APP_KEY_FILE=/run/secrets/app_key
- VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key
secrets:
- db_user_password
- redis_password
- app_key
- vault_encryption_key
# Production resource limits
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
# Health checks
healthcheck:
test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
# JSON logging
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
compress: "true"
labels: "service,environment"
# Graceful shutdown
stop_grace_period: 30s
# Wait for dependencies to be healthy before starting
depends_on:
redis:
condition: service_healthy
php:
condition: service_healthy
# Note: PostgreSQL (postgres) is external service, connection via app-internal network
# Certbot Sidecar Container for Let's Encrypt # Certbot Sidecar Container for Let's Encrypt
certbot: certbot:
@@ -385,11 +424,5 @@ volumes:
storage: storage:
driver: local driver: local
# Database volume with backup driver (optional) # Database volume removed - using external PostgreSQL Stack
db_data: # PostgreSQL data is managed by deployment/stacks/postgresql/
driver: local
# Optional: Use external volume for easier backups
# driver_opts:
# type: none
# o: bind
# device: /mnt/db-backups/michaelschiemer-prod

View File

@@ -0,0 +1,33 @@
# Shared PostgreSQL Connection Override for Staging
#
# This file provides network configuration for connecting to the PostgreSQL Stack.
# Used by Staging Stack.
#
# Usage:
# Staging Stack:
# docker compose -f docker-compose.base.yml \
# -f docker-compose.staging.yml \
# -f docker-compose.staging-postgres-override.yml up
#
# Prerequisites:
# - PostgreSQL Stack must be running (creates app-internal network)
# - app-internal network must exist as external network
# - PostgreSQL Stack: deployment/stacks/postgresql/docker-compose.yml
services:
# Staging Stack Services
# These services will be merged with existing definitions from base.yml + staging.yml
staging-app:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal)
staging-queue-worker:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal)
staging-scheduler:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access (merged with staging-internal)
networks:
app-internal:
external: true
name: app-internal

View File

@@ -353,7 +353,7 @@ services:
- TZ=Europe/Berlin - TZ=Europe/Berlin
- APP_ENV=staging - APP_ENV=staging
- APP_DEBUG=${APP_DEBUG:-true} - APP_DEBUG=${APP_DEBUG:-true}
# Database # Database (can share with production or use separate)
- DB_HOST=${DB_HOST:-postgres} - DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432} - DB_PORT=${DB_PORT:-5432}
- DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging} - DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging}
@@ -408,7 +408,7 @@ services:
- TZ=Europe/Berlin - TZ=Europe/Berlin
- APP_ENV=staging - APP_ENV=staging
- APP_DEBUG=${APP_DEBUG:-true} - APP_DEBUG=${APP_DEBUG:-true}
# Database # Database (can share with production or use separate)
- DB_HOST=${DB_HOST:-postgres} - DB_HOST=${DB_HOST:-postgres}
- DB_PORT=${DB_PORT:-5432} - DB_PORT=${DB_PORT:-5432}
- DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging} - DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging}
@@ -471,6 +471,9 @@ networks:
external: true external: true
staging-internal: staging-internal:
driver: bridge driver: bridge
app-internal:
external: true
name: app-internal
volumes: volumes:
staging-code: staging-code:

View File

@@ -0,0 +1,262 @@
# Plan: Konsolidierung der Production Docker-Compose Konfiguration
**Datum**: 2025-11-04
**Ziel**: `docker-compose.production.yml` im Root verwenden statt `deployment/stacks/application/docker-compose.yml`
## Aktuelle Situation
### Datei 1: `docker-compose.production.yml` (Repository Root)
**Typ**: Override-Datei f?r `docker-compose.base.yml`
**Status**: ? Aktiv verwendet in Deployment-Skripten
**Services**: `web`, `php`, `db`, `redis`, `queue-worker`, `certbot`
**Struktur**: Base + Override Pattern
**Verwendung**:
```bash
docker compose -f docker-compose.base.yml -f docker-compose.production.yml up -d
```
### Datei 2: `deployment/stacks/application/docker-compose.yml`
**Typ**: Vollst?ndige Docker-Compose-Definition
**Status**: ?? Wird noch in einigen Dokumenten referenziert, aber vermutlich nicht mehr aktiv verwendet
**Services**: `app`, `nginx`, `redis`, `queue-worker`, `scheduler`
**Struktur**: Standalone-Definition
**Referenzen gefunden in**:
- `deployment/ansible/inventory/production.yml`
- `deployment/docs/reference/application-stack.md`
- `deployment/README.md`
- Verschiedene Test-Dokumente
## Vergleich der Services
| Service | docker-compose.production.yml | application/docker-compose.yml |
|---------|-------------------------------|-------------------------------|
| **PHP Runtime** | `php` | `app` |
| **Web Server** | `web` | `nginx` |
| **Database** | `db` | ? (nicht definiert, nutzt externen PostgreSQL) |
| **Redis** | `redis` | `redis` |
| **Queue Worker** | `queue-worker` | `queue-worker` |
| **Scheduler** | ? (nicht definiert) | `scheduler` ? |
| **Certbot** | `certbot` ? | ? |
## Unterschiede
### 1. Service-Namen
- **production.yml**: `web`, `php` (konsistent mit base.yml)
- **application.yml**: `nginx`, `app` (eigene Namenskonvention)
### 2. Scheduler Service
- **production.yml**: ? Fehlt
- **application.yml**: ? Vorhanden (`scheduler`)
### 3. Certbot Service
- **production.yml**: ? Vorhanden (f?r Let's Encrypt)
- **application.yml**: ? Fehlt
### 4. Database Service
- **production.yml**: ? Definiert (`db`)
- **application.yml**: ? Nutzt externen PostgreSQL-Stack (`postgres`)
### 5. Netzwerk-Konfiguration
- **production.yml**: Nutzt `frontend`, `backend`, `cache` (aus base.yml)
- **application.yml**: Nutzt `app-internal`, `traefik-public` (external networks)
## Problemanalyse
### Problem 1: Service-Namen-Mismatch
Die Service-Namen sind unterschiedlich, was zu Verwirrung f?hrt:
- `php` vs `app`
- `web` vs `nginx`
### Problem 2: Fehlender Scheduler
`docker-compose.production.yml` hat keinen `scheduler` Service, der in `application/docker-compose.yml` vorhanden ist.
### Problem 3: Unterschiedliche Architektur
- **production.yml**: Nutzt Base+Override Pattern mit `db` Service
- **application.yml**: Nutzt externen PostgreSQL-Stack (`postgres`)
## L?sungsplan
### Phase 1: Scheduler zu production.yml hinzuf?gen
**Ziel**: `scheduler` Service in `docker-compose.production.yml` hinzuf?gen
**Begr?ndung**: Der Scheduler ist wichtig f?r Cron-Jobs und sollte in Production verf?gbar sein.
**Implementierung**:
```yaml
# In docker-compose.production.yml hinzuf?gen:
scheduler:
# Production restart policy
restart: always
# Use same build as php service
image: git.michaelschiemer.de:5000/framework:latest
# Production volumes
volumes:
- /home/deploy/michaelschiemer/current:/var/www/html:ro
- storage:/var/www/html/storage:rw
- var-data:/var/www/html/var:rw
- /home/deploy/michaelschiemer/shared/.env.production:/var/www/html/.env:ro
environment:
- APP_ENV=production
- APP_DEBUG=false
# Use Docker Secrets
- DB_PASSWORD_FILE=/run/secrets/db_user_password
- REDIS_PASSWORD_FILE=/run/secrets/redis_password
- APP_KEY_FILE=/run/secrets/app_key
- VAULT_ENCRYPTION_KEY_FILE=/run/secrets/vault_encryption_key
secrets:
- db_user_password
- redis_password
- app_key
- vault_encryption_key
command: php console.php scheduler:run
# Health checks
healthcheck:
test: ["CMD-SHELL", "php -r 'exit(0);' && test -f /var/www/html/console.php || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
php:
condition: service_healthy
```
### Phase 2: Referenzen aktualisieren
**Ziel**: Alle Referenzen auf `application/docker-compose.yml` entfernen/aktualisieren
**Dateien zu aktualisieren**:
1. `deployment/ansible/inventory/production.yml` - `compose_file` entfernen/aktualisieren
2. `deployment/docs/reference/application-stack.md` - Dokumentation aktualisieren
3. `deployment/README.md` - Referenzen aktualisieren
4. Test-Dokumente - Referenzen aktualisieren
### Phase 3: application/docker-compose.yml entfernen
**Voraussetzungen**:
- ? Alle Referenzen aktualisiert
- ? Scheduler in production.yml hinzugef?gt
- ? Deployment-Skripte testen
- ? Production-Deployment erfolgreich
**Aktion**:
- `deployment/stacks/application/docker-compose.yml` l?schen
- `deployment/stacks/application/docker-compose.base.yml` pr?fen (falls redundant)
- Backup erstellen vor L?schung
### Phase 4: PostgreSQL-Stack Integration
**Wichtig**: Da `docker-compose.production.yml` aktuell einen eigenen `db` Service definiert, aber wir zum PostgreSQL-Stack verbinden wollen:
**Option A**: `db` Service aus production.yml entfernen
- Nutzt externen PostgreSQL-Stack (`postgres`)
- F?gt `app-internal` Netzwerk hinzu (via `docker-compose.postgres-override.yml`)
**Option B**: `db` Service behalten (wenn separate Production-DB gew?nscht)
- Nutzt eigenen `db` Service
- Keine Verbindung zum PostgreSQL-Stack n?tig
**Empfehlung**: Option A - Nutze externen PostgreSQL-Stack f?r Konsistenz
## Migrationspfad
### Schritt 1: Scheduler hinzuf?gen
```bash
# 1. Scheduler zu docker-compose.production.yml hinzuf?gen
# 2. Testen lokal
docker compose -f docker-compose.base.yml -f docker-compose.production.yml config
```
### Schritt 2: Referenzen pr?fen
```bash
# Alle Referenzen finden
grep -r "stacks/application/docker-compose" deployment/
grep -r "application/docker-compose" deployment/
```
### Schritt 3: Referenzen aktualisieren
- Dokumentation aktualisieren
- Ansible-Playbooks pr?fen
- Deployment-Skripte aktualisieren
### Schritt 4: Testing
```bash
# Test Production-Deployment
docker compose -f docker-compose.base.yml \
-f docker-compose.production.yml \
-f docker-compose.postgres-override.yml \
config
# Alle Services pr?fen
docker compose -f docker-compose.base.yml \
-f docker-compose.production.yml \
-f docker-compose.postgres-override.yml \
ps
```
### Schritt 5: Cleanup
```bash
# Backup erstellen
cp deployment/stacks/application/docker-compose.yml \
deployment/stacks/application/docker-compose.yml.backup
# Datei entfernen
rm deployment/stacks/application/docker-compose.yml
```
## Checkliste
- [ ] Scheduler Service zu `docker-compose.production.yml` hinzuf?gen
- [ ] Alle Referenzen auf `application/docker-compose.yml` finden
- [ ] Dokumentation aktualisieren
- [ ] Ansible-Playbooks aktualisieren
- [ ] Deployment-Skripte testen
- [ ] Production-Deployment testen
- [ ] Backup von `application/docker-compose.yml` erstellen
- [ ] `application/docker-compose.yml` entfernen
- [ ] `docker-compose.postgres-override.yml` integrieren (f?r PostgreSQL-Stack)
## Risiken
### Risiko 1: Service-Namen-?nderungen
**Problem**: Services haben unterschiedliche Namen (`php` vs `app`, `web` vs `nginx`)
**L?sung**:
- Deployment-Skripte m?ssen Service-Namen aktualisieren
- `php` statt `app`, `web` statt `nginx`
### Risiko 2: Fehlende Features
**Problem**: `application/docker-compose.yml` k?nnte Features haben, die in `production.yml` fehlen
**L?sung**:
- Vergleich durchf?hren
- Scheduler bereits identifiziert und wird hinzugef?gt
- Weitere Features pr?fen
### Risiko 3: Referenzen vergessen
**Problem**: Versteckte Referenzen k?nnten zu Fehlern f?hren
**L?sung**:
- Systematische Suche nach allen Referenzen
- Tests durchf?hren
## N?chste Schritte
1. ? Plan erstellt
2. ? Scheduler zu `docker-compose.production.yml` hinzuf?gen
3. ? Referenzen systematisch finden und aktualisieren
4. ? Testing durchf?hren
5. ? Cleanup: `application/docker-compose.yml` entfernen

View File

@@ -0,0 +1,303 @@
# Plan: Gemeinsame Docker-Compose Override f?r PostgreSQL-Verbindung
**Datum**: 2025-11-04
**Ziel**: Gemeinsame Override-Datei f?r Application- und Staging-Stack, die beide mit dem PostgreSQL-Stack verbinden k?nnen
## Aktuelle Situation
### Application Stack (`deployment/stacks/application/docker-compose.yml`)
- ? **Bereits im `app-internal` Netzwerk**
- ? **Kann sich bereits mit PostgreSQL verbinden**
- Services: `app`, `nginx`, `queue-worker`, `scheduler`
- Netzwerk: `app-internal` (external: true)
### Staging Stack (`docker-compose.staging.yml`)
- ? **Nur im `staging-internal` Netzwerk**
- ? **Kann sich NICHT mit PostgreSQL verbinden**
- Services: `staging-app`, `staging-nginx`, `staging-queue-worker`, `staging-scheduler`
- Netzwerk: `staging-internal` (driver: bridge)
### PostgreSQL Stack (`deployment/stacks/postgresql/docker-compose.yml`)
- Service: `postgres`
- Netzwerk: `app-internal` (external: true)
## L?sung: Gemeinsame Override-Datei
### Datei-Struktur
```
Repository Root/
??? docker-compose.base.yml
??? docker-compose.staging.yml
??? docker-compose.postgres-override.yml # NEU: Gemeinsame Override
??? deployment/
??? stacks/
??? application/
? ??? docker-compose.yml
??? postgresql/
??? docker-compose.yml
```
### Vorteile
1. ? **DRY-Prinzip**: Netzwerk-Konfiguration zentral
2. ? **Wartbarkeit**: ?nderungen an einem Ort
3. ? **Konsistenz**: Beide Stacks nutzen identische Konfiguration
4. ? **Flexibilit?t**: Kann einfach aktiviert/deaktiviert werden
## Implementierungsplan
### 1. Neue Datei: `docker-compose.postgres-override.yml`
**Inhalt**: Netzwerk-Definition und Service-Overrides f?r PostgreSQL-Verbindung
```yaml
# Shared PostgreSQL Connection Override
# Usage:
# Application Stack:
# docker compose -f deployment/stacks/application/docker-compose.yml \
# -f docker-compose.postgres-override.yml up
#
# Staging Stack:
# docker compose -f docker-compose.base.yml \
# -f docker-compose.staging.yml \
# -f docker-compose.postgres-override.yml up
services:
# Application Stack Services
app:
networks:
app-internal: {} # Ensure app-internal network is used
nginx:
networks:
app-internal: {} # Ensure app-internal network is used
queue-worker:
networks:
app-internal: {} # Ensure app-internal network is used
scheduler:
networks:
app-internal: {} # Ensure app-internal network is used
# Staging Stack Services
staging-app:
networks:
app-internal: {} # Add app-internal network
staging-queue-worker:
networks:
app-internal: {} # Add app-internal network
staging-scheduler:
networks:
app-internal: {} # Add app-internal network
networks:
app-internal:
external: true
name: app-internal
```
### 2. Problem: Service-Namen sind unterschiedlich
**Application Stack**: `app`, `nginx`, `queue-worker`, `scheduler`
**Staging Stack**: `staging-app`, `staging-nginx`, `staging-queue-worker`, `staging-scheduler`
**L?sung**: Die Override-Datei kann beide Service-Namen unterst?tzen (Docker Compose ignoriert nicht-existierende Services).
### 3. Alternative: Zwei separate Override-Dateien
**Option A**: Eine Datei f?r beide (einfacher)
- ? Einfacher zu warten
- ?? Enth?lt Service-Definitionen f?r beide Stacks
**Option B**: Zwei Dateien (sauberer)
- `docker-compose.postgres-override-app.yml` (f?r Application Stack)
- `docker-compose.postgres-override-staging.yml` (f?r Staging Stack)
- ? Klarer getrennt
- ?? Mehr Dateien
**Empfehlung**: Option A - eine Datei, da die Konfiguration identisch ist
## Detaillierte Implementierung
### Datei: `docker-compose.postgres-override.yml`
```yaml
# Shared PostgreSQL Connection Override
#
# This file provides network configuration for connecting to the PostgreSQL Stack.
# It can be used by both Application Stack and Staging Stack.
#
# Usage:
# Application Stack:
# docker compose -f deployment/stacks/application/docker-compose.yml \
# -f docker-compose.postgres-override.yml up
#
# Staging Stack:
# docker compose -f docker-compose.base.yml \
# -f docker-compose.staging.yml \
# -f docker-compose.postgres-override.yml up
#
# Prerequisites:
# - PostgreSQL Stack must be running (creates app-internal network)
# - app-internal network must exist as external network
services:
# Application Stack Services
# (These will be ignored if not present in the base compose file)
app:
networks:
app-internal: {} # Ensure app-internal network is available
nginx:
networks:
app-internal: {} # Ensure app-internal network is available
queue-worker:
networks:
app-internal: {} # Ensure app-internal network is available
scheduler:
networks:
app-internal: {} # Ensure app-internal network is available
# Staging Stack Services
# (These will be ignored if not present in the base compose file)
staging-app:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access
staging-queue-worker:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access
staging-scheduler:
networks:
app-internal: {} # Add app-internal network for PostgreSQL access
networks:
app-internal:
external: true
name: app-internal
```
### Wichtig: Netzwerk-Merge-Verhalten
Docker Compose **merged** Netzwerke, wenn ein Service mehrere Netzwerke hat:
```yaml
# In docker-compose.staging.yml
staging-app:
networks:
- staging-internal
# In docker-compose.postgres-override.yml
staging-app:
networks:
app-internal: {}
# Ergebnis: staging-app ist in BEIDEN Netzwerken
# - staging-internal (aus staging.yml)
# - app-internal (aus override.yml)
```
## Verwendung
### Application Stack
**Aktuell**:
```bash
cd deployment/stacks/application
docker compose -f docker-compose.yml up -d
```
**Mit Override** (optional, da bereits im app-internal Netzwerk):
```bash
cd deployment/stacks/application
docker compose -f docker-compose.yml \
-f ../../docker-compose.postgres-override.yml up -d
```
### Staging Stack
**Aktuell**:
```bash
docker compose -f docker-compose.base.yml \
-f docker-compose.staging.yml up -d
```
**Mit Override** (f?r PostgreSQL-Verbindung):
```bash
docker compose -f docker-compose.base.yml \
-f docker-compose.staging.yml \
-f docker-compose.postgres-override.yml up -d
```
## Deployment-Skripte anpassen
### Ansible Playbooks
**`deployment/ansible/playbooks/deploy-staging.yml`**:
```yaml
- name: Start staging stack
command: >
docker compose
-f docker-compose.base.yml
-f docker-compose.staging.yml
-f docker-compose.postgres-override.yml
up -d
args:
chdir: "{{ staging_deploy_path }}"
```
**`deployment/ansible/playbooks/deploy-production.yml`**:
```yaml
- name: Start application stack
command: >
docker compose
-f docker-compose.yml
-f ../../docker-compose.postgres-override.yml
up -d
args:
chdir: "{{ app_deploy_path }}/deployment/stacks/application"
```
## Vorteile dieser L?sung
1. ? **Zentrale Konfiguration**: Netzwerk-Setup an einem Ort
2. ? **Wiederverwendbar**: Beide Stacks nutzen die gleiche Datei
3. ? **Optional**: Kann bei Bedarf weggelassen werden
4. ? **Flexibel**: Einfach erweiterbar f?r weitere Services
5. ? **DRY**: Keine Duplikation von Netzwerk-Konfiguration
## Nachteile / ?berlegungen
1. ?? **Pfad-Abh?ngigkeit**: Relative Pfade in verschiedenen Verzeichnissen
- **L?sung**: Absolute Pfade oder von Repository-Root aus ausf?hren
2. ?? **Service-Namen**: Docker Compose ignoriert nicht-existierende Services
- **Status**: ? Funktioniert - Docker Compose merged nur existierende Services
3. ?? **Netzwerk muss existieren**: `app-internal` muss vorher erstellt sein
- **Status**: ? Wird automatisch vom PostgreSQL-Stack erstellt
## Alternative: Inline-Netzwerk-Konfiguration
**Alternativ** k?nnte das Netzwerk auch direkt in den jeweiligen Compose-Dateien definiert werden:
- `docker-compose.staging.yml`: Netzwerk `app-internal` hinzuf?gen
- `deployment/stacks/application/docker-compose.yml`: Bereits vorhanden
**Nachteil**: Duplikation, weniger wartbar
**Vorteil**: Keine zus?tzliche Override-Datei n?tig
## Empfehlung
**Gemeinsame Override-Datei** ist die beste L?sung, weil:
1. **DRY-Prinzip**: Keine Duplikation
2. **Wartbarkeit**: ?nderungen an einem Ort
3. **Flexibilit?t**: Kann optional verwendet werden
4. **Klarheit**: Explizite PostgreSQL-Verbindung sichtbar
## N?chste Schritte
1. ? Plan erstellt
2. ? Implementierung: `docker-compose.postgres-override.yml` erstellen
3. ? Staging Stack: Verwendung in Dokumentation/Deployment-Skripten
4. ? Application Stack: Optional (bereits funktionsf?hig, aber f?r Konsistenz)
5. ? Testing: Beide Stacks mit Override testen
6. ? Dokumentation: README-Dateien aktualisieren

View File

@@ -0,0 +1,185 @@
# Analyse: Datenbankverbindung im staging-app Container
**Datum**: 2025-11-04
**Problem**: `staging-app` Container kann nicht zur Datenbank verbinden
**Fehlermeldung**: `could not translate host name "postgres" to address: Name or service not known`
## Problem-Identifikation
### Symptom
Der `staging-app` Container schl?gt fehl mit folgendem Fehler:
```
[2025-11-04 01:32:28] [ERROR] appConsoleHandler --- Failed to invoke initializer method __invoke for class App\Framework\Database\ConnectionInitializer: Failed to connect to database 'michaelschiemer' on 'postgres': SQLSTATE[08006] [7] could not translate host name "postgres" to address: Name or service not known
```
### Root Cause Analysis
#### 1. **Fehlender PostgreSQL-Service im Staging-Setup**
**Problem**: In `docker-compose.staging.yml` wird `DB_HOST=${DB_HOST:-postgres}` konfiguriert (Zeilen 32, 357, 412), aber es existiert kein PostgreSQL-Service mit dem Namen `postgres` im Staging-Setup.
**Beweis**:
- `docker-compose.staging.yml` Zeile 32: `DB_HOST=${DB_HOST:-postgres}`
- `docker-compose.staging.yml` Zeilen 456-458: Der `db` Service aus `docker-compose.base.yml` wird explizit deaktiviert:
```yaml
db:
profiles:
- never
```
#### 2. **Netzwerk-Isolation**
**Problem**: Der `staging-app` Container ist nur im `staging-internal` Netzwerk (Zeile 18), aber es gibt keinen Datenbank-Service in diesem Netzwerk.
**Netzwerk-Konfiguration**:
- `staging-app`: Netzwerk `staging-internal` (Zeile 18)
- `staging-redis`: Netzwerk `staging-internal` (Zeile 302)
- **Kein PostgreSQL-Service** im `staging-internal` Netzwerk
#### 3. **Vergleich mit Production-Setup**
**Production (`docker-compose.production.yml`)**:
- Zeile 154-196: `db` Service ist definiert und aktiv
- `db` Service ist im `backend` Netzwerk (aus `docker-compose.base.yml`)
- PHP-Services k?nnen sich mit `DB_HOST=postgres` verbinden, da der Service existiert
**Staging (`docker-compose.staging.yml`)**:
- Zeilen 456-458: `db` Service wird deaktiviert
- Kein PostgreSQL-Service definiert
- Services versuchen trotzdem, sich mit `postgres` zu verbinden
## Detaillierte Analyse der Konfiguration
### Staging-Services, die DB_HOST verwenden
1. **staging-app** (Zeile 13-205)
- `DB_HOST=${DB_HOST:-postgres}` (Zeile 32)
- Netzwerk: `staging-internal`
2. **staging-queue-worker** (Zeile 346-399)
- `DB_HOST=${DB_HOST:-postgres}` (Zeile 357)
- Netzwerk: `staging-internal`
3. **staging-scheduler** (Zeile 401-447)
- `DB_HOST=${DB_HOST:-postgres}` (Zeile 412)
- Netzwerk: `staging-internal`
### Fehlende Komponenten
1. **PostgreSQL-Service**: Nicht definiert in Staging-Konfiguration
2. **Netzwerk-Verbindung**: Kein Datenbank-Service im `staging-internal` Netzwerk
3. **Dependencies**: Keine `depends_on` f?r Datenbank-Service
## L?sungsoptionen
### Option 1: Eigener PostgreSQL-Service f?r Staging (Empfohlen)
**Vorteile**:
- Vollst?ndige Isolation zwischen Staging und Production
- Keine Abh?ngigkeit von externen Datenbanken
- Konsistente Konfiguration mit anderen Services
**Implementierung**:
```yaml
# In docker-compose.staging.yml hinzuf?gen:
staging-postgres:
image: postgres:16-alpine
container_name: staging-postgres
restart: unless-stopped
networks:
- staging-internal
environment:
- TZ=Europe/Berlin
- POSTGRES_DB=${DB_DATABASE:-michaelschiemer_staging}
- POSTGRES_USER=${DB_USERNAME}
- POSTGRES_PASSWORD_FILE=/run/secrets/db_user_password
secrets:
- db_user_password
volumes:
- staging-postgres-data:/var/lib/postgresql/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE:-michaelschiemer_staging}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
volumes:
staging-postgres-data:
name: staging-postgres-data
# DB_HOST in allen Services ?ndern:
- DB_HOST=staging-postgres # statt postgres
```
### Option 2: Externe Datenbank verwenden
**Vorteile**:
- Keine zus?tzliche Container-Verwaltung
- Kann auf bestehende Datenbank-Infrastruktur zur?ckgreifen
**Nachteile**:
- Abh?ngigkeit von externer Infrastruktur
- Netzwerk-Konfiguration komplexer (muss extern erreichbar sein)
**Implementierung**:
- `DB_HOST` auf externe IP/Hostname setzen
- Sicherstellen, dass `staging-internal` Netzwerk Zugriff hat (oder externes Netzwerk verwenden)
### Option 3: Base-Service `db` aktivieren und umbenennen
**Vorteile**:
- Nutzt bestehende Konfiguration aus `docker-compose.base.yml`
- Minimaler Aufwand
**Nachteile**:
- Kann Konflikte mit anderen Umgebungen geben
- Nicht so isoliert wie Option 1
**Implementierung**:
```yaml
# In docker-compose.staging.yml:
staging-postgres:
extends:
service: db
file: docker-compose.base.yml
container_name: staging-postgres
networks:
- staging-internal
# DB_HOST auf staging-postgres ?ndern
```
## Empfohlene L?sung
**Option 1** ist die beste L?sung, weil:
1. **Vollst?ndige Isolation**: Staging hat eigene Datenbank
2. **Konsistenz**: Gleiche Struktur wie `staging-redis`
3. **Einfachheit**: Alle Services im gleichen Netzwerk
4. **Wartbarkeit**: Klare Struktur, leicht zu verstehen
## N?chste Schritte
1. ? Analyse abgeschlossen
2. ? Implementierung: PostgreSQL-Service f?r Staging hinzuf?gen
3. ? `DB_HOST` in allen Staging-Services auf `staging-postgres` ?ndern
4. ? `depends_on` f?r Datenbank-Service hinzuf?gen
5. ? Volume f?r PostgreSQL-Daten definieren
6. ? Testing: Verbindung testen nach Implementierung
## Zus?tzliche ?berlegungen
### Netzwerk-Architektur
- `staging-internal`: Interne Services (app, postgres, redis, queue-worker, scheduler)
- `traefik-public`: Externes Netzwerk f?r Traefik (nur staging-nginx)
### Sicherheit
- Docker Secrets f?r Datenbank-Passwort verwenden (bereits konfiguriert)
- Keine Ports nach au?en f?r PostgreSQL (nur intern)
- Gesundheitschecks f?r Datenbank-Service
### Performance
- PostgreSQL-Volumes f?r Persistenz
- Gesundheitschecks f?r alle Services
- `depends_on` mit `condition: service_healthy` f?r Datenbank

View File

@@ -0,0 +1,209 @@
# Plan: Verbindung zum PostgreSQL-Stack f?r Staging
**Datum**: 2025-11-04
**Ziel**: Staging-Services sollen sich mit dem PostgreSQL-Container aus dem PostgreSQL-Stack verbinden k?nnen
## Aktuelle Situation
### PostgreSQL-Stack (`deployment/stacks/postgresql/docker-compose.yml`)
- **Service-Name**: `postgres`
- **Container-Name**: `postgres`
- **Netzwerk**: `app-internal` (external: true)
- **Port**: 5432
### Staging-Stack (`docker-compose.staging.yml`)
- **Netzwerk**: `staging-internal` (driver: bridge, **nicht external**)
- **Problem**: `staging-app` ist nur im `staging-internal` Netzwerk und kann daher den `postgres` Container nicht erreichen
### Application-Stack (Production) (`deployment/stacks/application/docker-compose.yml`)
- **Netzwerk**: `app-internal` (external: true)
- **Status**: ? Kann sich mit `postgres` verbinden (gleiches Netzwerk)
## L?sung: Multi-Network-Architektur
Die Staging-Services m?ssen **beide Netzwerke** nutzen:
1. **`app-internal`** (external) - f?r Zugriff auf PostgreSQL-Stack
2. **`staging-internal`** (internal) - f?r interne Staging-Services (Redis, etc.)
## Implementierungsplan
### 1. Netzwerk-Konfiguration anpassen
**?nderungen in `docker-compose.staging.yml`:**
#### A. `app-internal` Netzwerk als external definieren
```yaml
networks:
traefik-public:
external: true
staging-internal:
driver: bridge
app-internal: # NEU: F?r PostgreSQL-Zugriff
external: true
name: app-internal
```
#### B. Staging-Services an beide Netzwerke anschlie?en
**Services die PostgreSQL ben?tigen:**
- `staging-app` ? `app-internal` + `staging-internal`
- `staging-queue-worker` ? `app-internal` + `staging-internal`
- `staging-scheduler` ? `app-internal` + `staging-internal`
**Services die KEINE PostgreSQL ben?tigen:**
- `staging-nginx` ? bleibt bei `traefik-public` + `staging-internal`
- `staging-redis` ? bleibt bei `staging-internal` (nur intern)
### 2. DB_HOST Konfiguration
**Aktuell**: `DB_HOST=${DB_HOST:-postgres}`
**Status**: ? Korrekt - `postgres` ist der Service-Name im PostgreSQL-Stack
**Keine ?nderung n?tig**, da `postgres` der korrekte Hostname ist, sobald die Services im `app-internal` Netzwerk sind.
### 3. Dependencies hinzuf?gen
**Optional**: `depends_on` f?r PostgreSQL k?nnte hinzugef?gt werden, aber da der PostgreSQL-Stack extern ist, sollte besser auf Health-Checks verzichtet werden (externer Service kann nicht direkt abh?ngig sein).
**Alternative**: Warten auf PostgreSQL-Verf?gbarkeit im Entrypoint-Script.
### 4. Netzwerk-Architektur
```
???????????????????????????????????????????????????????????
? PostgreSQL Stack (deployment/stacks/postgresql/) ?
? ??????????????????????????????????????????????????????? ?
? ? postgres Container ? ?
? ? Network: app-internal (external) ? ?
? ??????????????????????????????????????????????????????? ?
???????????????????????????????????????????????????????????
?
?
? app-internal (external network)
?
????????????????????????????????????????????????????????????
? Staging Stack (docker-compose.staging.yml) ?
? ?
? ???????????????????? ???????????????????? ?
? ? staging-app ? ? staging-queue- ? ?
? ? Networks: ? ? worker ? ?
? ? ? app-internal ? ? Networks: ? ?
? ? ? staging-internal? ? ? app-internal ? ?
? ???????????????????? ? ? staging-internal? ?
? ???????????????????? ?
? ?
? ???????????????????? ???????????????????? ?
? ? staging-redis ? ? staging-nginx ? ?
? ? Networks: ? ? Networks: ? ?
? ? ? staging- ? ? ? traefik-public ? ?
? ? internal ? ? ? staging-internal? ?
? ???????????????????? ???????????????????? ?
????????????????????????????????????????????????????????????
```
## Konkrete ?nderungen
### Datei: `docker-compose.staging.yml`
#### 1. Networks-Sektion erweitern
```yaml
networks:
traefik-public:
external: true
staging-internal:
driver: bridge
app-internal: # NEU: F?r PostgreSQL-Zugriff
external: true
name: app-internal
```
#### 2. staging-app: app-internal Netzwerk hinzuf?gen
```yaml
staging-app:
networks:
- app-internal # NEU: F?r PostgreSQL
- staging-internal # Bestehend: F?r Redis
```
#### 3. staging-queue-worker: app-internal Netzwerk hinzuf?gen
```yaml
staging-queue-worker:
networks:
- app-internal # NEU: F?r PostgreSQL
- staging-internal # Bestehend: F?r Redis
```
#### 4. staging-scheduler: app-internal Netzwerk hinzuf?gen
```yaml
staging-scheduler:
networks:
- app-internal # NEU: F?r PostgreSQL
- staging-internal # Bestehend: F?r Redis
```
## Vorteile dieser L?sung
1. ? **Keine ?nderung am PostgreSQL-Stack n?tig**
2. ? **Staging nutzt bestehende PostgreSQL-Infrastruktur**
3. ? **Isolation bleibt erhalten**: Staging-Services kommunizieren intern ?ber `staging-internal`
4. ? **Konsistent mit Production**: Production nutzt ebenfalls `app-internal`
5. ? **Minimale ?nderungen**: Nur Netzwerk-Konfiguration, keine DB_HOST-?nderung n?tig
## Nachteile / ?berlegungen
1. ?? **Netzwerk-Abh?ngigkeit**: Staging h?ngt von externem `app-internal` Netzwerk ab
- **L?sung**: Netzwerk muss vor Staging-Start existieren (sollte automatisch sein)
2. ?? **Shared Database**: Staging und Production k?nnten theoretisch die gleiche DB nutzen
- **Aktuell**: `DB_DATABASE=${DB_DATABASE:-michaelschiemer_staging}` verhindert dies
- **Empfehlung**: Separate Datenbank f?r Staging (`michaelschiemer_staging`)
## Testing-Schritte nach Implementierung
1. **Netzwerk-Verf?gbarkeit pr?fen**:
```bash
docker network inspect app-internal | grep postgres
```
2. **Container-Verbindung testen**:
```bash
docker exec staging-app ping -c 1 postgres
```
3. **PostgreSQL-Verbindung testen**:
```bash
docker exec staging-app php -r "
\$dsn = 'pgsql:host=postgres;port=5432;dbname=' . getenv('DB_DATABASE');
\$pdo = new PDO(\$dsn, getenv('DB_USERNAME'), file_get_contents(getenv('DB_PASSWORD_FILE')));
echo 'Connection successful: ' . \$pdo->query('SELECT version()')->fetchColumn();
"
```
4. **Application-Logs pr?fen**:
```bash
docker logs staging-app | grep -i "database\|postgres\|connection"
```
## N?chste Schritte
1. ? Plan erstellt
2. ? Implementierung: Netzwerk-Konfiguration anpassen
3. ? Testing: Verbindung testen
4. ? Dokumentation: README aktualisieren
## Alternative Ans?tze (nicht empfohlen)
### Alternative 1: Eigener PostgreSQL f?r Staging
- **Vorteil**: Vollst?ndige Isolation
- **Nachteil**: Mehr Ressourcen, mehr Wartungsaufwand
- **Status**: Nicht gew?nscht (Benutzer m?chte PostgreSQL-Stack nutzen)
### Alternative 2: Externe PostgreSQL-URL
- **Vorteil**: Flexibler
- **Nachteil**: Komplexer, erfordert externe Netzwerk-Konfiguration
- **Status**: Nicht n?tig (Docker-Netzwerke sind ausreichend)

View File

@@ -51,13 +51,15 @@ final readonly class AppBootstrapper
// Initialize environment with encryption support // Initialize environment with encryption support
$env = $this->initializeEnvironment(); $env = $this->initializeEnvironment();
// Workaround: If REDIS_PASSWORD_FILE is not set but expected file exists, set it manually // Workaround: If REDIS_PASSWORD_FILE is not set but expected file exists, set it manually
// This handles cases where Docker Compose doesn't set the variable correctly // This handles cases where Docker Compose doesn't set the variable correctly
$expectedRedisPasswordFile = '/run/secrets/redis_password'; #$expectedRedisPasswordFile = '/run/secrets/redis_password';
if (!$env->has('REDIS_PASSWORD_FILE') && file_exists($expectedRedisPasswordFile)) { #if (!$env->has('REDIS_PASSWORD_FILE') && file_exists($expectedRedisPasswordFile)) {
// Add REDIS_PASSWORD_FILE to environment if file exists # // Add REDIS_PASSWORD_FILE to environment if file exists
$env = $env->withVariable('REDIS_PASSWORD_FILE', $expectedRedisPasswordFile); # #$env = $env->withVariable('REDIS_PASSWORD_FILE', $expectedRedisPasswordFile);
} #
#}
// Make Environment available throughout the application // Make Environment available throughout the application
$this->container->instance(Environment::class, $env); $this->container->instance(Environment::class, $env);
@@ -128,12 +130,13 @@ final readonly class AppBootstrapper
{ {
$this->collector->startTiming('bootstrap', PerformanceCategory::SYSTEM); $this->collector->startTiming('bootstrap', PerformanceCategory::SYSTEM);
$this->bootstrapper->bootstrap($this->basePath, $this->collector); // Get Environment from container (registered in constructor)
$env = $this->container->get(Environment::class);
$this->bootstrapper->bootstrap($this->basePath, $this->collector, $env);
$this->collector->endTiming('bootstrap'); $this->collector->endTiming('bootstrap');
// Initialize secrets management after container is bootstrapped // Initialize secrets management after container is bootstrapped
$env = $this->container->get(Environment::class);
$this->initializeSecretsManagement($env); $this->initializeSecretsManagement($env);
// ErrorHandler wird jetzt kontextabhängig registriert // ErrorHandler wird jetzt kontextabhängig registriert

View File

@@ -42,16 +42,17 @@ final readonly class ContainerBootstrapper
public function bootstrap( public function bootstrap(
string $basePath, string $basePath,
PerformanceCollectorInterface $collector, PerformanceCollectorInterface $collector,
Environment $environment,
): Container { ): Container {
// Temporarily disable compiled container to fix bootstrap issues // Temporarily disable compiled container to fix bootstrap issues
// TODO: Re-enable once container interface compatibility is fixed // TODO: Re-enable once container interface compatibility is fixed
// $optimizedContainer = $this->tryLoadCompiledContainer($basePath, $collector); // $optimizedContainer = $this->tryLoadCompiledContainer($basePath, $collector, $environment);
// if ($optimizedContainer !== null) { // if ($optimizedContainer !== null) {
// return $optimizedContainer; // return $optimizedContainer;
// } // }
// Fallback to fresh container bootstrap // Fallback to fresh container bootstrap
return $this->bootstrapFreshContainer($basePath, $collector); return $this->bootstrapFreshContainer($basePath, $collector, $environment);
} }
/** /**
@@ -59,7 +60,8 @@ final readonly class ContainerBootstrapper
*/ */
private function tryLoadCompiledContainer( private function tryLoadCompiledContainer(
string $basePath, string $basePath,
PerformanceCollectorInterface $collector PerformanceCollectorInterface $collector,
Environment $environment,
): ?Container { ): ?Container {
try { try {
$cacheDir = sys_get_temp_dir() . '/framework-cache'; $cacheDir = sys_get_temp_dir() . '/framework-cache';
@@ -71,7 +73,7 @@ final readonly class ContainerBootstrapper
} }
// Create temporary fresh container to validate against // Create temporary fresh container to validate against
$tempContainer = $this->createFreshContainer($basePath, $collector); $tempContainer = $this->createFreshContainer($basePath, $collector, $environment);
// Create compiler for validation // Create compiler for validation
$reflectionProvider = new CachedReflectionProvider(); $reflectionProvider = new CachedReflectionProvider();
@@ -86,8 +88,11 @@ final readonly class ContainerBootstrapper
// Load and return compiled container // Load and return compiled container
$compiledContainer = ContainerCompiler::load($compiledPath); $compiledContainer = ContainerCompiler::load($compiledPath);
// Environment must be registered first as runtime data that can't be compiled
$compiledContainer->instance(Environment::class, $environment);
// Add runtime instances that can't be compiled // Add runtime instances that can't be compiled
$this->addRuntimeInstances($compiledContainer, $basePath, $collector); $this->registerRuntimeServices($compiledContainer, $basePath, $collector, $environment);
return $compiledContainer; return $compiledContainer;
@@ -108,9 +113,10 @@ final readonly class ContainerBootstrapper
*/ */
private function bootstrapFreshContainer( private function bootstrapFreshContainer(
string $basePath, string $basePath,
PerformanceCollectorInterface $collector PerformanceCollectorInterface $collector,
Environment $environment,
): Container { ): Container {
$container = $this->createFreshContainer($basePath, $collector); $container = $this->createFreshContainer($basePath, $collector, $environment);
// Compile for next request (async in production) // Compile for next request (async in production)
$this->compileContainerAsync($container); $this->compileContainerAsync($container);
@@ -123,47 +129,87 @@ final readonly class ContainerBootstrapper
*/ */
private function createFreshContainer( private function createFreshContainer(
string $basePath, string $basePath,
PerformanceCollectorInterface $collector PerformanceCollectorInterface $collector,
Environment $environment,
): DefaultContainer { ): DefaultContainer {
// Use the existing container or create new DefaultContainer // Use the existing container or create new DefaultContainer
$container = $this->container instanceof DefaultContainer $container = $this->container instanceof DefaultContainer
? $this->container ? $this->container
: new DefaultContainer(); : new DefaultContainer();
$this->addRuntimeInstances($container, $basePath, $collector); // Environment must be registered first as it's required by other services
// Check if already registered (e.g. by AppBootstrapper) to avoid overwriting
if (! $container->has(Environment::class)) {
$container->instance(Environment::class, $environment);
}
$this->registerRuntimeServices($container, $basePath, $collector, $environment);
$this->autowire($container); $this->autowire($container);
return $container; return $container;
} }
/** /**
* Add instances that must be created at runtime * Register all runtime services that must be created at runtime
*/ */
private function addRuntimeInstances( private function registerRuntimeServices(
Container $container, Container $container,
string $basePath, string $basePath,
PerformanceCollectorInterface $collector PerformanceCollectorInterface $collector,
Environment $environment,
): void {
$this->registerCoreServices($container, $basePath, $collector);
$this->registerRedisAndCache($container, $collector, $environment);
$this->registerHttpServices($container);
$this->registerDatabaseServices($container, $environment);
}
/**
* Register core services: Clock, PathProvider, Logger, PerformanceCollector
*/
private function registerCoreServices(
Container $container,
string $basePath,
PerformanceCollectorInterface $collector,
): void { ): void {
// Core services that need runtime data
// Clock must be registered first as it's required by Logger // Clock must be registered first as it's required by Logger
// Only create if not already registered (e.g. by ClockInitializer) // Only create if not already registered (e.g. by ClockInitializer)
if (! $container->has(Clock::class)) { if (! $container->has(Clock::class)) {
$container->instance(Clock::class, new SystemClock()); $container->instance(Clock::class, new SystemClock());
} }
$clock = $container->get(Clock::class);
$container->instance(PathProvider::class, new PathProvider($basePath)); $container->instance(PathProvider::class, new PathProvider($basePath));
$init = $container->get(LoggerInitializer::class);
$logger = $container->invoker->invoke(LoggerInitializer::class, '__invoke');
$container->instance(Logger::class, $logger);
$container->instance(PerformanceCollectorInterface::class, $collector); $container->instance(PerformanceCollectorInterface::class, $collector);
$pool = new RedisPoolInitializer($container->get(Environment::class))->initialize();
$container->instance(Cache::class, new CacheInitializer($collector, $container, $pool)());
// Initialize Logger
$logger = $container->invoker->invoke(LoggerInitializer::class, '__invoke');
$container->instance(Logger::class, $logger);
}
/**
* Register Redis connection pool and Cache services
* Requires Environment to be registered in container
*/
private function registerRedisAndCache(
Container $container,
PerformanceCollectorInterface $collector,
Environment $environment,
): void {
$pool = new RedisPoolInitializer($environment)->initialize();
$container->instance(Cache::class, new CacheInitializer($collector, $container, $pool)());
}
/**
* Register HTTP-related services
*
* TODO: Remove manual Request binding once Discovery system properly handles RequestFactory Initializer
* The RequestFactory should be discovered via Initializer attribute and registered automatically
*/
private function registerHttpServices(Container $container): void {
$container->instance(ResponseEmitter::class, new ResponseEmitter()); $container->instance(ResponseEmitter::class, new ResponseEmitter());
// TEMPORARY FIX: Manual RequestFactory binding until Discovery issue is resolved // TEMPORARY FIX: Manual Request binding until Discovery issue is resolved
// This should be handled by RequestFactory Initializer via Discovery system
$container->singleton(Request::class, function ($container) { $container->singleton(Request::class, function ($container) {
error_log("ContainerBootstrapper: Creating Request singleton"); error_log("ContainerBootstrapper: Creating Request singleton");
@@ -179,13 +225,24 @@ final readonly class ContainerBootstrapper
return $request; return $request;
}); });
}
/**
* Register database-related services
*
* TODO: Remove manual DatabasePlatform binding once Discovery system properly handles DatabasePlatformInitializer
* The DatabasePlatformInitializer should be discovered via Initializer attribute and registered automatically
*/
private function registerDatabaseServices(
Container $container,
Environment $environment,
): void {
// TEMPORARY FIX: Manual DatabasePlatform binding until Initializer Discovery issue is resolved // TEMPORARY FIX: Manual DatabasePlatform binding until Initializer Discovery issue is resolved
$container->singleton(\App\Framework\Database\Platform\DatabasePlatform::class, function ($container) { // This should be handled by DatabasePlatformInitializer via Discovery system
$container->singleton(\App\Framework\Database\Platform\DatabasePlatform::class, function ($container) use ($environment) {
error_log("ContainerBootstrapper: Creating DatabasePlatform singleton"); error_log("ContainerBootstrapper: Creating DatabasePlatform singleton");
$env = $container->get(\App\Framework\Config\Environment::class); $driver = $environment->get('DB_DRIVER', 'pgsql');
$driver = $env->get('DB_DRIVER', 'pgsql');
error_log("ContainerBootstrapper: DB_DRIVER = {$driver}"); error_log("ContainerBootstrapper: DB_DRIVER = {$driver}");
@@ -200,7 +257,6 @@ final readonly class ContainerBootstrapper
return $platform; return $platform;
}); });
} }
/** /**