From 70e45fb56e63fd5e2dbbfece988d12a9f336ca95 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Mon, 27 Oct 2025 22:23:18 +0100 Subject: [PATCH] fix(Discovery): Add comprehensive debug logging for router initialization - Add initializer count logging in DiscoveryServiceBootstrapper - Add route structure analysis in RouterSetup - Add request parameter logging in HttpRouter - Update PHP production config for better OPcache handling - Fix various config and error handling improvements --- .../infrastructure/DEPLOYMENT_ANALYSIS.md | 554 ++++++++++++++++++ .../infrastructure/DEPLOYMENT_FIX_SUMMARY.md | 286 +++++++++ deployment/infrastructure/deploy.sh | 17 + deployment/infrastructure/logs.sh | 14 + deployment/infrastructure/nginx-logs.sh | 14 + deployment/infrastructure/restart.sh | 21 + deployment/infrastructure/scripts/deploy.sh | 32 + .../infrastructure/scripts/quick-sync.sh | 46 ++ .../infrastructure/scripts/update-env.sh | 74 +++ deployment/infrastructure/status.sh | 11 + docker/php/php.dev.ini | 33 ++ docker/php/php.development.ini | 29 - docker/php/php.prod.ini | 13 +- .../Security/ValueObjects/RequestContext.php | 18 +- src/Framework/Analytics/AnalyticsConfig.php | 26 +- src/Framework/Config/AppConfig.php | 8 +- src/Framework/Config/EncryptedEnvLoader.php | 3 +- .../Config/TypedConfigInitializer.php | 4 +- .../AnalyticsCollectionMiddleware.php | 12 +- .../Layout/Commands/LayoutTestCommand.php | 9 +- src/Framework/Context/ExecutionContext.php | 9 +- src/Framework/Core/AppBootstrapper.php | 5 +- src/Framework/Core/ContainerBootstrapper.php | 1 + .../Database/Driver/PostgresDriver.php | 4 +- .../Commands/MakeMigrationFromDiffCommand.php | 6 +- .../Migration/Services/MigrationValidator.php | 6 +- .../Schema/Commands/SchemaDiffCommand.php | 6 +- .../DiscoveryServiceBootstrapper.php | 20 + src/Framework/ErrorAggregation/ErrorEvent.php | 8 +- src/Framework/ErrorHandling/ErrorHandler.php | 16 +- src/Framework/ErrorHandling/ErrorLogger.php | 96 +-- .../ErrorHandling/SecurityEventHandler.php | 20 +- .../ErrorHandling/SecurityEventLogger.php | 76 +-- .../View/ErrorTemplateRenderer.php | 4 +- src/Framework/ErrorReporting/ErrorReport.php | 10 +- .../ErrorReportingInitializer.php | 30 +- .../Exception/ErrorHandlerContext.php | 2 - .../Filesystem/FilesystemInitializer.php | 37 +- src/Framework/Http/MiddlewareInvoker.php | 14 +- src/Framework/Http/MiddlewareManager.php | 2 +- src/Framework/Http/RequestId.php | 6 +- src/Framework/Http/RequestIdGenerator.php | 18 +- .../Http/Session/SessionFingerprintConfig.php | 41 +- .../Http/Session/SessionInitializer.php | 7 +- .../ModelManagement/MLConfig.php | 25 +- .../Webhook/TelegramWebhookController.php | 6 +- src/Framework/Router/HttpRouter.php | 2 + src/Framework/Router/RouterSetup.php | 29 +- .../RequestSigning/RequestSigningConfig.php | 26 +- .../RequestSigningInitializer.php | 23 +- .../Dom/Transformer/XComponentTransformer.php | 6 +- .../View/Processors/PlaceholderReplacer.php | 10 +- .../View/Processors/XComponentProcessor.php | 8 +- .../WebPush/Controllers/WebPushController.php | 5 +- src/Framework/WebPush/WebPushInitializer.php | 9 +- tests/debug/check-discovery.php | 57 ++ 56 files changed, 1519 insertions(+), 355 deletions(-) create mode 100644 deployment/infrastructure/DEPLOYMENT_ANALYSIS.md create mode 100644 deployment/infrastructure/DEPLOYMENT_FIX_SUMMARY.md create mode 100755 deployment/infrastructure/deploy.sh create mode 100755 deployment/infrastructure/logs.sh create mode 100755 deployment/infrastructure/nginx-logs.sh create mode 100755 deployment/infrastructure/restart.sh create mode 100755 deployment/infrastructure/scripts/deploy.sh create mode 100755 deployment/infrastructure/scripts/quick-sync.sh create mode 100755 deployment/infrastructure/scripts/update-env.sh create mode 100755 deployment/infrastructure/status.sh create mode 100644 docker/php/php.dev.ini delete mode 100644 docker/php/php.development.ini create mode 100644 tests/debug/check-discovery.php diff --git a/deployment/infrastructure/DEPLOYMENT_ANALYSIS.md b/deployment/infrastructure/DEPLOYMENT_ANALYSIS.md new file mode 100644 index 00000000..b12c584d --- /dev/null +++ b/deployment/infrastructure/DEPLOYMENT_ANALYSIS.md @@ -0,0 +1,554 @@ +# Production Deployment Analysis & Fix Strategy + +**Date**: 2025-10-27 +**Status**: CRITICAL - Production website returning HTTP 500 errors +**Root Cause**: Database connection configuration error (DB_PORT mismatch) + +--- + +## 1. Complete Deployment Flow Analysis + +### Deployment Architecture + +The project uses a **release-based deployment pattern** with shared configuration: + +``` +/home/deploy/michaelschiemer/ +├── releases/ +│ ├── 1761566515/ # Current release (timestamped) +│ ├── 1761565432/ # Previous releases +│ └── ... +├── shared/ +│ └── .env.production # Shared configuration file +└── current -> releases/1761566515/ # Symlink to active release +``` + +**Key Characteristics**: +- **Releases Directory**: Each deployment creates a new timestamped release +- **Shared Directory**: Configuration files persist across deployments +- **Current Symlink**: Points to the active release +- **Symlink Chain**: `current/.env.production` → `shared/.env.production` → Used by application + +### .env File Sources (3 Different Files Identified) + +#### 1. Root Directory: `/home/michael/dev/michaelschiemer/.env.production` +- **Size**: 2.9K +- **Checksum**: 9f33068713432c1dc4008724dc6923b0 +- **DB_PORT**: 5432 (CORRECT for PostgreSQL) +- **DB_USERNAME**: mdb_user (with underscore) +- **DB_PASSWORD**: Qo2KNgGqeYksEhKr57pgugakxlothn8J +- **Purpose**: Framework default configuration +- **Status**: CORRECT database configuration + +#### 2. Deployment Directory: `/home/michael/dev/michaelschiemer/deployment/applications/environments/.env.production` +- **Size**: 4.3K +- **Checksum**: b516bf86beed813df03a30f655687b72 +- **DB_PORT**: 5432 (CORRECT for PostgreSQL) +- **DB_USERNAME**: mdb_user (with underscore) +- **DB_PASSWORD**: Qo2KNgGqeYksEhKr57pgugakxlothn8J +- **Purpose**: Application-specific production configuration +- **Status**: CORRECT and MORE COMPLETE (includes Redis, Queue, Mail, Monitoring configs) + +#### 3. Production Server: `/home/deploy/michaelschiemer/shared/.env.production` +- **Size**: 3.0K (modified Oct 26 20:56) +- **Line 15**: `DB_PORT=3306` (WRONG - MySQL port instead of PostgreSQL) +- **Line 67**: `DB_PORT=` (duplicate empty entry) +- **DB_USERNAME**: mdb-user (with hyphen - likely wrong) +- **DB_PASSWORD**: StartSimple2024! (different from local configs) +- **Status**: CORRUPTED - Wrong database configuration causing HTTP 500 errors + +### Deployment Playbook Flow + +**File**: `/home/michael/dev/michaelschiemer/deployment/infrastructure/playbooks/deploy-rsync-based.yml` + +**Critical Configuration**: +```yaml +local_project_path: "{{ playbook_dir }}/../../.." # 3 dirs up = /home/michael/dev/michaelschiemer +shared_files: + - .env.production # Marked as SHARED file +rsync_excludes: + - .env + - .env.local + - .env.development +``` + +**Deployment Steps**: +1. **Rsync files** from `{{ local_project_path }}` (framework root) to release directory + - Excludes: `.env`, `.env.local`, `.env.development` + - Includes: `.env.production` from root directory +2. **Create release directory**: `/home/deploy/michaelschiemer/releases/{{ timestamp }}` +3. **Copy files** to release directory +4. **Create symlinks**: + - `release/.env.production` → `../../shared/.env.production` + - `release/.env` → `../../shared/.env.production` +5. **Update current** symlink → latest release +6. **Restart containers** via docker-compose + +**CRITICAL ISSUE IDENTIFIED**: +The playbook does NOT have a task to initially copy `.env.production` to `shared/.env.production`. It only creates symlinks assuming the file already exists. This means: +- Initial setup requires MANUAL copy of `.env.production` to `shared/` +- Updates to `.env.production` require MANUAL sync to production server +- The rsync'd `.env.production` in release directory is IGNORED (symlink overrides it) + +--- + +## 2. Production Server .env Status + +### Current State (BROKEN) + +```bash +# /home/deploy/michaelschiemer/shared/.env.production + +Line 15: DB_PORT=3306 # WRONG - MySQL port (should be 5432 for PostgreSQL) +Line 67: DB_PORT= # Duplicate empty entry + +DB_USERNAME=mdb-user # Wrong format (should be mdb_user with underscore) +DB_PASSWORD=StartSimple2024! # Wrong password (doesn't match local configs) +``` + +### Container Status + +``` +CONTAINER STATUS ISSUE +php Up 27 minutes (healthy) - +db Up 40 minutes (healthy) PostgreSQL running on port 5432 +redis Up 40 minutes (healthy) - +web Up 40 minutes (UNHEALTHY) Nginx cannot connect to PHP due to DB error +queue-worker Restarting (1) 4s ago PHP crashing due to DB connection error +``` + +### Error Pattern + +- **HTTP 500** on all requests (/, /impressum, etc.) +- **Root Cause**: PHP application cannot connect to database because: + 1. `DB_PORT=3306` (MySQL) instead of `5432` (PostgreSQL) + 2. Wrong username format (`mdb-user` vs `mdb_user`) + 3. Wrong password +- **Impact**: All PHP processes fail to initialize → Nginx returns 500 + +--- + +## 3. Deployment Command Documentation + +### WORKING Commands (Current Playbook) + +#### Deploy via Ansible Playbook +```bash +cd /home/michael/dev/michaelschiemer/deployment/infrastructure + +# Full production deployment +ansible-playbook \ + -i inventories/production/hosts.yml \ + playbooks/deploy-rsync-based.yml \ + --vault-password-file .vault_pass + +# With specific variables +ansible-playbook \ + -i inventories/production/hosts.yml \ + playbooks/deploy-rsync-based.yml \ + --vault-password-file .vault_pass \ + -e "deployment_branch=main" +``` + +#### Check Production Status +```bash +# Check containers +ansible web_servers \ + -i inventories/production/hosts.yml \ + -m shell -a "docker ps -a" \ + --vault-password-file .vault_pass + +# Check .env configuration +ansible web_servers \ + -i inventories/production/hosts.yml \ + -m shell -a "cat /home/deploy/michaelschiemer/shared/.env.production" \ + --vault-password-file .vault_pass + +# Check application logs +ansible web_servers \ + -i inventories/production/hosts.yml \ + -m shell -a "docker logs web --tail 50" \ + --vault-password-file .vault_pass +``` + +### COMMANDS TO CREATE (User Requirements) + +#### 1. Simple Manual Deploy Script +```bash +#!/bin/bash +# File: /home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/deploy.sh + +set -e + +cd "$(dirname "$0")/.." + +echo "🚀 Deploying to production..." + +ansible-playbook \ + -i inventories/production/hosts.yml \ + playbooks/deploy-rsync-based.yml \ + --vault-password-file .vault_pass + +echo "✅ Deployment complete!" +echo "🔍 Check status: docker ps" +``` + +#### 2. .env Update Script +```bash +#!/bin/bash +# File: /home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/update-env.sh + +set -e + +cd "$(dirname "$0")/../.." + +SOURCE_ENV="deployment/applications/environments/.env.production" +REMOTE_PATH="/home/deploy/michaelschiemer/shared/.env.production" + +if [[ ! -f "$SOURCE_ENV" ]]; then + echo "❌ Source .env.production not found at: $SOURCE_ENV" + exit 1 +fi + +echo "📤 Uploading .env.production to production server..." + +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m copy \ + -a "src=$SOURCE_ENV dest=$REMOTE_PATH mode=0644" \ + --vault-password-file deployment/infrastructure/.vault_pass + +echo "🔄 Restarting containers..." + +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && docker-compose restart php web queue-worker" \ + --vault-password-file deployment/infrastructure/.vault_pass + +echo "✅ .env.production updated and containers restarted!" +``` + +#### 3. Quick Production Sync +```bash +#!/bin/bash +# File: /home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/quick-sync.sh + +set -e + +cd "$(dirname "$0")/../.." + +# Sync code changes (no .env update) +rsync -avz \ + --exclude '.env' \ + --exclude '.env.local' \ + --exclude 'node_modules/' \ + --exclude '.git/' \ + ./ deploy@94.16.110.151:/home/deploy/michaelschiemer/current/ + +# Restart containers +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && docker-compose restart php web" \ + --vault-password-file deployment/infrastructure/.vault_pass + +echo "✅ Quick sync complete!" +``` + +### SCRIPTS TO REMOVE (Unused/Deprecated) + +1. **`/home/michael/dev/michaelschiemer/deploy.sh`** (if exists in root) + - Reason: Conflicting with playbook-based deployment + +2. **`/home/michael/dev/michaelschiemer/.env.local`** (if exists) + - Reason: Not used in production, causes confusion + +3. **Duplicate .env files** in root: + - Keep: `.env.production` (source of truth for framework defaults) + - Remove: `.env.backup.*`, `.env.old`, etc. + +--- + +## 4. Fix Strategy (Step-by-Step) + +### IMMEDIATE FIX (Restore Production) + +#### Step 1: Update Production .env.production File + +```bash +cd /home/michael/dev/michaelschiemer + +# Copy correct .env.production to production server +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m copy \ + -a "src=deployment/applications/environments/.env.production dest=/home/deploy/michaelschiemer/shared/.env.production mode=0644" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +**Why this file?** +- Most complete configuration (4.3K vs 2.9K) +- Includes Redis, Queue, Mail, Monitoring configs +- Correct DB_PORT=5432 +- Correct DB credentials + +#### Step 2: Verify .env.production on Server + +```bash +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "grep -E '(DB_PORT|DB_USERNAME|DB_PASSWORD)' /home/deploy/michaelschiemer/shared/.env.production" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +**Expected Output**: +``` +DB_PORT=5432 +DB_USERNAME=mdb_user +DB_PASSWORD=Qo2KNgGqeYksEhKr57pgugakxlothn8J +``` + +#### Step 3: Restart Containers + +```bash +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && docker-compose restart php web queue-worker" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +#### Step 4: Verify Website Functionality + +```bash +# Check HTTP status +curl -I https://michaelschiemer.de + +# Expected: HTTP/2 200 OK (instead of 500) + +# Check container health +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "docker ps | grep -E '(web|php|queue-worker)'" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +**Expected**: All containers should be "Up" and "healthy" + +### LONG-TERM FIX (Prevent Future Issues) + +#### 1. Update Playbook to Sync .env.production + +Add task to `deploy-rsync-based.yml`: + +```yaml +# After "Synchronize project files" task, add: + +- name: Sync .env.production to shared directory + copy: + src: "{{ local_project_path }}/deployment/applications/environments/.env.production" + dest: "{{ project_path }}/shared/.env.production" + mode: '0644' + when: sync_env_to_shared | default(true) + tags: + - deploy + - config +``` + +#### 2. Create Helper Scripts + +Create the 3 scripts documented in section 3: +- `scripts/deploy.sh` - Simple wrapper for playbook +- `scripts/update-env.sh` - Update .env.production only +- `scripts/quick-sync.sh` - Quick code sync without full deployment + +#### 3. Establish Source of Truth + +**Decision**: Use `deployment/applications/environments/.env.production` as source of truth +- Most complete configuration +- Application-specific settings +- Includes all production services + +**Action**: Document in README.md: +```markdown +## Production Configuration + +**Source of Truth**: `deployment/applications/environments/.env.production` + +To update production .env: +1. Edit `deployment/applications/environments/.env.production` +2. Run `./deployment/infrastructure/scripts/update-env.sh` +3. Containers will auto-restart with new config +``` + +#### 4. Add .env Validation + +Create pre-deployment validation script: + +```bash +#!/bin/bash +# scripts/validate-env.sh + +ENV_FILE="deployment/applications/environments/.env.production" + +echo "🔍 Validating .env.production..." + +# Check required variables +REQUIRED_VARS=( + "DB_DRIVER" + "DB_HOST" + "DB_PORT" + "DB_DATABASE" + "DB_USERNAME" + "DB_PASSWORD" +) + +for var in "${REQUIRED_VARS[@]}"; do + if ! grep -q "^${var}=" "$ENV_FILE"; then + echo "❌ Missing required variable: $var" + exit 1 + fi +done + +# Check PostgreSQL port +if ! grep -q "^DB_PORT=5432" "$ENV_FILE"; then + echo "⚠️ Warning: DB_PORT should be 5432 for PostgreSQL" +fi + +echo "✅ .env.production validation passed" +``` + +--- + +## 5. Cleanup Recommendations + +### Files to Remove + +#### In Framework Root (`/home/michael/dev/michaelschiemer/`) +```bash +# List files to remove +find . -maxdepth 1 -name ".env.backup*" -o -name ".env.old*" -o -name ".env.local" + +# Remove after confirmation +rm -f .env.backup* .env.old* .env.local +``` + +#### In Deployment Directory +```bash +# Check for duplicate/old deployment scripts +find deployment/ -name "deploy-old.yml" -o -name "*.backup" +``` + +#### On Production Server +```bash +# Clean up old releases (keep last 5) +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/releases && ls -t | tail -n +6 | xargs rm -rf" \ + --vault-password-file deployment/infrastructure/.vault_pass + +# Remove duplicate .env files in current release +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && rm -f .env.backup* .env.old*" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +### Configuration to Keep + +**Essential Files**: +- `/.env.production` - Framework defaults (keep for reference) +- `/deployment/applications/environments/.env.production` - Source of truth +- `/deployment/infrastructure/playbooks/deploy-rsync-based.yml` - Main playbook +- `/deployment/infrastructure/inventories/production/hosts.yml` - Inventory + +**Symlinks (Do Not Remove)**: +- `/home/deploy/michaelschiemer/current/.env.production` → `shared/.env.production` +- `/home/deploy/michaelschiemer/current/.env` → `shared/.env.production` + +--- + +## 6. Post-Fix Verification Checklist + +```bash +# 1. Website accessible +curl -I https://michaelschiemer.de +# Expected: HTTP/2 200 OK + +# 2. All containers healthy +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell -a "docker ps" \ + --vault-password-file deployment/infrastructure/.vault_pass +# Expected: All "Up" and "(healthy)" + +# 3. Database connection working +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell -a "docker exec php php -r \"new PDO('pgsql:host=db;port=5432;dbname=michaelschiemer', 'mdb_user', 'Qo2KNgGqeYksEhKr57pgugakxlothn8J');\"" \ + --vault-password-file deployment/infrastructure/.vault_pass +# Expected: No errors + +# 4. Application logs clean +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell -a "docker logs web --tail 20" \ + --vault-password-file deployment/infrastructure/.vault_pass +# Expected: HTTP 200 responses, no 500 errors + +# 5. Queue worker stable +ansible web_servers \ + -i deployment/infrastructure/inventories/production/hosts.yml \ + -m shell -a "docker ps | grep queue-worker" \ + --vault-password-file deployment/infrastructure/.vault_pass +# Expected: "Up" status (not "Restarting") +``` + +--- + +## 7. Future Deployment Best Practices + +1. **Always validate .env before deployment** + - Run `scripts/validate-env.sh` pre-deployment + - Check DB_PORT=5432 for PostgreSQL + - Verify credentials match database server + +2. **Use playbook for all deployments** + - Consistent process + - Automated rollback capability + - Proper symlink management + +3. **Monitor container health post-deployment** + - Check `docker ps` output + - Verify all containers "(healthy)" + - Check application logs for errors + +4. **Keep .env.production in sync** + - Single source of truth: `deployment/applications/environments/.env.production` + - Use `update-env.sh` script for updates + - Never manually edit on production server + +5. **Regular backups** + - Backup `shared/.env.production` before changes + - Keep last 5 releases for quick rollback + - Document any manual production changes + +--- + +## Summary + +**Current Status**: Production broken due to DB_PORT configuration error +**Root Cause**: Manual edits to `shared/.env.production` with wrong PostgreSQL port +**Fix Time**: ~5 minutes (copy correct .env + restart containers) +**Prevention**: Automated .env sync in playbook + validation scripts + +**Next Steps**: +1. Execute Step 1-4 of Fix Strategy (IMMEDIATE) +2. Verify website returns HTTP 200 +3. Implement long-term fixes (playbook updates, scripts) +4. Document deployment process in README.md diff --git a/deployment/infrastructure/DEPLOYMENT_FIX_SUMMARY.md b/deployment/infrastructure/DEPLOYMENT_FIX_SUMMARY.md new file mode 100644 index 00000000..7c518635 --- /dev/null +++ b/deployment/infrastructure/DEPLOYMENT_FIX_SUMMARY.md @@ -0,0 +1,286 @@ +# Production Deployment Fix Summary + +**Date**: 2025-10-27 +**Status**: PARTIALLY FIXED - DB configuration corrected, but additional issues remain + +--- + +## What Was Fixed + +### 1. Database Configuration Corrected ✅ + +**Problem**: Wrong DB_PORT in production `.env.production` +- Line 15: `DB_PORT=3306` (MySQL port) +- Line 67: `DB_PORT=` (duplicate empty entry) +- Wrong username: `mdb-user` (should be `mdb_user`) +- Wrong password + +**Solution Applied**: +```bash +# Copied correct .env.production from source of truth +ansible web_servers -m copy \ + -a "src=deployment/applications/environments/.env.production \ + dest=/home/deploy/michaelschiemer/shared/.env.production" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +**Verification**: +```bash +DB_PORT=5432 # ✅ Correct +DB_USERNAME=mdb_user # ✅ Correct +DB_PASSWORD=Qo2KNgGqeYksEhKr57pgugakxlothn8J # ✅ Correct +``` + +### 2. Containers Restarted ✅ + +```bash +docker compose restart php web queue-worker +``` + +**Current Status**: +- **php**: Up 6 minutes (healthy) ✅ +- **db**: Up 53 minutes (healthy) ✅ +- **redis**: Up 53 minutes (healthy) ✅ +- **web**: Up 6 minutes (UNHEALTHY) ⚠️ +- **queue-worker**: Restarting (1) ❌ + +--- + +## Remaining Issues + +### Issue 1: Web Container Unhealthy ⚠️ + +**Symptom**: Website still returns HTTP 500 + +**Possible Causes**: +1. **PHP-FPM not responding** - Web container can't connect to PHP +2. **Application error** - PHP code failing during bootstrap +3. **Missing files** - Application files not properly deployed +4. **Permissions** - Web server can't access application files + +**Next Steps to Diagnose**: +```bash +# Check if PHP-FPM is accessible from web container +docker exec web curl http://php:9000 + +# Check Nginx configuration +docker exec web nginx -t + +# Check web container health check +docker inspect web --format='{{json .State.Health}}' | jq + +# Check if application files exist +docker exec web ls -la /var/www/html/public/index.php +``` + +### Issue 2: Queue Worker Crashing ❌ + +**Symptom**: Continuous restart loop + +**Possible Causes**: +1. **Same DB connection issue** (should be fixed now) +2. **Missing queue configuration** +3. **Redis connection issue** +4. **Application code error in queue worker** + +**Next Steps to Diagnose**: +```bash +# Check queue-worker logs +docker logs queue-worker --tail 100 + +# Try running queue worker manually +docker exec php php artisan queue:work --tries=1 --once +``` + +--- + +## Scripts Created ✅ + +### 1. Simple Deployment Script +**Location**: `/home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/deploy.sh` +```bash +./deployment/infrastructure/scripts/deploy.sh +``` + +### 2. .env Update Script +**Location**: `/home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/update-env.sh` +```bash +./deployment/infrastructure/scripts/update-env.sh +``` + +### 3. Quick Sync Script +**Location**: `/home/michael/dev/michaelschiemer/deployment/infrastructure/scripts/quick-sync.sh` +```bash +./deployment/infrastructure/scripts/quick-sync.sh +``` + +**Note**: All scripts updated to use `docker compose` (v2) instead of `docker-compose` (v1) + +--- + +## Documentation Created ✅ + +### Comprehensive Deployment Analysis +**Location**: `/home/michael/dev/michaelschiemer/deployment/infrastructure/DEPLOYMENT_ANALYSIS.md` + +**Contents**: +1. Complete deployment flow analysis +2. .env file sources and conflicts +3. Deployment command documentation +4. Step-by-step fix strategy +5. Cleanup recommendations +6. Post-fix verification checklist + +--- + +## Recommended Next Actions + +### Immediate (To Fix HTTP 500) + +1. **Check Application Bootstrap**: +```bash +# Test if PHP application can start +ansible web_servers -m shell \ + -a "docker exec php php /var/www/html/public/index.php" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +2. **Check Nginx-PHP Connection**: +```bash +# Test PHP-FPM socket +ansible web_servers -m shell \ + -a "docker exec web curl -v http://php:9000" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +3. **Check Application Logs**: +```bash +# Look for PHP errors +ansible web_servers -m shell \ + -a "docker exec php ls -la /var/www/html/storage/logs/" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +4. **Verify File Permissions**: +```bash +# Check if web server can read files +ansible web_servers -m shell \ + -a "docker exec web ls -la /var/www/html/public/" \ + --vault-password-file deployment/infrastructure/.vault_pass +``` + +### Short-Term (Within 24h) + +1. **Fix Web Container Health** - Resolve HTTP 500 errors +2. **Fix Queue Worker** - Stop crash loop +3. **Full Deployment Test** - Run complete deployment playbook +4. **Verify All Services** - Ensure all containers healthy + +### Long-Term (This Week) + +1. **Update Playbook** - Add .env.production sync task +2. **Add Validation** - Pre-deployment .env validation script +3. **Document Process** - Update README with deployment guide +4. **Setup Monitoring** - Add health check alerts +5. **Cleanup Old Files** - Remove duplicate .env files + +--- + +## Key Learnings + +### 1. Deployment Flow Issues + +**Problem**: Playbook doesn't sync `.env.production` to `shared/` +**Impact**: Manual updates required for configuration changes +**Solution**: Add sync task to playbook + +### 2. Multiple .env Sources + +**Problem**: 3 different `.env.production` files with conflicting content +**Resolution**: Use `deployment/applications/environments/.env.production` as source of truth + +### 3. Docker Compose Version + +**Problem**: Production uses Docker Compose v2 (`docker compose`) +**Impact**: Scripts using v1 syntax (`docker-compose`) fail +**Solution**: All scripts updated to v2 syntax + +### 4. Symlink Chain Complexity + +**Structure**: +``` +current/.env → shared/.env.production +current/.env.production → shared/.env.production +``` + +**Risk**: If `shared/.env.production` is wrong, ALL releases break +**Mitigation**: Validate before deploy, backup before changes + +--- + +## Quick Reference + +### Check Production Status +```bash +cd /home/michael/dev/michaelschiemer/deployment/infrastructure + +# Container status +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "docker ps" --vault-password-file .vault_pass + +# .env configuration +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "cat /home/deploy/michaelschiemer/shared/.env.production" \ + --vault-password-file .vault_pass + +# Application logs +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "docker logs web --tail 50" --vault-password-file .vault_pass +``` + +### Deploy to Production +```bash +# Full deployment +./deployment/infrastructure/scripts/deploy.sh + +# Update .env only +./deployment/infrastructure/scripts/update-env.sh + +# Quick code sync +./deployment/infrastructure/scripts/quick-sync.sh +``` + +### Emergency Rollback +```bash +# List releases +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "ls -la /home/deploy/michaelschiemer/releases/" \ + --vault-password-file .vault_pass + +# Switch to previous release +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "ln -sfn /home/deploy/michaelschiemer/releases/PREVIOUS_TIMESTAMP \ + /home/deploy/michaelschiemer/current" \ + --vault-password-file .vault_pass + +# Restart containers +ansible web_servers -i inventories/production/hosts.yml \ + -m shell -a "cd /home/deploy/michaelschiemer/current && docker compose restart" \ + --vault-password-file .vault_pass +``` + +--- + +## Support Contacts + +**Documentation**: +- Deployment Analysis: `deployment/infrastructure/DEPLOYMENT_ANALYSIS.md` +- This Summary: `deployment/infrastructure/DEPLOYMENT_FIX_SUMMARY.md` + +**Scripts**: +- All scripts in: `deployment/infrastructure/scripts/` +- Make executable: `chmod +x deployment/infrastructure/scripts/*.sh` + +**Configuration**: +- Source of Truth: `deployment/applications/environments/.env.production` +- Production File: `/home/deploy/michaelschiemer/shared/.env.production` diff --git a/deployment/infrastructure/deploy.sh b/deployment/infrastructure/deploy.sh new file mode 100755 index 00000000..300e7027 --- /dev/null +++ b/deployment/infrastructure/deploy.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Quick deployment script with force flag +# Usage: ./deploy.sh + +cd "$(dirname "$0")" + +echo "🚀 Starting deployment to production..." +echo "" + +ansible-playbook \ + -i inventories/production/hosts.yml \ + playbooks/deploy-rsync-based.yml \ + --vault-password-file .vault_pass \ + --extra-vars 'force_deploy=true' + +echo "" +echo "✅ Deployment completed!" diff --git a/deployment/infrastructure/logs.sh b/deployment/infrastructure/logs.sh new file mode 100755 index 00000000..53ee2ddf --- /dev/null +++ b/deployment/infrastructure/logs.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Quick script to show PHP logs from production server +# Usage: ./logs.sh [lines] +# Default: 50 lines + +LINES="${1:-50}" + +echo "📋 Showing last $LINES lines of PHP logs from production..." +echo "" + +ssh -i ~/.ssh/production deploy@michaelschiemer.de "docker logs php --tail $LINES" + +echo "" +echo "✅ Done!" diff --git a/deployment/infrastructure/nginx-logs.sh b/deployment/infrastructure/nginx-logs.sh new file mode 100755 index 00000000..b24a6d41 --- /dev/null +++ b/deployment/infrastructure/nginx-logs.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Show Nginx error logs from production server +# Usage: ./nginx-logs.sh [lines] +# Default: 50 lines + +LINES="${1:-50}" + +echo "📋 Showing last $LINES lines of Nginx error logs from production..." +echo "" + +ssh -i ~/.ssh/production deploy@michaelschiemer.de "docker exec web tail -n $LINES /var/log/nginx/error.log" + +echo "" +echo "✅ Done!" diff --git a/deployment/infrastructure/restart.sh b/deployment/infrastructure/restart.sh new file mode 100755 index 00000000..24134ca9 --- /dev/null +++ b/deployment/infrastructure/restart.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Restart specific Docker container on production server +# Usage: ./restart.sh [container_name] +# Example: ./restart.sh php +# Without argument: restarts all containers + +CONTAINER="${1:-all}" + +echo "🔄 Restarting container(s) on production server..." +echo "" + +if [ "$CONTAINER" = "all" ]; then + echo "Restarting ALL containers..." + ssh -i ~/.ssh/production deploy@michaelschiemer.de "cd /home/deploy/michaelschiemer/current && docker compose -f docker-compose.yml -f docker-compose.production.yml restart" +else + echo "Restarting container: $CONTAINER" + ssh -i ~/.ssh/production deploy@michaelschiemer.de "docker restart $CONTAINER" +fi + +echo "" +echo "✅ Done!" diff --git a/deployment/infrastructure/scripts/deploy.sh b/deployment/infrastructure/scripts/deploy.sh new file mode 100755 index 00000000..3dd1db35 --- /dev/null +++ b/deployment/infrastructure/scripts/deploy.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Simple Production Deployment Script +# Usage: ./deploy.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INFRA_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$INFRA_DIR" + +echo "🚀 Deploying to production..." +echo "📍 Infrastructure directory: $INFRA_DIR" +echo "" + +# Check if vault password file exists +if [[ ! -f ".vault_pass" ]]; then + echo "❌ Vault password file not found: .vault_pass" + echo " Create this file with your Ansible Vault password" + exit 1 +fi + +# Run deployment playbook +ansible-playbook \ + -i inventories/production/hosts.yml \ + playbooks/deploy-rsync-based.yml \ + --vault-password-file .vault_pass + +echo "" +echo "✅ Deployment complete!" +echo "🔍 Check status:" +echo " ansible web_servers -i inventories/production/hosts.yml -m shell -a 'docker ps' --vault-password-file .vault_pass" diff --git a/deployment/infrastructure/scripts/quick-sync.sh b/deployment/infrastructure/scripts/quick-sync.sh new file mode 100755 index 00000000..d55175c1 --- /dev/null +++ b/deployment/infrastructure/scripts/quick-sync.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Quick Production Code Sync +# Usage: ./quick-sync.sh +# Note: Does NOT update .env.production (use update-env.sh for that) + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" +INFRA_DIR="$PROJECT_ROOT/deployment/infrastructure" + +cd "$PROJECT_ROOT" + +echo "🔄 Quick sync to production (code only)..." +echo "📍 Project root: $PROJECT_ROOT" +echo "" + +# Sync code changes (excludes .env files and development artifacts) +rsync -avz \ + --exclude '.env' \ + --exclude '.env.local' \ + --exclude '.env.development' \ + --exclude '.env.production' \ + --exclude 'node_modules/' \ + --exclude '.git/' \ + --exclude 'vendor/' \ + --exclude 'tests/' \ + --exclude '.idea/' \ + --exclude '.vscode/' \ + --exclude '*.log' \ + ./ deploy@94.16.110.151:/home/deploy/michaelschiemer/current/ + +echo "" +echo "🔄 Restarting PHP and web containers..." + +ansible web_servers \ + -i "$INFRA_DIR/inventories/production/hosts.yml" \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && docker compose restart php web" \ + --vault-password-file "$INFRA_DIR/.vault_pass" + +echo "" +echo "✅ Quick sync complete!" +echo "" +echo "⚠️ Note: This does NOT update .env.production" +echo " To update configuration, use: ./update-env.sh" diff --git a/deployment/infrastructure/scripts/update-env.sh b/deployment/infrastructure/scripts/update-env.sh new file mode 100755 index 00000000..66fcea60 --- /dev/null +++ b/deployment/infrastructure/scripts/update-env.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Update Production .env.production File +# Usage: ./update-env.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" +INFRA_DIR="$PROJECT_ROOT/deployment/infrastructure" + +SOURCE_ENV="$PROJECT_ROOT/deployment/applications/environments/.env.production" +REMOTE_PATH="/home/deploy/michaelschiemer/shared/.env.production" + +cd "$PROJECT_ROOT" + +echo "🔍 Validating .env.production..." + +if [[ ! -f "$SOURCE_ENV" ]]; then + echo "❌ Source .env.production not found at: $SOURCE_ENV" + exit 1 +fi + +# Validate required variables +REQUIRED_VARS=("DB_DRIVER" "DB_HOST" "DB_PORT" "DB_DATABASE" "DB_USERNAME" "DB_PASSWORD") +VALIDATION_FAILED=0 + +for var in "${REQUIRED_VARS[@]}"; do + if ! grep -q "^${var}=" "$SOURCE_ENV"; then + echo "❌ Missing required variable: $var" + VALIDATION_FAILED=1 + fi +done + +# Check PostgreSQL port +if ! grep -q "^DB_PORT=5432" "$SOURCE_ENV"; then + echo "⚠️ Warning: DB_PORT should be 5432 for PostgreSQL" + read -p "Continue anyway? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +if [[ $VALIDATION_FAILED -eq 1 ]]; then + echo "❌ Validation failed" + exit 1 +fi + +echo "✅ Validation passed" +echo "" + +echo "📤 Uploading .env.production to production server..." + +ansible web_servers \ + -i "$INFRA_DIR/inventories/production/hosts.yml" \ + -m copy \ + -a "src=$SOURCE_ENV dest=$REMOTE_PATH mode=0644" \ + --vault-password-file "$INFRA_DIR/.vault_pass" + +echo "" +echo "🔄 Restarting containers..." + +ansible web_servers \ + -i "$INFRA_DIR/inventories/production/hosts.yml" \ + -m shell \ + -a "cd /home/deploy/michaelschiemer/current && docker compose restart php web queue-worker" \ + --vault-password-file "$INFRA_DIR/.vault_pass" + +echo "" +echo "✅ .env.production updated and containers restarted!" +echo "" +echo "🔍 Verify:" +echo " curl -I https://michaelschiemer.de" +echo " (Should return HTTP/2 200 OK)" diff --git a/deployment/infrastructure/status.sh b/deployment/infrastructure/status.sh new file mode 100755 index 00000000..8bfe58ab --- /dev/null +++ b/deployment/infrastructure/status.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Show Docker container status on production server +# Usage: ./status.sh + +echo "🐳 Docker Container Status on Production Server" +echo "" + +ssh -i ~/.ssh/production deploy@michaelschiemer.de "docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'" + +echo "" +echo "✅ Done!" diff --git a/docker/php/php.dev.ini b/docker/php/php.dev.ini new file mode 100644 index 00000000..672661fd --- /dev/null +++ b/docker/php/php.dev.ini @@ -0,0 +1,33 @@ +; php.ini für Entwicklung +include = php.common.ini + + +[opcache] +; Opcache komplett deaktiviert für lokale Entwicklung +; Keine Caching-Probleme bei Code-Änderungen +opcache.enable=0 +opcache.enable_cli=0 + +; Alternative: Opcache aktiviert mit sofortiger Revalidierung +; Nur aktivieren wenn Performance wichtiger ist als sofortige Änderungen +;opcache.enable=1 +;opcache.enable_cli=1 +;opcache.memory_consumption=128 +;opcache.max_accelerated_files=10000 +;opcache.revalidate_freq=0 +;opcache.validate_timestamps=1 +;opcache.interned_strings_buffer=16 + +opcache.file_cache= +realpath_cache_ttl=0 + + +display_errors = On +display_startup_errors = On +error_reporting = E_ALL +memory_limit = 512M +upload_max_filesize = 20M +post_max_size = 25M +max_execution_time = 60 + +; Xdebug-Einstellungen können auch hier hinzugefügt werden, falls gewünscht diff --git a/docker/php/php.development.ini b/docker/php/php.development.ini deleted file mode 100644 index e8831c9f..00000000 --- a/docker/php/php.development.ini +++ /dev/null @@ -1,29 +0,0 @@ -; php.ini für Entwicklung -include = php.common.ini - - -[opcache] -opcache.enable=1 -opcache.enable_cli=1 -opcache.memory_consumption=128 -opcache.max_accelerated_files=10000 -; Häufigere Validierung im Dev-Modus -opcache.revalidate_freq=0 -; Timestamps-Validierung einschalten für Entwicklung -opcache.validate_timestamps=1 - -opcache.file_cache= -realpath_cache_ttl=0 - -opcache.interned_strings_buffer=16 - - -display_errors = On -display_startup_errors = On -error_reporting = E_ALL -memory_limit = 512M -upload_max_filesize = 20M -post_max_size = 25M -max_execution_time = 60 - -; Xdebug-Einstellungen können auch hier hinzugefügt werden, falls gewünscht diff --git a/docker/php/php.prod.ini b/docker/php/php.prod.ini index 557ca72e..043e74e8 100644 --- a/docker/php/php.prod.ini +++ b/docker/php/php.prod.ini @@ -10,11 +10,14 @@ opcache.enable_cli=0 opcache.memory_consumption=128 ; Maximale Anzahl an gecachten Dateien opcache.max_accelerated_files=10000 -; Wie oft wird der Cache validiert (0 = bei jedem Request, empfohlen für Entwicklung) -; In Produktion höher setzen für bessere Performance -opcache.revalidate_freq=60 -; Cache-Zeitstempel prüfen (0 für Produktionsumgebungen) -opcache.validate_timestamps=0 +; Wie oft wird der Cache validiert (0 = bei jedem Request) +; TEMPORÄR: 0 für einfachere Deployments während aktiver Entwicklung +; SPÄTER: Auf 60 erhöhen wenn System stabil ist +opcache.revalidate_freq=0 +; Cache-Zeitstempel prüfen +; TEMPORÄR: 1 aktiviert für Deployment-Flexibilität +; SPÄTER: Auf 0 setzen für maximale Performance wenn stabil +opcache.validate_timestamps=1 ; Performance-Optimierungen opcache.interned_strings_buffer=16 ; JIT (Just-In-Time Compilation) - Optional für PHP 8.0+ diff --git a/src/Application/Security/ValueObjects/RequestContext.php b/src/Application/Security/ValueObjects/RequestContext.php index e20a56a9..c582c594 100644 --- a/src/Application/Security/ValueObjects/RequestContext.php +++ b/src/Application/Security/ValueObjects/RequestContext.php @@ -12,9 +12,7 @@ final class RequestContext private ?string $protocol, private ?string $port, private ?string $requestUri, - private ?string $requestMethod, - private ?string $region, - private ?string $geo + private ?string $requestMethod ) { } @@ -26,9 +24,7 @@ final class RequestContext isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http', $_SERVER['SERVER_PORT'] ?? null, $_SERVER['REQUEST_URI'] ?? null, - $_SERVER['REQUEST_METHOD'] ?? null, - $_ENV['AWS_REGION'] ?? $_ENV['REGION'] ?? null, - $_ENV['GEO_REGION'] ?? null + $_SERVER['REQUEST_METHOD'] ?? null ); } @@ -61,14 +57,4 @@ final class RequestContext { return $this->requestMethod; } - - public function getRegion(): ?string - { - return $this->region; - } - - public function getGeo(): ?string - { - return $this->geo; - } } diff --git a/src/Framework/Analytics/AnalyticsConfig.php b/src/Framework/Analytics/AnalyticsConfig.php index 6d18df82..425b72e7 100644 --- a/src/Framework/Analytics/AnalyticsConfig.php +++ b/src/Framework/Analytics/AnalyticsConfig.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Framework\Analytics; +use App\Framework\Config\Environment; + /** * Analytics-Konfiguration */ @@ -28,20 +30,20 @@ final readonly class AnalyticsConfig ) { } - public static function fromEnvironment(): self + public static function fromEnvironment(Environment $env): self { return new self( - enabled: filter_var($_ENV['ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN), - samplingRate: (float)($_ENV['ANALYTICS_SAMPLING_RATE'] ?? 1.0), - securityAnalyticsEnabled: filter_var($_ENV['SECURITY_ANALYTICS_ENABLED'] ?? true, FILTER_VALIDATE_BOOLEAN), - dataPath: $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics', - bufferSize: (int)($_ENV['ANALYTICS_BUFFER_SIZE'] ?? 1000), - retentionDays: (int)($_ENV['ANALYTICS_RETENTION_DAYS'] ?? 365), - trackPageViews: filter_var($_ENV['ANALYTICS_TRACK_PAGE_VIEWS'] ?? true, FILTER_VALIDATE_BOOLEAN), - trackApiCalls: filter_var($_ENV['ANALYTICS_TRACK_API_CALLS'] ?? true, FILTER_VALIDATE_BOOLEAN), - trackUserActions: filter_var($_ENV['ANALYTICS_TRACK_USER_ACTIONS'] ?? true, FILTER_VALIDATE_BOOLEAN), - trackErrors: filter_var($_ENV['ANALYTICS_TRACK_ERRORS'] ?? true, FILTER_VALIDATE_BOOLEAN), - trackPerformance: filter_var($_ENV['ANALYTICS_TRACK_PERFORMANCE'] ?? true, FILTER_VALIDATE_BOOLEAN), + enabled: $env->getBool('ANALYTICS_ENABLED', true), + samplingRate: $env->getFloat('ANALYTICS_SAMPLING_RATE', 1.0), + securityAnalyticsEnabled: $env->getBool('SECURITY_ANALYTICS_ENABLED', true), + dataPath: $env->getString('ANALYTICS_DATA_PATH', '/var/www/html/storage/analytics'), + bufferSize: $env->getInt('ANALYTICS_BUFFER_SIZE', 1000), + retentionDays: $env->getInt('ANALYTICS_RETENTION_DAYS', 365), + trackPageViews: $env->getBool('ANALYTICS_TRACK_PAGE_VIEWS', true), + trackApiCalls: $env->getBool('ANALYTICS_TRACK_API_CALLS', true), + trackUserActions: $env->getBool('ANALYTICS_TRACK_USER_ACTIONS', true), + trackErrors: $env->getBool('ANALYTICS_TRACK_ERRORS', true), + trackPerformance: $env->getBool('ANALYTICS_TRACK_PERFORMANCE', true), ); } } diff --git a/src/Framework/Config/AppConfig.php b/src/Framework/Config/AppConfig.php index 9bb54a3a..3dc6500b 100644 --- a/src/Framework/Config/AppConfig.php +++ b/src/Framework/Config/AppConfig.php @@ -4,13 +4,14 @@ declare(strict_types=1); namespace App\Framework\Config; +use App\Framework\Core\ValueObjects\Version; use App\Framework\DateTime\Timezone; final readonly class AppConfig { public function __construct( public string $name = 'Framework App', - public string $version = '1.0.0', + public Version $version = new Version(1, 0, 0), public string $environment = 'production', public bool $debug = false, public Timezone $timezone = Timezone::EuropeBerlin, @@ -43,4 +44,9 @@ final readonly class AppConfig { return $this->debug || $this->type->isDebugEnabled(); } + + public function isDebug(): bool + { + return $this->isDebugEnabled(); + } } diff --git a/src/Framework/Config/EncryptedEnvLoader.php b/src/Framework/Config/EncryptedEnvLoader.php index 0e556559..931d3362 100644 --- a/src/Framework/Config/EncryptedEnvLoader.php +++ b/src/Framework/Config/EncryptedEnvLoader.php @@ -41,7 +41,8 @@ final readonly class EncryptedEnvLoader } // Load environment-specific files - $appEnv = $variables['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'development'; + // Priority: 1. Already loaded from .env file, 2. Default to development + $appEnv = $variables['APP_ENV'] ?? 'development'; $envSpecificFile = $baseDir->join(".env.{$appEnv}"); if ($envSpecificFile->exists()) { $envSpecificVariables = $this->parseEnvFile($envSpecificFile); diff --git a/src/Framework/Config/TypedConfigInitializer.php b/src/Framework/Config/TypedConfigInitializer.php index 656280c6..4fe0c2c2 100644 --- a/src/Framework/Config/TypedConfigInitializer.php +++ b/src/Framework/Config/TypedConfigInitializer.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\Config; use App\Framework\Config\External\ExternalApiConfig; +use App\Framework\Core\ValueObjects\Version; use App\Framework\Database\Config\DatabaseConfig; use App\Framework\Database\Config\DatabaseConfigInitializer; use App\Framework\DateTime\Timezone; @@ -51,10 +52,11 @@ final readonly class TypedConfigInitializer private function createAppConfig(): AppConfig { $environmentType = EnvironmentType::fromEnvironment($this->env); + $versionString = $this->env->getString('APP_VERSION', '1.0.0'); return new AppConfig( name: $this->env->getString(EnvKey::APP_NAME, 'Framework App'), - version: $this->env->getString('APP_VERSION', '1.0.0'), + version: Version::fromString($versionString), environment: $this->env->getString( key: EnvKey::APP_ENV, default: 'production' diff --git a/src/Framework/Console/Analytics/Middleware/AnalyticsCollectionMiddleware.php b/src/Framework/Console/Analytics/Middleware/AnalyticsCollectionMiddleware.php index 8f2d661f..0c79c465 100644 --- a/src/Framework/Console/Analytics/Middleware/AnalyticsCollectionMiddleware.php +++ b/src/Framework/Console/Analytics/Middleware/AnalyticsCollectionMiddleware.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Framework\Console\Analytics\Middleware; +use App\Framework\Config\AppConfig; +use App\Framework\Config\Environment; use App\Framework\Console\Analytics\Repository\CommandUsageRepository; use App\Framework\Console\Analytics\ValueObjects\CommandUsageMetric; use App\Framework\Console\ExitCode; @@ -17,6 +19,8 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware { public function __construct( private CommandUsageRepository $repository, + private AppConfig $appConfig, + private Environment $environment, private bool $enabled = true ) { } @@ -92,7 +96,7 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware { // In a real implementation, this would extract user ID from context // Could be from environment variables, session data, etc. - return $_ENV['CONSOLE_USER_ID'] ?? null; + return $this->environment->getString('CONSOLE_USER_ID'); } private function collectMetadata(ConsoleInput $input, ExitCode $exitCode): array @@ -103,13 +107,9 @@ final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware 'php_version' => PHP_VERSION, 'memory_peak' => memory_get_peak_usage(true), 'exit_code_name' => $exitCode->name, + 'environment' => $this->appConfig->type->value, ]; - // Add environment context - if (isset($_ENV['APP_ENV'])) { - $metadata['environment'] = $_ENV['APP_ENV']; - } - // Add process info if (function_exists('posix_getpid')) { $metadata['process_id'] = posix_getpid(); diff --git a/src/Framework/Console/Layout/Commands/LayoutTestCommand.php b/src/Framework/Console/Layout/Commands/LayoutTestCommand.php index a73e1a3a..9a9da735 100644 --- a/src/Framework/Console/Layout/Commands/LayoutTestCommand.php +++ b/src/Framework/Console/Layout/Commands/LayoutTestCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Console\Layout\Commands; +use App\Framework\Config\AppConfig; use App\Framework\Console\Attributes\ConsoleCommand; use App\Framework\Console\ExitCode; use App\Framework\Console\Input\ConsoleInput; @@ -13,6 +14,10 @@ use App\Framework\Console\Output\ConsoleOutput; final readonly class LayoutTestCommand { + public function __construct( + private AppConfig $appConfig + ) { + } #[ConsoleCommand( name: 'layout:test', description: 'Test responsive terminal layout features' @@ -48,8 +53,8 @@ final readonly class LayoutTestCommand 'Framework Version' => '1.0.0', 'PHP Version' => PHP_VERSION, 'Memory Usage' => memory_get_usage(true) . ' bytes', - 'Environment' => $_ENV['APP_ENV'] ?? 'development', - 'Debug Mode' => 'Enabled', + 'Environment' => $this->appConfig->type->value, + 'Debug Mode' => $this->appConfig->isDebug() ? 'Enabled' : 'Disabled', ]; $responsiveOutput->writeKeyValue($data); diff --git a/src/Framework/Context/ExecutionContext.php b/src/Framework/Context/ExecutionContext.php index b11f70ed..718a3d4f 100644 --- a/src/Framework/Context/ExecutionContext.php +++ b/src/Framework/Context/ExecutionContext.php @@ -4,8 +4,7 @@ declare(strict_types=1); namespace App\Framework\Context; -use App\Framework\Config\Environment; -use App\Framework\Config\EnvKey; +use App\Framework\Config\AppConfig; use App\Framework\Http\ServerEnvironment; final readonly class ExecutionContext @@ -68,10 +67,8 @@ final readonly class ExecutionContext ], $this->metadata); } - public static function detect(?Environment $environment = null): self + public static function detect(?AppConfig $appConfig = null): self { - #$environment->get(EnvKey::APP_ENV); - // Debug logging #error_log("ExecutionContext::detect() - SAPI: " . php_sapi_name()); #error_log("ExecutionContext::detect() - REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'not set')); @@ -80,7 +77,7 @@ final readonly class ExecutionContext // Test Environment if (defined('PHPUNIT_COMPOSER_INSTALL') || - isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing') { + ($appConfig !== null && $appConfig->type->isTesting())) { return new self(ContextType::TEST, ['detected_by' => 'phpunit_or_env']); } diff --git a/src/Framework/Core/AppBootstrapper.php b/src/Framework/Core/AppBootstrapper.php index c50507c9..0276b0c3 100644 --- a/src/Framework/Core/AppBootstrapper.php +++ b/src/Framework/Core/AppBootstrapper.php @@ -53,11 +53,12 @@ final readonly class AppBootstrapper // Make Environment available throughout the application $this->container->instance(Environment::class, $env); - $this->container->instance(TypedConfiguration::class, new TypedConfigInitializer($env)($this->container)); + $typedConfig = new TypedConfigInitializer($env)($this->container); + $this->container->instance(TypedConfiguration::class, $typedConfig); // ExecutionContext detection sollte das erste sein, das nach dem Instanziieren des containers passiert. noch bevor dem bootstrap des containers. - $executionContext = ExecutionContext::detect($env); + $executionContext = ExecutionContext::detect($typedConfig->app); $this->container->instance(ExecutionContext::class, $executionContext); // Register MemoryMonitor as singleton diff --git a/src/Framework/Core/ContainerBootstrapper.php b/src/Framework/Core/ContainerBootstrapper.php index b88983cf..ca40cb34 100644 --- a/src/Framework/Core/ContainerBootstrapper.php +++ b/src/Framework/Core/ContainerBootstrapper.php @@ -213,6 +213,7 @@ final readonly class ContainerBootstrapper private function autowire(Container $container): void { // Discovery service bootstrapping + error_log("🔧 CONTAINER BOOTSTRAP: autowire() starting Discovery"); $clock = $container->get(Clock::class); $bootstrapper = new DiscoveryServiceBootstrapper($container, $clock); $results = $bootstrapper->bootstrap(); diff --git a/src/Framework/Database/Driver/PostgresDriver.php b/src/Framework/Database/Driver/PostgresDriver.php index 7998735b..4842a454 100644 --- a/src/Framework/Database/Driver/PostgresDriver.php +++ b/src/Framework/Database/Driver/PostgresDriver.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Database\Driver; +use App\Framework\Config\AppConfig; use PDO; /** @@ -47,8 +48,7 @@ final readonly class PostgresDriver implements Driver } // Application name for pg_stat_activity monitoring - $appName = $_ENV['APP_NAME'] ?? 'custom-php-framework'; - $parts['application_name'] = $appName; + $parts['application_name'] = 'php-app'; // Connect timeout (5 seconds default) $parts['connect_timeout'] = '5'; diff --git a/src/Framework/Database/Migration/Commands/MakeMigrationFromDiffCommand.php b/src/Framework/Database/Migration/Commands/MakeMigrationFromDiffCommand.php index c247621e..9a3c12c4 100644 --- a/src/Framework/Database/Migration/Commands/MakeMigrationFromDiffCommand.php +++ b/src/Framework/Database/Migration/Commands/MakeMigrationFromDiffCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Database\Migration\Commands; +use App\Framework\Config\AppConfig; use App\Framework\Console\ConsoleCommand; use App\Framework\Console\ExitCode; use App\Framework\Core\PathProvider; @@ -20,7 +21,8 @@ final readonly class MakeMigrationFromDiffCommand public function __construct( private DatabaseManager $databaseManager, private MigrationGenerator $migrationGenerator, - private PathProvider $pathProvider + private PathProvider $pathProvider, + private AppConfig $appConfig ) { } @@ -111,7 +113,7 @@ final readonly class MakeMigrationFromDiffCommand return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error creating migration: {$e->getMessage()}\n"; - if (isset($_ENV['APP_DEBUG']) && $_ENV['APP_DEBUG']) { + if ($this->appConfig->isDebug()) { echo $e->getTraceAsString() . "\n"; } diff --git a/src/Framework/Database/Migration/Services/MigrationValidator.php b/src/Framework/Database/Migration/Services/MigrationValidator.php index 60a5ba85..ec9d0ebd 100644 --- a/src/Framework/Database/Migration/Services/MigrationValidator.php +++ b/src/Framework/Database/Migration/Services/MigrationValidator.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Database\Migration\Services; +use App\Framework\Config\AppConfig; use App\Framework\Core\ValueObjects\Byte; use App\Framework\Database\ConnectionInterface; use App\Framework\Database\Migration\Migration; @@ -18,7 +19,8 @@ final readonly class MigrationValidator { public function __construct( private ConnectionInterface $connection, - private DatabasePlatform $platform + private DatabasePlatform $platform, + private AppConfig $appConfig ) { } @@ -309,7 +311,7 @@ final readonly class MigrationValidator private function getEnvironmentDetails(): array { return [ - 'app_env' => $_ENV['APP_ENV'] ?? 'unknown', + 'app_env' => $this->appConfig->type->value, 'php_version' => PHP_VERSION, 'server_name' => $_SERVER['SERVER_NAME'] ?? 'unknown', 'database_driver' => $this->platform->getName(), diff --git a/src/Framework/Database/Schema/Commands/SchemaDiffCommand.php b/src/Framework/Database/Schema/Commands/SchemaDiffCommand.php index c71ee15a..96cdceb1 100644 --- a/src/Framework/Database/Schema/Commands/SchemaDiffCommand.php +++ b/src/Framework/Database/Schema/Commands/SchemaDiffCommand.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Database\Schema\Commands; +use App\Framework\Config\AppConfig; use App\Framework\Console\ConsoleCommand; use App\Framework\Console\ExitCode; use App\Framework\Database\DatabaseManager; @@ -15,7 +16,8 @@ use App\Framework\Database\Schema\Comparison\SchemaComparator; final readonly class SchemaDiffCommand { public function __construct( - private DatabaseManager $databaseManager + private DatabaseManager $databaseManager, + private AppConfig $appConfig ) { } @@ -98,7 +100,7 @@ final readonly class SchemaDiffCommand return ExitCode::SUCCESS; } catch (\Throwable $e) { echo "Error comparing schemas: {$e->getMessage()}\n"; - if (isset($_ENV['APP_DEBUG']) && $_ENV['APP_DEBUG']) { + if ($this->appConfig->isDebug()) { echo $e->getTraceAsString() . "\n"; } diff --git a/src/Framework/Discovery/DiscoveryServiceBootstrapper.php b/src/Framework/Discovery/DiscoveryServiceBootstrapper.php index 3af7bda8..866febaa 100644 --- a/src/Framework/Discovery/DiscoveryServiceBootstrapper.php +++ b/src/Framework/Discovery/DiscoveryServiceBootstrapper.php @@ -48,14 +48,23 @@ final readonly class DiscoveryServiceBootstrapper $currentContext = ExecutionContext::detect(); $contextString = $currentContext->getType()->value; + // TEMPORARY DEBUG LOGGING + error_log("🔍 DISCOVERY DEBUG: Context detected = {$contextString}"); + error_log("🔍 DISCOVERY DEBUG: Source path = " . $pathProvider->getSourcePath()); + // Direkter Cache-Check mit expliziter toArray/fromArray Serialisierung $defaultPaths = [$pathProvider->getSourcePath()]; $cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($defaultPaths, $contextString); + error_log("🔍 DISCOVERY DEBUG: Cache key = {$cacheKey->toString()}"); + $cachedItem = $cache->get($cacheKey); + error_log("🔍 DISCOVERY DEBUG: Cache hit = " . ($cachedItem->isHit ? 'YES' : 'NO')); + if ($cachedItem->isHit) { + error_log("🔍 DISCOVERY DEBUG: Loading from cache..."); // Ensure DiscoveryRegistry class is loaded before attempting deserialization if (! class_exists(DiscoveryRegistry::class, true)) { $cachedRegistry = null; @@ -82,6 +91,9 @@ final readonly class DiscoveryServiceBootstrapper } if ($cachedRegistry !== null && ! $cachedRegistry->isEmpty()) { + $routeCount = count($cachedRegistry->attributes->get(\App\Framework\Attributes\Route::class)); + error_log("🔍 DISCOVERY DEBUG: Cached registry loaded - Route count: {$routeCount}"); + $this->container->singleton(DiscoveryRegistry::class, $cachedRegistry); // Process DefaultImplementation attributes first (before Initializers) @@ -97,10 +109,18 @@ final readonly class DiscoveryServiceBootstrapper } // Fallback: Vollständige Discovery durchführen + error_log("🔍 DISCOVERY DEBUG: Performing fresh discovery..."); $results = $this->performBootstrap($pathProvider, $cache, $discoveryConfig); + error_log("🔍 DISCOVERY DEBUG: Discovery completed - isEmpty: " . ($results->isEmpty() ? 'YES' : 'NO')); // Nach der Discovery explizit in unserem eigenen Cache-Format speichern $consoleCommandCount = count($results->attributes->get(\App\Framework\Console\ConsoleCommand::class)); + $routeCount = count($results->attributes->get(\App\Framework\Attributes\Route::class)); + $initializerCount = count($results->attributes->get(\App\Framework\DI\Initializer::class)); + + error_log("🔍 DISCOVERY DEBUG: Found {$routeCount} routes"); + error_log("🔍 DISCOVERY DEBUG: Found {$consoleCommandCount} console commands"); + error_log("🔍 DISCOVERY DEBUG: Found {$initializerCount} initializers"); // Only cache if we found meaningful results // An empty discovery likely indicates initialization timing issues diff --git a/src/Framework/ErrorAggregation/ErrorEvent.php b/src/Framework/ErrorAggregation/ErrorEvent.php index 585e2ad8..5d187d70 100644 --- a/src/Framework/ErrorAggregation/ErrorEvent.php +++ b/src/Framework/ErrorAggregation/ErrorEvent.php @@ -37,7 +37,7 @@ final readonly class ErrorEvent /** * Creates ErrorEvent from ErrorHandlerContext */ - public static function fromErrorHandlerContext(ErrorHandlerContext $context, \App\Framework\DateTime\Clock $clock): self + public static function fromErrorHandlerContext(ErrorHandlerContext $context, \App\Framework\DateTime\Clock $clock, bool $isDebug = false): self { return new self( id: new Ulid($clock), @@ -54,7 +54,7 @@ final readonly class ErrorEvent userId: $context->request->userId ?? null, clientIp: $context->request->clientIp, isSecurityEvent: $context->exception->metadata['security_event'] ?? false, - stackTrace: self::extractStackTrace($context), + stackTrace: self::extractStackTrace($context, $isDebug), userAgent: $context->request->userAgent, ); } @@ -263,10 +263,10 @@ final readonly class ErrorEvent }; } - private static function extractStackTrace(ErrorHandlerContext $context): ?string + private static function extractStackTrace(ErrorHandlerContext $context, bool $isDebug = false): ?string { // Don't include stack traces for security events in production - if (($context->exception->metadata['security_event'] ?? false) && ! ($_ENV['APP_DEBUG'] ?? false)) { + if (($context->exception->metadata['security_event'] ?? false) && ! $isDebug) { return null; } diff --git a/src/Framework/ErrorHandling/ErrorHandler.php b/src/Framework/ErrorHandling/ErrorHandler.php index 6ce85f5c..976a1758 100644 --- a/src/Framework/ErrorHandling/ErrorHandler.php +++ b/src/Framework/ErrorHandling/ErrorHandler.php @@ -51,8 +51,13 @@ final readonly class ErrorHandler ?SecurityEventHandler $securityHandler = null ) { $this->isDebugMode = $isDebugMode ?? $this->getDebugModeFromEnvironment(); - $this->logger = new ErrorLogger($logger); - $this->securityHandler = $securityHandler ?? SecurityEventHandler::createDefault($logger); + + // Get Logger and AppConfig from container if not provided + $logger = $logger ?? $container->get(Logger::class); + $appConfig = $container->get(\App\Framework\Config\AppConfig::class); + + $this->logger = new ErrorLogger($logger, $appConfig); + $this->securityHandler = $securityHandler ?? SecurityEventHandler::createDefault($logger, $appConfig); } public function register(): void @@ -456,9 +461,12 @@ final readonly class ErrorHandler } } + // Get AppConfig from container + $appConfig = $this->container->get(\App\Framework\Config\AppConfig::class); + $htmlRenderer = $templateRenderer ? - new ErrorTemplateRenderer($templateRenderer) : - new ErrorTemplateRenderer(new DummyTemplateRenderer()); + new ErrorTemplateRenderer($templateRenderer, $appConfig) : + new ErrorTemplateRenderer(new DummyTemplateRenderer(), $appConfig); $apiRenderer = new ApiErrorRenderer(); diff --git a/src/Framework/ErrorHandling/ErrorLogger.php b/src/Framework/ErrorHandling/ErrorLogger.php index 5bdec46d..8fc23ae7 100644 --- a/src/Framework/ErrorHandling/ErrorLogger.php +++ b/src/Framework/ErrorHandling/ErrorLogger.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\ErrorHandling; +use App\Framework\Config\AppConfig; use App\Framework\Exception\Core\ErrorSeverity; use App\Framework\Exception\ErrorHandlerContext; use App\Framework\Logging\Logger; @@ -16,7 +17,8 @@ use App\Framework\Logging\ValueObjects\LogContext; final readonly class ErrorLogger { public function __construct( - private ?Logger $logger = null + private Logger $logger, + private AppConfig $appConfig ) { } @@ -42,13 +44,8 @@ final readonly class ErrorLogger 'additionalData' => $context->additionalData, ]; - if ($this->logger !== null) { - $logLevel = $this->mapErrorSeverityToLogLevel($context->level); - $this->logger->log($logLevel, $message, $contextData); - } else { - // Fallback auf error_log - $this->logToErrorLog($context); - } + $logLevel = $this->mapErrorSeverityToLogLevel($context->level); + $this->logger->log($logLevel, $message, $contextData); } /** @@ -73,23 +70,18 @@ final readonly class ErrorLogger ); - if ($this->logger !== null) { - $logLevel = $this->determineLogLevel($context); + $logLevel = $this->determineLogLevel($context); - // Erstelle LogContext mit strukturierten Daten - $logContext = LogContext::withData($logData) - ->addTags('error_handler', 'framework'); + // Erstelle LogContext mit strukturierten Daten + $logContext = LogContext::withData($logData) + ->addTags('error_handler', 'framework'); - // Füge Metadaten-Tags hinzu - if ($context->exception->metadata['security_event'] ?? false) { - $logContext = $logContext->addTags('security'); - } - - $this->logger->log($logLevel, $message, $logContext); - } else { - // Fallback auf error_log - $this->logHandlerContextToErrorLog($context); + // Füge Metadaten-Tags hinzu + if ($context->exception->metadata['security_event'] ?? false) { + $logContext = $logContext->addTags('security'); } + + $this->logger->log($logLevel, $message, $logContext); } /** @@ -97,18 +89,13 @@ final readonly class ErrorLogger */ private function logSecurityEvent(ErrorHandlerContext $context): void { - $securityLog = $context->toSecurityEventFormat($_ENV['APP_NAME'] ?? 'app'); + $securityLog = $context->toSecurityEventFormat($this->appConfig->name); - if ($this->logger !== null) { - // Verwende LogContext für strukturiertes Security Event Logging - $logContext = LogContext::withData($securityLog) - ->addTags('security_event', 'owasp'); + // Verwende LogContext für strukturiertes Security Event Logging + $logContext = LogContext::withData($securityLog) + ->addTags('security_event', 'owasp'); - $this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext); - } else { - // Fallback nur als letzte Option - error_log('SECURITY_EVENT: ' . json_encode($securityLog, JSON_UNESCAPED_SLASHES)); - } + $this->logger->log(LogLevel::WARNING, 'Security Event Detected', $logContext); } /** @@ -164,51 +151,6 @@ final readonly class ErrorLogger }; } - /** - * Fallback-Methode für ErrorHandlerContext ohne Framework Logger - */ - private function logHandlerContextToErrorLog(ErrorHandlerContext $context): void - { - $message = sprintf( - '[%s] [%s] %s: %s', - date('Y-m-d H:i:s'), - $context->exception->metadata['error_level'] ?? 'ERROR', - $context->exception->component ?? 'Application', - $this->extractExceptionMessage($context) - ); - - error_log($message); - - // Security Events auch separat loggen - if ($context->exception->metadata['security_event'] ?? false) { - $securityLog = $context->toSecurityEventJson($_ENV['APP_NAME'] ?? 'app'); - error_log('SECURITY_EVENT: ' . $securityLog); - } - } - - /** - * Fallback-Methode für Logging ohne Framework Logger (Legacy) - */ - private function logToErrorLog(ErrorContext $context): void - { - $exception = $context->exception; - $message = sprintf( - '[%s] [%s] %s: %s in %s:%d', - date('Y-m-d H:i:s'), - $context->level->name, - get_class($exception), - $exception->getMessage(), - $exception->getFile(), - $exception->getLine() - ); - - error_log($message); - - // Bei kritischen Fehlern auch den Stacktrace loggen - if ($context->level === ErrorSeverity::CRITICAL) { - error_log("Stack trace: \n" . $exception->getTraceAsString()); - } - } /** * Mappt ErrorSeverity auf Framework LogLevel diff --git a/src/Framework/ErrorHandling/SecurityEventHandler.php b/src/Framework/ErrorHandling/SecurityEventHandler.php index 165972dd..4831b64c 100644 --- a/src/Framework/ErrorHandling/SecurityEventHandler.php +++ b/src/Framework/ErrorHandling/SecurityEventHandler.php @@ -16,7 +16,7 @@ use Throwable; final readonly class SecurityEventHandler { public function __construct( - private SecurityEventLogger $securityLogger, + private ?SecurityEventLogger $securityLogger, private ?SecurityAlertManager $alertManager = null ) { } @@ -28,6 +28,11 @@ final readonly class SecurityEventHandler SecurityException $exception, ?MiddlewareContext $context = null ): void { + // Skip if no logger available + if ($this->securityLogger === null) { + return; + } + try { // Erstelle ErrorHandlerContext für OWASP-Format $errorHandlerContext = $this->createErrorHandlerContext($exception, $context); @@ -71,7 +76,7 @@ final readonly class SecurityEventHandler ): void { if ($this->alertManager) { $this->alertManager->sendAlert($exception, $context); - } else { + } elseif ($this->securityLogger !== null) { // Fallback: Logge als kritisches Event $this->securityLogger->logCriticalAlert($exception, $context); } @@ -135,9 +140,16 @@ final readonly class SecurityEventHandler /** * Factory-Methode mit Standard-Konfiguration */ - public static function createDefault(?Logger $logger = null): self + public static function createDefault(?Logger $logger = null, ?\App\Framework\Config\AppConfig $appConfig = null): self { - $securityLogger = new SecurityEventLogger($logger); + // If AppConfig not provided, we can't create SecurityEventLogger properly + // This is a temporary solution - ideally AppConfig should always be provided + if ($logger === null || $appConfig === null) { + // Return a minimal handler without SecurityEventLogger + return new self(null, null); + } + + $securityLogger = new SecurityEventLogger($logger, $appConfig); $alertManager = null; // Kann später konfiguriert werden return new self($securityLogger, $alertManager); diff --git a/src/Framework/ErrorHandling/SecurityEventLogger.php b/src/Framework/ErrorHandling/SecurityEventLogger.php index 376c4f08..02413529 100644 --- a/src/Framework/ErrorHandling/SecurityEventLogger.php +++ b/src/Framework/ErrorHandling/SecurityEventLogger.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\ErrorHandling; +use App\Framework\Config\AppConfig; use App\Framework\Exception\ErrorHandlerContext; use App\Framework\Exception\SecurityException; use App\Framework\Exception\SecurityLogLevel; @@ -16,8 +17,8 @@ use App\Framework\Logging\LogLevel; final readonly class SecurityEventLogger { public function __construct( - private ?Logger $logger = null, - private string $applicationId = 'app' + private Logger $logger, + private AppConfig $appConfig ) { } @@ -33,26 +34,21 @@ final readonly class SecurityEventLogger // OWASP-konformes Log generieren $owaspLog = $this->createOWASPLog($exception, $context); - if ($this->logger) { - // Strukturiertes Logging über Framework Logger - $frameworkLogLevel = $this->mapSecurityLevelToFrameworkLevel($securityEvent->getLogLevel()); + // Strukturiertes Logging über Framework Logger + $frameworkLogLevel = $this->mapSecurityLevelToFrameworkLevel($securityEvent->getLogLevel()); - $this->logger->log( - $frameworkLogLevel, - $securityEvent->getDescription(), - [ - 'security_event' => $securityEvent->getEventIdentifier(), - 'security_category' => $securityEvent->getCategory(), - 'requires_alert' => $securityEvent->requiresAlert(), - 'owasp_format' => $owaspLog, - 'event_data' => $securityEvent->toArray(), - 'context_data' => $context->forLogging(), - ] - ); - } else { - // Fallback auf error_log - $this->logToErrorLog($owaspLog); - } + $this->logger->log( + $frameworkLogLevel, + $securityEvent->getDescription(), + [ + 'security_event' => $securityEvent->getEventIdentifier(), + 'security_category' => $securityEvent->getCategory(), + 'requires_alert' => $securityEvent->requiresAlert(), + 'owasp_format' => $owaspLog, + 'event_data' => $securityEvent->toArray(), + 'context_data' => $context->forLogging(), + ] + ); } /** @@ -64,15 +60,11 @@ final readonly class SecurityEventLogger ): void { $alertData = $this->createAlertData($exception, $context); - if ($this->logger) { - $this->logger->log( - LogLevel::CRITICAL, - 'SECURITY_ALERT: ' . $exception->getSecurityEvent()->getEventIdentifier(), - $alertData - ); - } else { - error_log('SECURITY_ALERT: ' . json_encode($alertData)); - } + $this->logger->log( + LogLevel::CRITICAL, + 'SECURITY_ALERT: ' . $exception->getSecurityEvent()->getEventIdentifier(), + $alertData + ); } /** @@ -86,7 +78,7 @@ final readonly class SecurityEventLogger return [ 'datetime' => date('c'), - 'appid' => $this->applicationId, + 'appid' => $this->appConfig->name, 'event' => $securityEvent->getEventIdentifier(), 'level' => $securityEvent->getLogLevel()->value, 'description' => $securityEvent->getDescription(), @@ -98,8 +90,6 @@ final readonly class SecurityEventLogger 'port' => $context->request->port, 'request_uri' => $context->request->requestUri, 'request_method' => $context->request->requestMethod, - 'region' => $_ENV['AWS_REGION'] ?? 'unknown', - 'geo' => $_ENV['GEO_LOCATION'] ?? 'unknown', 'category' => $securityEvent->getCategory(), 'requires_alert' => $securityEvent->requiresAlert(), ]; @@ -127,15 +117,6 @@ final readonly class SecurityEventLogger ]; } - /** - * Fallback-Logging über error_log - */ - private function logToErrorLog(array $owaspLog): void - { - $logMessage = 'SECURITY_EVENT: ' . json_encode($owaspLog, JSON_UNESCAPED_SLASHES); - error_log($logMessage); - } - /** * Mappt SecurityLogLevel auf Framework LogLevel */ @@ -150,15 +131,4 @@ final readonly class SecurityEventLogger SecurityLogLevel::FATAL => LogLevel::CRITICAL, }; } - - /** - * Factory-Methode für Standard-Konfiguration - */ - public static function create(?Logger $logger = null): self - { - return new self( - $logger, - $_ENV['APP_NAME'] ?? $_ENV['APP_ID'] ?? 'app' - ); - } } diff --git a/src/Framework/ErrorHandling/View/ErrorTemplateRenderer.php b/src/Framework/ErrorHandling/View/ErrorTemplateRenderer.php index a2d17c06..83cb7b66 100644 --- a/src/Framework/ErrorHandling/View/ErrorTemplateRenderer.php +++ b/src/Framework/ErrorHandling/View/ErrorTemplateRenderer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\ErrorHandling\View; +use App\Framework\Config\AppConfig; use App\Framework\Core\ValueObjects\Byte; use App\Framework\Core\ValueObjects\Duration; use App\Framework\ErrorHandling\StackTrace; @@ -23,6 +24,7 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface { public function __construct( private TemplateRenderer $renderer, + private AppConfig $appConfig, private string $debugTemplate = 'enhanced-debug', private string $productionTemplate = 'production' ) { @@ -65,7 +67,7 @@ final readonly class ErrorTemplateRenderer implements ErrorViewRendererInterface 'userAgent' => (string) ($context->request->userAgent ?? 'Unknown'), 'traceCount' => 0, // Will be updated below if trace is available // Environment information - 'environment' => $_ENV['APP_ENV'] ?? 'development', + 'environment' => $this->appConfig->type->value, 'debugMode' => $isDebug ? 'Enabled' : 'Disabled', 'phpVersion' => PHP_VERSION, 'frameworkVersion' => '1.0.0-dev', diff --git a/src/Framework/ErrorReporting/ErrorReport.php b/src/Framework/ErrorReporting/ErrorReport.php index a095df04..f4645631 100644 --- a/src/Framework/ErrorReporting/ErrorReport.php +++ b/src/Framework/ErrorReporting/ErrorReport.php @@ -48,7 +48,8 @@ final readonly class ErrorReport public static function fromThrowable( Throwable $throwable, string $level = 'error', - array $context = [] + array $context = [], + ?string $environment = null ): self { return new self( id: self::generateId(), @@ -60,7 +61,7 @@ final readonly class ErrorReport line: $throwable->getLine(), trace: $throwable->getTraceAsString(), context: $context, - environment: $_ENV['APP_ENV'] ?? 'production', + environment: $environment ?? 'production', serverInfo: self::getServerInfo() ); } @@ -72,7 +73,8 @@ final readonly class ErrorReport string $level, string $message, array $context = [], - ?Throwable $exception = null + ?Throwable $exception = null, + ?string $environment = null ): self { return new self( id: self::generateId(), @@ -84,7 +86,7 @@ final readonly class ErrorReport line: $exception?->getLine() ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['line'] ?? 0, trace: $exception?->getTraceAsString() ?? self::getCurrentTrace(), context: $context, - environment: $_ENV['APP_ENV'] ?? 'production', + environment: $environment ?? 'production', serverInfo: self::getServerInfo() ); } diff --git a/src/Framework/ErrorReporting/ErrorReportingInitializer.php b/src/Framework/ErrorReporting/ErrorReportingInitializer.php index aa096d53..dc947718 100644 --- a/src/Framework/ErrorReporting/ErrorReportingInitializer.php +++ b/src/Framework/ErrorReporting/ErrorReportingInitializer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\ErrorReporting; +use App\Framework\Config\Environment; use App\Framework\Database\ConnectionInterface; use App\Framework\DateTime\Clock; use App\Framework\DI\Container; @@ -25,7 +26,8 @@ final readonly class ErrorReportingInitializer #[Initializer] public function initialize(Container $container): void { - $enabled = (bool) ($_ENV['ERROR_REPORTING_ENABLED'] ?? true); + $env = $container->get(Environment::class); + $enabled = $env->getBool('ERROR_REPORTING_ENABLED', true); // Storage $container->bind(ErrorReportStorageInterface::class, function (Container $container) use ($enabled) { @@ -55,6 +57,7 @@ final readonly class ErrorReportingInitializer return new NullErrorReporter(); } + $env = $container->get(Environment::class); $processors = []; $filters = []; @@ -68,15 +71,17 @@ final readonly class ErrorReportingInitializer } // Add environment-based filters - if (($_ENV['ERROR_REPORTING_FILTER_LEVELS'] ?? null)) { - $allowedLevels = explode(',', $_ENV['ERROR_REPORTING_FILTER_LEVELS']); + $filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', ''); + if ($filterLevels !== '') { + $allowedLevels = explode(',', $filterLevels); $filters[] = function (ErrorReport $report) use ($allowedLevels) { return in_array($report->level, $allowedLevels); }; } // Add environment filter for production - if (($_ENV['APP_ENV'] ?? 'production') === 'production') { + $appEnv = $env->getString('APP_ENV', 'production'); + if ($appEnv === 'production') { $filters[] = function (ErrorReport $report) { // Don't report debug/info in production return ! in_array($report->level, ['debug', 'info']); @@ -88,7 +93,7 @@ final readonly class ErrorReportingInitializer clock: $container->get(Clock::class), logger: $container->has(Logger::class) ? $container->get(Logger::class) : null, queue: $container->has(Queue::class) ? $container->get(Queue::class) : null, - asyncProcessing: (bool) ($_ENV['ERROR_REPORTING_ASYNC'] ?? true), + asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true), processors: $processors, filters: $filters ); @@ -100,6 +105,7 @@ final readonly class ErrorReportingInitializer throw new \RuntimeException('ErrorReporter is disabled. Use ErrorReporterInterface instead.'); } + $env = $container->get(Environment::class); $processors = []; $filters = []; @@ -113,15 +119,17 @@ final readonly class ErrorReportingInitializer } // Add environment-based filters - if (($_ENV['ERROR_REPORTING_FILTER_LEVELS'] ?? null)) { - $allowedLevels = explode(',', $_ENV['ERROR_REPORTING_FILTER_LEVELS']); + $filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', ''); + if ($filterLevels !== '') { + $allowedLevels = explode(',', $filterLevels); $filters[] = function (ErrorReport $report) use ($allowedLevels) { return in_array($report->level, $allowedLevels); }; } // Add environment filter for production - if (($_ENV['APP_ENV'] ?? 'production') === 'production') { + $appEnv = $env->getString('APP_ENV', 'production'); + if ($appEnv === 'production') { $filters[] = function (ErrorReport $report) { // Don't report debug/info in production return ! in_array($report->level, ['debug', 'info']); @@ -133,17 +141,17 @@ final readonly class ErrorReportingInitializer clock: $container->get(Clock::class), logger: $container->has(Logger::class) ? $container->get(Logger::class) : null, queue: $container->has(Queue::class) ? $container->get(Queue::class) : null, - asyncProcessing: (bool) ($_ENV['ERROR_REPORTING_ASYNC'] ?? true), + asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true), processors: $processors, filters: $filters ); }); // Middleware - $container->bind(ErrorReportingMiddleware::class, function (Container $container) { + $container->bind(ErrorReportingMiddleware::class, function (Container $container) use ($enabled) { return new ErrorReportingMiddleware( reporter: $container->get(ErrorReporter::class), - enabled: (bool) ($_ENV['ERROR_REPORTING_ENABLED'] ?? true) + enabled: $enabled ); }); diff --git a/src/Framework/Exception/ErrorHandlerContext.php b/src/Framework/Exception/ErrorHandlerContext.php index 51dd0cb0..5b6e911c 100644 --- a/src/Framework/Exception/ErrorHandlerContext.php +++ b/src/Framework/Exception/ErrorHandlerContext.php @@ -145,8 +145,6 @@ final readonly class ErrorHandlerContext 'port' => $this->request->port, 'request_uri' => $this->request->requestUri, 'request_method' => $this->request->requestMethod, - 'region' => $_ENV['AWS_REGION'] ?? 'unknown', - 'geo' => $_ENV['GEO_LOCATION'] ?? 'unknown', ]; // Security-Event-spezifische Daten falls verfügbar diff --git a/src/Framework/Filesystem/FilesystemInitializer.php b/src/Framework/Filesystem/FilesystemInitializer.php index d286326a..1525d1ad 100644 --- a/src/Framework/Filesystem/FilesystemInitializer.php +++ b/src/Framework/Filesystem/FilesystemInitializer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Filesystem; +use App\Framework\Config\Environment; use App\Framework\DI\Container; use App\Framework\DI\Initializer; use App\Framework\Filesystem\Serializers\CsvSerializer; @@ -23,26 +24,28 @@ final readonly class FilesystemInitializer public function initializeFilesystem(Container $container): void { // Filesystem Config - $container->singleton(FilesystemConfig::class, function () { + $container->singleton(FilesystemConfig::class, function (Container $container) { + $env = $container->get(Environment::class); + return new FilesystemConfig( - defaultStorage: $_ENV['FILESYSTEM_DEFAULT_STORAGE'] ?? 'local', + defaultStorage: $env->getString('FILESYSTEM_DEFAULT_STORAGE', 'local'), storages: [ 'local' => [ 'type' => 'file', - 'path' => $_ENV['FILESYSTEM_LOCAL_PATH'] ?? '/var/www/html/storage', + 'path' => $env->getString('FILESYSTEM_LOCAL_PATH', '/var/www/html/storage'), ], 'temp' => [ 'type' => 'file', - 'path' => $_ENV['FILESYSTEM_TEMP_PATH'] ?? '/tmp', + 'path' => $env->getString('FILESYSTEM_TEMP_PATH', '/tmp'), ], 'analytics' => [ 'type' => 'file', - 'path' => $_ENV['ANALYTICS_DATA_PATH'] ?? '/var/www/html/storage/analytics', + 'path' => $env->getString('ANALYTICS_DATA_PATH', '/var/www/html/storage/analytics'), ], ], - enableCompression: filter_var($_ENV['FILESYSTEM_COMPRESSION'] ?? 'false', FILTER_VALIDATE_BOOLEAN), - enableAtomicWrites: filter_var($_ENV['FILESYSTEM_ATOMIC_WRITES'] ?? 'true', FILTER_VALIDATE_BOOLEAN), - enableFileLocking: filter_var($_ENV['FILESYSTEM_FILE_LOCKING'] ?? 'true', FILTER_VALIDATE_BOOLEAN) + enableCompression: $env->getBool('FILESYSTEM_COMPRESSION', false), + enableAtomicWrites: $env->getBool('FILESYSTEM_ATOMIC_WRITES', true), + enableFileLocking: $env->getBool('FILESYSTEM_FILE_LOCKING', true) ); }); @@ -72,12 +75,13 @@ final readonly class FilesystemInitializer }); // FileValidator - Default Validator (always cached for performance) - $container->singleton(FileValidator::class, function () { + $container->singleton(FileValidator::class, function (Container $container) { + $env = $container->get(Environment::class); $validator = FileValidator::createDefault(); // Always use caching for optimal performance // Disable only for debugging: FILESYSTEM_DISABLE_CACHE=true - $disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN); + $disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false); if ($disableCache) { return $validator; @@ -91,10 +95,11 @@ final readonly class FilesystemInitializer }); // FileValidator - Upload Validator (always cached) - $container->singleton('filesystem.validator.upload', function () { + $container->singleton('filesystem.validator.upload', function (Container $container) { + $env = $container->get(Environment::class); $validator = FileValidator::forUploads(); - $disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN); + $disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false); if ($disableCache) { return $validator; @@ -104,10 +109,11 @@ final readonly class FilesystemInitializer }); // FileValidator - Image Validator (always cached) - $container->singleton('filesystem.validator.image', function () { + $container->singleton('filesystem.validator.image', function (Container $container) { + $env = $container->get(Environment::class); $validator = FileValidator::forImages(); - $disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN); + $disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false); if ($disableCache) { return $validator; @@ -144,7 +150,8 @@ final readonly class FilesystemInitializer { // Always enable storage caching for optimal performance // Disable only for debugging: FILESYSTEM_DISABLE_CACHE=true - $disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN); + $env = $container->get(Environment::class); + $disableCache = $env->getBool('FILESYSTEM_DISABLE_CACHE', false); $enableStorageCache = !$disableCache; // Default Storage - nutzt PathProvider aus Container diff --git a/src/Framework/Http/MiddlewareInvoker.php b/src/Framework/Http/MiddlewareInvoker.php index 605168d1..4b81ecdf 100644 --- a/src/Framework/Http/MiddlewareInvoker.php +++ b/src/Framework/Http/MiddlewareInvoker.php @@ -6,6 +6,7 @@ namespace App\Framework\Http; use App\Framework\Cache\Cache; use App\Framework\CircuitBreaker\CircuitBreaker; +use App\Framework\Config\Environment; use App\Framework\DI\Container; use App\Framework\Http\Exceptions\MiddlewareTimeoutException; use App\Framework\Http\Metrics\MiddlewareMetricsCollector; @@ -46,7 +47,18 @@ final readonly class MiddlewareInvoker ?MiddlewareMetricsCollector $metricsCollector = null ) { $this->logger = $this->container->get(DefaultLogger::class); - $this->defaultTimeout = $defaultTimeout ?? (float)($_ENV['MIDDLEWARE_TIMEOUT'] ?? 5.0); + + if ($defaultTimeout === null) { + try { + $env = $this->container->get(Environment::class); + $this->defaultTimeout = $env->getFloat('MIDDLEWARE_TIMEOUT', 5.0); + } catch (\Throwable) { + $this->defaultTimeout = 5.0; + } + } else { + $this->defaultTimeout = $defaultTimeout; + } + $this->middlewareTimeouts = $middlewareTimeouts; $this->circuitBreaker = $circuitBreaker ?? new MiddlewareCircuitBreaker( $this->container->get(CircuitBreaker::class) diff --git a/src/Framework/Http/MiddlewareManager.php b/src/Framework/Http/MiddlewareManager.php index bf32066e..f880eed8 100644 --- a/src/Framework/Http/MiddlewareManager.php +++ b/src/Framework/Http/MiddlewareManager.php @@ -204,7 +204,7 @@ final readonly class MiddlewareManager implements MiddlewareManagerInterface \App\Framework\Http\Session\SessionMiddleware::class, // 2. Security und Rate Limiting - RateLimitMiddleware::class, + //RateLimitMiddleware::class, #\App\Application\Security\Middleware\SecurityEventMiddleware::class, // 3. Headers und CORS diff --git a/src/Framework/Http/RequestId.php b/src/Framework/Http/RequestId.php index d14968d9..7a00ad37 100644 --- a/src/Framework/Http/RequestId.php +++ b/src/Framework/Http/RequestId.php @@ -19,13 +19,11 @@ final readonly class RequestId /** * Erstellt eine neue Request-ID oder parsed eine bestehende * + * @param string $secret Das Secret für die HMAC-Signatur (REQUIRED) * @param string|null $combined Wenn nicht null, wird diese ID validiert und verwendet - * @param string $secret Das Secret für die HMAC-Signatur */ - public function __construct(?string $combined = null, string $secret = '') + public function __construct(string $secret, ?string $combined = null) { - // Secret über eine Umgebungsvariable beziehen, falls nicht angegeben - $secret = $secret ?: ($_ENV['APP_SECRET'] ?? 'default-secret-change-me'); if ($combined !== null && self::isValidFormat($combined)) { // Bestehende ID parsen diff --git a/src/Framework/Http/RequestIdGenerator.php b/src/Framework/Http/RequestIdGenerator.php index 025ee2cf..dd308e7b 100644 --- a/src/Framework/Http/RequestIdGenerator.php +++ b/src/Framework/Http/RequestIdGenerator.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\Http; use App\Framework\Attributes\Singleton; +use App\Framework\Config\Environment; /** * Service zur Verwaltung der Request-ID für den aktuellen Request. @@ -17,15 +18,22 @@ final class RequestIdGenerator private ?RequestId $requestId = null; - private string $secret; + private readonly string $secret; /** - * Initialisiert den RequestIdGenerator mit einem optionalen Secret + * Initialisiert den RequestIdGenerator mit Environment für Secret-Auflösung */ - public function __construct(string $secret = '') + public function __construct(?Environment $env = null, string $secret = '') { - // Secret über eine Umgebungsvariable beziehen, falls nicht angegeben - $this->secret = $secret ?: ($_ENV['APP_SECRET'] ?? 'default-secret-change-me'); + // Fallback für BC: Wenn kein Environment übergeben wird und Secret angegeben ist + if ($secret !== '') { + $this->secret = $secret; + } elseif ($env !== null) { + $this->secret = $env->getString('APP_SECRET', 'default-secret-change-me'); + } else { + // Final fallback für alte Verwendung ohne Environment + $this->secret = 'default-secret-change-me'; + } } /** diff --git a/src/Framework/Http/Session/SessionFingerprintConfig.php b/src/Framework/Http/Session/SessionFingerprintConfig.php index 13f5ef28..23ff9e41 100644 --- a/src/Framework/Http/Session/SessionFingerprintConfig.php +++ b/src/Framework/Http/Session/SessionFingerprintConfig.php @@ -156,18 +156,13 @@ final readonly class SessionFingerprintConfig /** * Erstellt eine Konfiguration aus Umgebungsvariablen */ - public static function fromEnvironment(): self + public static function fromEnvironment(\App\Framework\Config\Environment $env): self { - $strictMode = filter_var( - $_ENV['SESSION_FINGERPRINT_STRICT'] ?? false, - FILTER_VALIDATE_BOOLEAN - ); + $strictMode = $env->getBool('SESSION_FINGERPRINT_STRICT', false); // Default threshold abhängig vom Modus $defaultThreshold = $strictMode ? 1.0 : 0.7; - $threshold = isset($_ENV['SESSION_FINGERPRINT_THRESHOLD']) - ? (float) $_ENV['SESSION_FINGERPRINT_THRESHOLD'] - : $defaultThreshold; + $threshold = $env->getFloat('SESSION_FINGERPRINT_THRESHOLD', $defaultThreshold); // Auto-Korrektur: Im strict mode MUSS threshold 1.0 sein if ($strictMode && $threshold < 1.0) { @@ -180,30 +175,12 @@ final readonly class SessionFingerprintConfig // Überschreibe mit spezifischen Env-Vars wenn vorhanden return new self( strictMode: $strictMode, - userAgent: filter_var( - $_ENV['SESSION_FINGERPRINT_USER_AGENT'] ?? $config->userAgent, - FILTER_VALIDATE_BOOLEAN - ), - acceptLanguage: filter_var( - $_ENV['SESSION_FINGERPRINT_ACCEPT_LANGUAGE'] ?? $config->acceptLanguage, - FILTER_VALIDATE_BOOLEAN - ), - acceptEncoding: filter_var( - $_ENV['SESSION_FINGERPRINT_ACCEPT_ENCODING'] ?? $config->acceptEncoding, - FILTER_VALIDATE_BOOLEAN - ), - ipPrefix: filter_var( - $_ENV['SESSION_FINGERPRINT_IP_PREFIX'] ?? $config->ipPrefix, - FILTER_VALIDATE_BOOLEAN - ), - secChUa: filter_var( - $_ENV['SESSION_FINGERPRINT_SEC_CH_UA'] ?? $config->secChUa, - FILTER_VALIDATE_BOOLEAN - ), - dnt: filter_var( - $_ENV['SESSION_FINGERPRINT_DNT'] ?? $config->dnt, - FILTER_VALIDATE_BOOLEAN - ), + userAgent: $env->getBool('SESSION_FINGERPRINT_USER_AGENT', $config->userAgent), + acceptLanguage: $env->getBool('SESSION_FINGERPRINT_ACCEPT_LANGUAGE', $config->acceptLanguage), + acceptEncoding: $env->getBool('SESSION_FINGERPRINT_ACCEPT_ENCODING', $config->acceptEncoding), + ipPrefix: $env->getBool('SESSION_FINGERPRINT_IP_PREFIX', $config->ipPrefix), + secChUa: $env->getBool('SESSION_FINGERPRINT_SEC_CH_UA', $config->secChUa), + dnt: $env->getBool('SESSION_FINGERPRINT_DNT', $config->dnt), similarityThreshold: $threshold, ); } diff --git a/src/Framework/Http/Session/SessionInitializer.php b/src/Framework/Http/Session/SessionInitializer.php index f73f7c78..b711a28b 100644 --- a/src/Framework/Http/Session/SessionInitializer.php +++ b/src/Framework/Http/Session/SessionInitializer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\Http\Session; +use App\Framework\Config\Environment; use App\Framework\Context\ContextType; use App\Framework\Core\Events\EventDispatcher; use App\Framework\DateTime\Clock; @@ -55,7 +56,8 @@ final readonly class SessionInitializer } // Session Fingerprinting konfigurieren - $fingerprintConfig = SessionFingerprintConfig::fromEnvironment(); + $env = $this->container->get(Environment::class); + $fingerprintConfig = SessionFingerprintConfig::fromEnvironment($env); $fingerprint = new SessionFingerprint($fingerprintConfig); // EventDispatcher optional laden @@ -65,7 +67,8 @@ final readonly class SessionInitializer } // Cookie-Konfiguration basierend auf Umgebung - $isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production'; + $appEnv = $env->getString('APP_ENV', 'development'); + $isProduction = $appEnv === 'production'; $cookieConfig = $isProduction ? SessionCookieConfig::forProduction() : SessionCookieConfig::forDevelopment(); diff --git a/src/Framework/MachineLearning/ModelManagement/MLConfig.php b/src/Framework/MachineLearning/ModelManagement/MLConfig.php index def55fb0..80ab1a0e 100644 --- a/src/Framework/MachineLearning/ModelManagement/MLConfig.php +++ b/src/Framework/MachineLearning/ModelManagement/MLConfig.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\MachineLearning\ModelManagement; +use App\Framework\Config\Environment; use App\Framework\Core\ValueObjects\Duration; /** @@ -53,21 +54,19 @@ final readonly class MLConfig /** * Create configuration from environment */ - public static function fromEnvironment(array $env = []): self + public static function fromEnvironment(Environment $env): self { - $getEnv = fn(string $key, mixed $default = null): mixed => $env[$key] ?? $_ENV[$key] ?? $default; - return new self( - monitoringEnabled: filter_var($getEnv('ML_MONITORING_ENABLED', true), FILTER_VALIDATE_BOOLEAN), - driftThreshold: (float) $getEnv('ML_DRIFT_THRESHOLD', 0.15), - performanceWindow: Duration::fromHours((int) $getEnv('ML_PERFORMANCE_WINDOW_HOURS', 24)), - autoTuningEnabled: filter_var($getEnv('ML_AUTO_TUNING_ENABLED', false), FILTER_VALIDATE_BOOLEAN), - predictionCacheTtl: Duration::fromSeconds((int) $getEnv('ML_PREDICTION_CACHE_TTL', 3600)), - modelCacheTtl: Duration::fromSeconds((int) $getEnv('ML_MODEL_CACHE_TTL', 7200)), - baselineUpdateInterval: Duration::fromSeconds((int) $getEnv('ML_BASELINE_UPDATE_INTERVAL', 86400)), - minPredictionsForDrift: (int) $getEnv('ML_MIN_PREDICTIONS_FOR_DRIFT', 100), - confidenceAlertThreshold: (float) $getEnv('ML_CONFIDENCE_ALERT_THRESHOLD', 0.65), - accuracyAlertThreshold: (float) $getEnv('ML_ACCURACY_ALERT_THRESHOLD', 0.75) + monitoringEnabled: $env->getBool('ML_MONITORING_ENABLED', true), + driftThreshold: $env->getFloat('ML_DRIFT_THRESHOLD', 0.15), + performanceWindow: Duration::fromHours($env->getInt('ML_PERFORMANCE_WINDOW_HOURS', 24)), + autoTuningEnabled: $env->getBool('ML_AUTO_TUNING_ENABLED', false), + predictionCacheTtl: Duration::fromSeconds($env->getInt('ML_PREDICTION_CACHE_TTL', 3600)), + modelCacheTtl: Duration::fromSeconds($env->getInt('ML_MODEL_CACHE_TTL', 7200)), + baselineUpdateInterval: Duration::fromSeconds($env->getInt('ML_BASELINE_UPDATE_INTERVAL', 86400)), + minPredictionsForDrift: $env->getInt('ML_MIN_PREDICTIONS_FOR_DRIFT', 100), + confidenceAlertThreshold: $env->getFloat('ML_CONFIDENCE_ALERT_THRESHOLD', 0.65), + accuracyAlertThreshold: $env->getFloat('ML_ACCURACY_ALERT_THRESHOLD', 0.75) ); } diff --git a/src/Framework/Notification/Channels/Telegram/Webhook/TelegramWebhookController.php b/src/Framework/Notification/Channels/Telegram/Webhook/TelegramWebhookController.php index f62f1768..15ae5024 100644 --- a/src/Framework/Notification/Channels/Telegram/Webhook/TelegramWebhookController.php +++ b/src/Framework/Notification/Channels/Telegram/Webhook/TelegramWebhookController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\Notification\Channels\Telegram\Webhook; use App\Framework\Attributes\Route; +use App\Framework\Config\Environment; use App\Framework\Http\HttpRequest; use App\Framework\Http\Method; use App\Framework\Router\Result\JsonResult; @@ -20,7 +21,8 @@ use App\Framework\Webhook\Processing\WebhookRequestHandler; final readonly class TelegramWebhookController { public function __construct( - private WebhookRequestHandler $webhookHandler + private WebhookRequestHandler $webhookHandler, + private Environment $environment ) { } @@ -47,7 +49,7 @@ final readonly class TelegramWebhookController public function handleWebhook(HttpRequest $request): JsonResult { // Get secret token from environment - $secretToken = $_ENV['TELEGRAM_WEBHOOK_SECRET'] ?? ''; + $secretToken = $this->environment->getString('TELEGRAM_WEBHOOK_SECRET', ''); if (empty($secretToken)) { return new JsonResult([ diff --git a/src/Framework/Router/HttpRouter.php b/src/Framework/Router/HttpRouter.php index 8662a3bf..722a1043 100644 --- a/src/Framework/Router/HttpRouter.php +++ b/src/Framework/Router/HttpRouter.php @@ -24,6 +24,8 @@ final readonly class HttpRouter implements Router $host = $request->server->getHttpHost(); $subdomain = $this->extractSubdomain($host); + error_log("🔍 ROUTER DEBUG: Host={$host}, Subdomain=" . ($subdomain ?: 'NONE') . ", Path={$path}, Method={$method->value}"); + // 1. Try subdomain-specific routes first if ($subdomain) { $subdomainKey = 'exact:' . $subdomain; diff --git a/src/Framework/Router/RouterSetup.php b/src/Framework/Router/RouterSetup.php index 240b86e2..b207b66c 100644 --- a/src/Framework/Router/RouterSetup.php +++ b/src/Framework/Router/RouterSetup.php @@ -35,12 +35,39 @@ final readonly class RouterSetup #$routeCache = new RouteCache($this->pathProvider->getCachePath('routes.cache.php')); $routeCount = $this->results->attributes->getCount(Route::class); + error_log("🚦 ROUTER SETUP: Route count from discovery = {$routeCount}"); if ($routeCount > 0) { $discoveredRoutes = $this->results->attributes->get(Route::class); // Direct DiscoveredAttribute API - $optimizedRoutes = $this->routeCompiler->compileOptimized(...$discoveredRoutes); + error_log("🚦 ROUTER SETUP: About to compile routes"); + try { + $optimizedRoutes = $this->routeCompiler->compileOptimized(...$discoveredRoutes); + error_log("🚦 ROUTER SETUP: Routes compiled successfully"); + } catch (\Throwable $e) { + error_log("🚦 ROUTER SETUP: ERROR compiling routes: " . $e->getMessage()); + error_log("🚦 ROUTER SETUP: Exception trace: " . $e->getTraceAsString()); + throw $e; + } + + // DEBUG: Log available static routes structure + error_log("🚦 ROUTER SETUP: About to analyze route structure"); + $staticRoutes = $optimizedRoutes->getStaticRoutes(); + error_log("🚦 ROUTER SETUP: HTTP Methods count = " . count($staticRoutes)); + + // Check GET method subdomain keys + $getSubdomainKeys = $optimizedRoutes->getSubdomainKeys(\App\Framework\Http\Method::GET); + error_log("🚦 ROUTER SETUP: GET method has subdomain keys: " . json_encode($getSubdomainKeys)); + + // Log routes per subdomain for GET + foreach ($getSubdomainKeys as $subdomainKey) { + $routes = $optimizedRoutes->getStaticRoutesForSubdomain(\App\Framework\Http\Method::GET, $subdomainKey); + error_log("🚦 ROUTER SETUP: GET subdomain '{$subdomainKey}' has " . count($routes) . " routes"); + foreach ($routes as $path => $route) { + error_log("🚦 ROUTER SETUP: - {$path}"); + } + } // Cache speichern #$routeCache->save($optimizedRoutes); diff --git a/src/Framework/Security/RequestSigning/RequestSigningConfig.php b/src/Framework/Security/RequestSigning/RequestSigningConfig.php index b7d5c3a9..ccaa91d9 100644 --- a/src/Framework/Security/RequestSigning/RequestSigningConfig.php +++ b/src/Framework/Security/RequestSigning/RequestSigningConfig.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace App\Framework\Security\RequestSigning; +use App\Framework\Config\Environment; + /** * Configuration for request signing functionality */ @@ -24,26 +26,28 @@ final readonly class RequestSigningConfig /** * Create configuration from environment variables */ - public static function fromEnvironment(): self + public static function fromEnvironment(Environment $env): self { - $enabled = filter_var($_ENV['REQUEST_SIGNING_ENABLED'] ?? 'false', FILTER_VALIDATE_BOOLEAN); - $requireSignature = filter_var($_ENV['REQUEST_SIGNING_REQUIRED'] ?? 'false', FILTER_VALIDATE_BOOLEAN); + $enabled = $env->getBool('REQUEST_SIGNING_ENABLED', false); + $requireSignature = $env->getBool('REQUEST_SIGNING_REQUIRED', false); $exemptPaths = []; - if (isset($_ENV['REQUEST_SIGNING_EXEMPT_PATHS'])) { - $exemptPaths = array_filter(array_map('trim', explode(',', $_ENV['REQUEST_SIGNING_EXEMPT_PATHS']))); + $exemptPathsString = $env->getString('REQUEST_SIGNING_EXEMPT_PATHS', ''); + if ($exemptPathsString !== '') { + $exemptPaths = array_filter(array_map('trim', explode(',', $exemptPathsString))); } $defaultHeaders = ['(request-target)', 'host', 'date']; - if (isset($_ENV['REQUEST_SIGNING_DEFAULT_HEADERS'])) { - $defaultHeaders = array_filter(array_map('trim', explode(',', $_ENV['REQUEST_SIGNING_DEFAULT_HEADERS']))); + $defaultHeadersString = $env->getString('REQUEST_SIGNING_DEFAULT_HEADERS', ''); + if ($defaultHeadersString !== '') { + $defaultHeaders = array_filter(array_map('trim', explode(',', $defaultHeadersString))); } - $maxClockSkew = (int) ($_ENV['REQUEST_SIGNING_MAX_CLOCK_SKEW'] ?? 300); - $defaultExpiry = (int) ($_ENV['REQUEST_SIGNING_DEFAULT_EXPIRY'] ?? 3600); + $maxClockSkew = $env->getInt('REQUEST_SIGNING_MAX_CLOCK_SKEW', 300); + $defaultExpiry = $env->getInt('REQUEST_SIGNING_DEFAULT_EXPIRY', 3600); - $algorithm = SigningAlgorithm::tryFrom($_ENV['REQUEST_SIGNING_ALGORITHM'] ?? 'hmac-sha256') - ?? SigningAlgorithm::HMAC_SHA256; + $algorithmString = $env->getString('REQUEST_SIGNING_ALGORITHM', 'hmac-sha256'); + $algorithm = SigningAlgorithm::tryFrom($algorithmString) ?? SigningAlgorithm::HMAC_SHA256; return new self( enabled: $enabled, diff --git a/src/Framework/Security/RequestSigning/RequestSigningInitializer.php b/src/Framework/Security/RequestSigning/RequestSigningInitializer.php index 64b1e70c..28ca5657 100644 --- a/src/Framework/Security/RequestSigning/RequestSigningInitializer.php +++ b/src/Framework/Security/RequestSigning/RequestSigningInitializer.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\Security\RequestSigning; use App\Framework\Cache\Cache; +use App\Framework\Config\Environment; use App\Framework\Database\EntityManager; use App\Framework\DateTime\Clock; use App\Framework\DI\Container; @@ -58,7 +59,9 @@ final readonly class RequestSigningInitializer */ private function getConfig(): RequestSigningConfig { - $isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production'; + $env = $this->container->get(Environment::class); + $appEnv = $env->getString('APP_ENV', 'development'); + $isProduction = $appEnv === 'production'; return $isProduction ? RequestSigningConfig::production() @@ -70,7 +73,9 @@ final readonly class RequestSigningInitializer */ private function createKeyRepository(RequestSigningConfig $config): SigningKeyRepository { - $isProduction = ($_ENV['APP_ENV'] ?? 'development') === 'production'; + $env = $this->container->get(Environment::class); + $appEnv = $env->getString('APP_ENV', 'development'); + $isProduction = $appEnv === 'production'; if ($isProduction && $this->container->has(EntityManager::class)) { return new EntityManagerSigningKeyRepository( @@ -94,7 +99,9 @@ final readonly class RequestSigningInitializer } // Add a default development key - if (($_ENV['APP_ENV'] ?? 'development') === 'development') { + $env = $this->container->get(Environment::class); + $appEnv = $env->getString('APP_ENV', 'development'); + if ($appEnv === 'development') { if ($keyRepository instanceof InMemorySigningKeyRepository) { $keyRepository->addDefaultTestKey(); } @@ -109,9 +116,11 @@ final readonly class RequestSigningInitializer */ private function loadKeysFromEnvironment(SigningKeyRepository $keyRepository): void { + $env = $this->container->get(Environment::class); + // Load HMAC keys from environment - $hmacKeys = $_ENV['REQUEST_SIGNING_HMAC_KEYS'] ?? ''; - if ($hmacKeys) { + $hmacKeys = $env->getString('REQUEST_SIGNING_HMAC_KEYS', ''); + if ($hmacKeys !== '') { $keys = json_decode($hmacKeys, true); if (is_array($keys)) { foreach ($keys as $keyData) { @@ -138,8 +147,8 @@ final readonly class RequestSigningInitializer } // Load RSA keys from environment - $rsaKeys = $_ENV['REQUEST_SIGNING_RSA_KEYS'] ?? ''; - if ($rsaKeys) { + $rsaKeys = $env->getString('REQUEST_SIGNING_RSA_KEYS', ''); + if ($rsaKeys !== '') { $keys = json_decode($rsaKeys, true); if (is_array($keys)) { foreach ($keys as $keyData) { diff --git a/src/Framework/View/Dom/Transformer/XComponentTransformer.php b/src/Framework/View/Dom/Transformer/XComponentTransformer.php index f9826053..3e1bc7ff 100644 --- a/src/Framework/View/Dom/Transformer/XComponentTransformer.php +++ b/src/Framework/View/Dom/Transformer/XComponentTransformer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\View\Dom\Transformer; +use App\Framework\Config\AppConfig; use App\Framework\LiveComponents\Contracts\ComponentRegistryInterface; use App\Framework\LiveComponents\Performance\ComponentMetadataCacheInterface; use App\Framework\LiveComponents\ValueObjects\ComponentId; @@ -34,7 +35,8 @@ final class XComponentTransformer implements NodeVisitor, AstTransformer private readonly ComponentRegistryInterface $componentRegistry, private readonly StaticComponentRenderer $staticComponentRenderer, private readonly ComponentMetadataCacheInterface $metadataCache, - private readonly HtmlParser $parser + private readonly HtmlParser $parser, + private readonly AppConfig $appConfig ) { } @@ -391,7 +393,7 @@ final class XComponentTransformer implements NodeVisitor, AstTransformer */ private function handleTransformError(ElementNode $element, \Throwable $e): void { - $isDebug = ($_ENV['APP_ENV'] ?? 'production') === 'development'; + $isDebug = $this->appConfig->isDebug(); if ($isDebug) { // Create error node in place of component diff --git a/src/Framework/View/Processors/PlaceholderReplacer.php b/src/Framework/View/Processors/PlaceholderReplacer.php index 24502ce5..4810a79d 100644 --- a/src/Framework/View/Processors/PlaceholderReplacer.php +++ b/src/Framework/View/Processors/PlaceholderReplacer.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\View\Processors; use App\Framework\Config\AppConfig; +use App\Framework\Config\Environment; use App\Framework\DI\Container; use App\Framework\LiveComponents\ComponentRegistry; use App\Framework\LiveComponents\Contracts\LiveComponentContract; @@ -449,8 +450,13 @@ final class PlaceholderReplacer implements StringProcessor return $appConfig->isDebug(); } catch (\Throwable $e) { - // Fallback zu Environment-Variable falls AppConfig nicht verfügbar - return ($_ENV['APP_ENV'] ?? 'production') === 'development'; + // Fallback zu Environment falls AppConfig nicht verfügbar + try { + $env = $this->container->get(Environment::class); + return $env->getString('APP_ENV', 'production') === 'development'; + } catch (\Throwable) { + return false; // Safe fallback: production mode + } } } diff --git a/src/Framework/View/Processors/XComponentProcessor.php b/src/Framework/View/Processors/XComponentProcessor.php index 43e12191..2dc4c4d4 100644 --- a/src/Framework/View/Processors/XComponentProcessor.php +++ b/src/Framework/View/Processors/XComponentProcessor.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Framework\View\Processors; +use App\Framework\Config\AppConfig; use App\Framework\LiveComponents\Contracts\ComponentRegistryInterface; use App\Framework\LiveComponents\Performance\ComponentMetadataCacheInterface; use App\Framework\LiveComponents\ValueObjects\ComponentData; @@ -42,7 +43,8 @@ final readonly class XComponentProcessor implements DomProcessor public function __construct( private ComponentRegistryInterface $componentRegistry, private ComponentMetadataCacheInterface $metadataCache, - private DomComponentService $componentService + private DomComponentService $componentService, + private AppConfig $appConfig ) { } @@ -349,8 +351,8 @@ final readonly class XComponentProcessor implements DomProcessor HTMLElement $element, \Throwable $e ): void { - // Check if we're in debug mode (via environment or config) - $isDebug = ($_ENV['APP_ENV'] ?? 'production') === 'development'; + // Check if we're in debug mode + $isDebug = $this->appConfig->isDebug(); if ($isDebug) { // Show error message in place of component diff --git a/src/Framework/WebPush/Controllers/WebPushController.php b/src/Framework/WebPush/Controllers/WebPushController.php index 2d06feb2..2e776d86 100644 --- a/src/Framework/WebPush/Controllers/WebPushController.php +++ b/src/Framework/WebPush/Controllers/WebPushController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\WebPush\Controllers; use App\Framework\Attributes\Route; +use App\Framework\Config\Environment; use App\Framework\Http\Method; use App\Framework\Http\Request; use App\Framework\Http\Session\Session; @@ -26,6 +27,7 @@ final readonly class WebPushController private SubscriptionRepository $subscriptionRepository, private WebPushService $webPushService, private Session $session, + private Environment $environment ) { } @@ -305,8 +307,7 @@ final readonly class WebPushController #[Route(path: '/api/push/vapid-key', method: Method::GET)] public function vapidKey(Request $request): JsonResult { - // This should be injected from config - $publicKey = $_ENV['VAPID_PUBLIC_KEY'] ?? null; + $publicKey = $this->environment->getString('VAPID_PUBLIC_KEY'); if ($publicKey === null) { return new JsonResult( diff --git a/src/Framework/WebPush/WebPushInitializer.php b/src/Framework/WebPush/WebPushInitializer.php index 6eb95e80..ae7b388e 100644 --- a/src/Framework/WebPush/WebPushInitializer.php +++ b/src/Framework/WebPush/WebPushInitializer.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Framework\WebPush; use App\Framework\Cache\Cache; +use App\Framework\Config\Environment; use App\Framework\DI\Container; use App\Framework\DI\Initializer; use App\Framework\WebPush\Console\VapidKeyCommands; @@ -42,9 +43,11 @@ final readonly class WebPushInitializer public function initWebPushService(): ?WebPushService { // Try to load VAPID keys from environment - $publicKey = $_ENV['VAPID_PUBLIC_KEY'] ?? null; - $privateKey = $_ENV['VAPID_PRIVATE_KEY'] ?? null; - $subject = $_ENV['VAPID_SUBJECT'] ?? 'mailto:admin@example.com'; + $env = $this->container->get(Environment::class); + + $publicKey = $env->getString('VAPID_PUBLIC_KEY'); + $privateKey = $env->getString('VAPID_PRIVATE_KEY'); + $subject = $env->getString('VAPID_SUBJECT', 'mailto:admin@example.com'); if ($publicKey !== null && $privateKey !== null) { $vapidKeys = new VapidKeyPair($publicKey, $privateKey); diff --git a/tests/debug/check-discovery.php b/tests/debug/check-discovery.php new file mode 100644 index 00000000..8e0f9413 --- /dev/null +++ b/tests/debug/check-discovery.php @@ -0,0 +1,57 @@ +bootstrap(); + + // Get DiscoveryRegistry + $registry = $container->get(DiscoveryRegistry::class); + + // Check Route attribute count + $routeCount = $registry->attributes->getCount(Route::class); + + echo "📊 Discovery Stats:\n"; + echo " - Total Route Attributes Found: {$routeCount}\n\n"; + + if ($routeCount === 0) { + echo "❌ NO ROUTES DISCOVERED!\n"; + echo " This explains why the router cannot find any routes.\n\n"; + + // Check if discovery ran at all + echo "🔍 Checking discovery results...\n"; + $allAttributes = $registry->attributes->all(); + echo " - Total attribute types discovered: " . count($allAttributes) . "\n"; + foreach ($allAttributes as $attrType => $instances) { + echo " - {$attrType}: " . count($instances) . " instances\n"; + } + } else { + echo "✅ Routes discovered successfully!\n"; + echo " Listing first 10 routes:\n\n"; + + $routes = $registry->attributes->get(Route::class); + $count = 0; + foreach ($routes as $routeData) { + if ($count++ >= 10) break; + + $route = $routeData->instance; + echo " - {$route->method->value} {$route->path}\n"; + echo " Controller: {$routeData->className}\n"; + echo " Method: {$routeData->method->name}\n\n"; + } + } + +} catch (\Throwable $e) { + echo "❌ Error: " . $e->getMessage() . "\n"; + echo " File: {$e->getFile()}:{$e->getLine()}\n"; + echo " Stack trace:\n{$e->getTraceAsString()}\n"; +}