diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 8cf65852..9df03e27 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -51,7 +51,7 @@ RUN curl -sS https://getcomposer.org/installer | php \ && mv composer.phar /usr/local/bin/composer # Installiere Xdebug nur im Entwicklungsmodus -ARG ENV=prod +ARG ENV=production RUN if [ "$ENV" = "dev" ]; then \ pecl install xdebug \ && docker-php-ext-enable xdebug; \ @@ -108,3 +108,136 @@ RUN apt-get update && apt-get install -y gosu && apt-get clean && rm -rf /var/li ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD ["php-fpm"] + +############################################# +# Production Stages (Extended Build) +############################################# + +# Stage: Composer Dependencies (Production) +FROM base AS composer-deps-production + +WORKDIR /app + +# Copy composer files +COPY composer.json composer.lock ./ + +# Install production dependencies only +RUN composer install \ + --no-dev \ + --no-scripts \ + --no-autoloader \ + --ignore-platform-reqs \ + --prefer-dist + +# Copy application code +COPY . . + +# Generate optimized autoloader +RUN composer dump-autoload --optimize --classmap-authoritative + +# Stage: Frontend Build +FROM node:20-alpine AS frontend-build + +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json ./ + +# Install npm dependencies +RUN npm ci --production=false + +# Copy source files needed for build +COPY resources ./resources +COPY vite.config.js ./ +COPY tsconfig.json ./ + +# Build production assets +RUN npm run build + +# Stage: Production Runtime +FROM php:8.5.0RC3-fpm AS production + +# Install system dependencies + nginx for production +RUN apt-get update && apt-get install -y \ + nginx \ + curl \ + libzip-dev \ + libpng-dev \ + libjpeg-dev \ + libfreetype6-dev \ + libwebp-dev \ + libavif-dev \ + libxpm-dev \ + libsodium-dev \ + libpq-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install PHP extensions (same as base) +RUN docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + --with-webp \ + --with-avif \ + --with-xpm \ + && docker-php-ext-install -j$(nproc) gd + +RUN docker-php-ext-install -j$(nproc) \ + zip \ + pdo \ + pdo_mysql \ + pdo_pgsql \ + pcntl \ + posix \ + shmop \ + bcmath \ + sodium + +# Install PECL extensions +RUN pecl install apcu redis-6.3.0RC1 \ + && docker-php-ext-enable apcu redis + +# Configure APCu +RUN echo "apc.enable_cli=1" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini \ + && echo "apc.shm_size=128M" >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini + +# Set working directory +WORKDIR /var/www/html + +# Configure PHP for production +COPY docker/php/php.production.ini /usr/local/etc/php/conf.d/zzz-custom.ini +COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini + +# Configure PHP-FPM (clear_env = no for Docker secrets) +COPY docker/php/www.production.conf /usr/local/etc/php-fpm.d/www.conf + +# Configure Nginx for Traefik integration +COPY docker/nginx/nginx.production.conf /etc/nginx/nginx.conf +COPY docker/nginx/default.traefik.conf /etc/nginx/sites-available/default +RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default + +# Copy application code and dependencies +COPY --chown=www-data:www-data . . +COPY --from=composer-deps-production --chown=www-data:www-data /app/vendor ./vendor +COPY --from=frontend-build --chown=www-data:www-data /app/public/assets ./public/assets +COPY --from=frontend-build --chown=www-data:www-data /app/public/.vite ./public/.vite + +# Create required directories +RUN mkdir -p storage/logs storage/cache storage/uploads \ + && chown -R www-data:www-data storage \ + && chmod -R 775 storage + +# Copy entrypoint script for production +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ + CMD curl -f http://localhost/health || exit 1 + +# Expose HTTP only (Traefik handles HTTPS) +EXPOSE 80 + +# Use production entrypoint +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["nginx-php-fpm"] diff --git a/docker/php/php.dev.ini b/docker/php/php.development.ini similarity index 100% rename from docker/php/php.dev.ini rename to docker/php/php.development.ini diff --git a/docker/php/php.prod.ini b/docker/php/php.prod.ini deleted file mode 100644 index 043e74e8..00000000 --- a/docker/php/php.prod.ini +++ /dev/null @@ -1,34 +0,0 @@ -; php.ini für Produktion -include = php.common.ini - -[opcache] -; Aktiviere OPcache -opcache.enable=1 -; Aktiviere OPcache für CLI-Anwendungen (optional) -opcache.enable_cli=0 -; Maximale Speichernutzung für Cache in MB -opcache.memory_consumption=128 -; Maximale Anzahl an gecachten Dateien -opcache.max_accelerated_files=10000 -; 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+ -opcache.jit_buffer_size=100M -opcache.jit=1255 - - -display_errors = Off -display_startup_errors = Off -error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT -memory_limit = 256M -upload_max_filesize = 10M -post_max_size = 12M -max_execution_time = 30 diff --git a/docker/php/php.production.ini b/docker/php/php.production.ini index 557ca72e..043e74e8 100644 --- a/docker/php/php.production.ini +++ b/docker/php/php.production.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/Framework/Cache/Strategies/AccessPattern.php b/src/Framework/Cache/Strategies/AccessPattern.php new file mode 100644 index 00000000..ff27ba79 --- /dev/null +++ b/src/Framework/Cache/Strategies/AccessPattern.php @@ -0,0 +1,58 @@ + */ + private array $accessTimes = []; + + public function __construct( + private readonly int $windowSize + ) {} + + public function recordAccess(): void + { + $this->accessTimes[] = Timestamp::now(); + + // Keep only recent accesses within the window + if (count($this->accessTimes) > $this->windowSize) { + array_shift($this->accessTimes); + } + } + + public function getRecentAccessCount(): int + { + return count($this->accessTimes); + } + + public function getTotalAccesses(): int + { + return count($this->accessTimes); + } + + /** + * Calculate access frequency (accesses per hour) + */ + public function getAccessFrequency(): float + { + if (count($this->accessTimes) < 2) { + return 0.0; + } + + $oldest = $this->accessTimes[0]; + $newest = end($this->accessTimes); + $timeSpan = $newest->diff($oldest); + + if ($timeSpan->toSeconds() <= 0) { + return 0.0; + } + + $hours = $timeSpan->toSeconds() / 3600.0; + + return count($this->accessTimes) / $hours; + } +} diff --git a/src/Framework/Cache/Strategies/AdaptiveTtlStats.php b/src/Framework/Cache/Strategies/AdaptiveTtlStats.php new file mode 100644 index 00000000..e9d60f7b --- /dev/null +++ b/src/Framework/Cache/Strategies/AdaptiveTtlStats.php @@ -0,0 +1,44 @@ +hits++; + } else { + $this->misses++; + } + } + + public function getHitRate(): float + { + $total = $this->hits + $this->misses; + + return $total > 0 ? ($this->hits / $total) : 0.0; + } + + public function getTotalRequests(): int + { + return $this->hits + $this->misses; + } + + public function getHits(): int + { + return $this->hits; + } + + public function getMisses(): int + { + return $this->misses; + } +} diff --git a/src/Framework/Cache/Strategies/HeatMapEntry.php b/src/Framework/Cache/Strategies/HeatMapEntry.php new file mode 100644 index 00000000..9ca9480c --- /dev/null +++ b/src/Framework/Cache/Strategies/HeatMapEntry.php @@ -0,0 +1,90 @@ + */ + private array $accesses = []; + + private int $totalHits = 0; + + private int $totalMisses = 0; + + private float $totalRetrievalTime = 0.0; + + private int $retrievalTimeCount = 0; + + public function __construct( + private readonly CacheKey $key + ) {} + + public function recordAccess(bool $isHit, ?Duration $retrievalTime = null): void + { + $this->accesses[] = [ + 'timestamp' => Timestamp::now(), + 'is_hit' => $isHit, + 'retrieval_time' => $retrievalTime?->toSeconds(), + ]; + + if ($isHit) { + $this->totalHits++; + } else { + $this->totalMisses++; + } + + if ($retrievalTime !== null) { + $this->totalRetrievalTime += $retrievalTime->toSeconds(); + $this->retrievalTimeCount++; + } + + // Keep only recent data to prevent memory bloat + $cutoff = Timestamp::now()->subtract(Duration::fromHours(48)); // Keep 48 hours + $this->accesses = array_filter( + $this->accesses, + fn($access) => $access['timestamp']->isAfter($cutoff) + ); + } + + public function getRecentAccesses(Timestamp $since): array + { + return array_filter( + $this->accesses, + fn($access) => $access['timestamp']->isAfter($since) + ); + } + + public function getHitRate(): float + { + $total = $this->totalHits + $this->totalMisses; + + return $total > 0 ? ($this->totalHits / $total) : 0.0; + } + + public function getAverageRetrievalTime(): float + { + return $this->retrievalTimeCount > 0 ? ($this->totalRetrievalTime / $this->retrievalTimeCount) : 0.0; + } + + public function getTotalAccesses(): int + { + return count($this->accesses); + } + + public function getLastAccessTime(): ?Timestamp + { + if (empty($this->accesses)) { + return null; + } + + return end($this->accesses)['timestamp']; + } + + public function getKey(): CacheKey + { + return $this->key; + } +} diff --git a/src/Framework/Cache/Strategies/PredictionPattern.php b/src/Framework/Cache/Strategies/PredictionPattern.php new file mode 100644 index 00000000..0a1e5471 --- /dev/null +++ b/src/Framework/Cache/Strategies/PredictionPattern.php @@ -0,0 +1,71 @@ + */ + private array $accesses = []; + + /** @var array */ + private array $dependencies = []; + + private mixed $warmingCallback = null; + + public function __construct( + private readonly CacheKey $key + ) {} + + public function recordAccess(Timestamp $timestamp, array $context = []): void + { + $this->accesses[] = [ + 'timestamp' => $timestamp, + 'context' => $context, + ]; + + // Keep only recent accesses to prevent memory bloat + $cutoff = $timestamp->subtract(Duration::fromHours(168)); // 1 week + $this->accesses = array_filter( + $this->accesses, + fn($access) => $access['timestamp']->isAfter($cutoff) + ); + } + + public function addDependency(CacheKey $dependentKey): void + { + $this->dependencies[] = $dependentKey; + } + + public function setWarmingCallback(callable $callback): void + { + $this->warmingCallback = $callback; + } + + public function getKey(): CacheKey + { + return $this->key; + } + + public function getRecentAccesses(int $hours): array + { + $cutoff = Timestamp::now()->subtract(Duration::fromHours($hours)); + + return array_filter( + $this->accesses, + fn($access) => $access['timestamp']->isAfter($cutoff) + ); + } + + public function getDependencies(): array + { + return $this->dependencies; + } + + public function getWarmingCallback(): ?callable + { + return $this->warmingCallback; + } +} diff --git a/src/Framework/Cache/Strategies/WarmingJob.php b/src/Framework/Cache/Strategies/WarmingJob.php new file mode 100644 index 00000000..70de0581 --- /dev/null +++ b/src/Framework/Cache/Strategies/WarmingJob.php @@ -0,0 +1,16 @@ +