startTime = microtime(true); $this->memoryStart = memory_get_usage(); $this->cache = new Cache(); } /** * Performance-Monitoring starten */ public function startMonitoring() { // Query-Logging aktivieren if (getenv('WS_DEBUG') === 'true') { $this->enableQueryLogging(); } // Gzip-Kompression aktivieren $this->enableGzip(); // Browser-Caching aktivieren $this->setBrowserCache(); } /** * Performance-Statistiken abrufen */ public function getStats() { $endTime = microtime(true); $memoryEnd = memory_get_usage(); $peakMemory = memory_get_peak_usage(); return [ 'execution_time' => round(($endTime - $this->startTime) * 1000, 2), // ms 'memory_usage' => $this->formatBytes($memoryEnd - $this->memoryStart), 'peak_memory' => $this->formatBytes($peakMemory), 'queries_count' => count($this->queries), 'cache_hits' => $this->cache->getStats()['hits'] ?? 0, 'cache_misses' => $this->cache->getStats()['misses'] ?? 0 ]; } /** * Query-Logging aktivieren */ private function enableQueryLogging() { // Query-Logger registrieren register_shutdown_function([$this, 'logQueries']); } /** * Gzip-Kompression aktivieren */ private function enableGzip() { if (extension_loaded('zlib') && !ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 1); ini_set('zlib.output_compression_level', 6); } } /** * Browser-Caching setzen */ private function setBrowserCache() { $cacheTime = 3600; // 1 Stunde header('Cache-Control: public, max-age=' . $cacheTime); header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time() + $cacheTime)); header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T')); } /** * Query hinzufügen */ public function addQuery($sql, $params = [], $executionTime = 0) { $this->queries[] = [ 'sql' => $sql, 'params' => $params, 'execution_time' => $executionTime, 'timestamp' => microtime(true) ]; } /** * Queries loggen */ public function logQueries() { if (empty($this->queries)) { return; } $logFile = __DIR__ . '/../../logs/queries.log'; $logDir = dirname($logFile); if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } $log = "=== Query Log " . date('Y-m-d H:i:s') . " ===\n"; $log .= "Total Queries: " . count($this->queries) . "\n"; $log .= "Total Time: " . array_sum(array_column($this->queries, 'execution_time')) . "ms\n\n"; foreach ($this->queries as $i => $query) { $log .= "Query " . ($i + 1) . ":\n"; $log .= "SQL: " . $query['sql'] . "\n"; $log .= "Params: " . json_encode($query['params']) . "\n"; $log .= "Time: " . $query['execution_time'] . "ms\n\n"; } file_put_contents($logFile, $log, FILE_APPEND | LOCK_EX); } /** * Bild-Optimierung */ public function optimizeImage($sourcePath, $destinationPath, $options = []) { $defaultOptions = [ 'quality' => 85, 'max_width' => 1200, 'max_height' => 1200, 'format' => 'jpeg' ]; $options = array_merge($defaultOptions, $options); if (!file_exists($sourcePath)) { return false; } $imageInfo = getimagesize($sourcePath); if (!$imageInfo) { return false; } list($width, $height, $type) = $imageInfo; // Neue Dimensionen berechnen $ratio = min($options['max_width'] / $width, $options['max_height'] / $height); $newWidth = round($width * $ratio); $newHeight = round($height * $ratio); // Bild laden switch ($type) { case IMAGETYPE_JPEG: $source = imagecreatefromjpeg($sourcePath); break; case IMAGETYPE_PNG: $source = imagecreatefrompng($sourcePath); break; case IMAGETYPE_GIF: $source = imagecreatefromgif($sourcePath); break; default: return false; } // Neues Bild erstellen $destination = imagecreatetruecolor($newWidth, $newHeight); // Transparenz für PNG beibehalten if ($type === IMAGETYPE_PNG) { imagealphablending($destination, false); imagesavealpha($destination, true); } // Bild skalieren imagecopyresampled($destination, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); // Verzeichnis erstellen $dir = dirname($destinationPath); if (!is_dir($dir)) { mkdir($dir, 0755, true); } // Bild speichern $result = false; switch ($options['format']) { case 'jpeg': $result = imagejpeg($destination, $destinationPath, $options['quality']); break; case 'png': $result = imagepng($destination, $destinationPath, round($options['quality'] / 10)); break; case 'gif': $result = imagegif($destination, $destinationPath); break; } imagedestroy($source); imagedestroy($destination); return $result; } /** * CSS/JS-Minifizierung */ public function minify($content, $type = 'css') { if ($type === 'css') { return $this->minifyCSS($content); } elseif ($type === 'js') { return $this->minifyJS($content); } return $content; } /** * CSS minifizieren */ private function minifyCSS($css) { // Kommentare entfernen $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css); // Whitespace entfernen $css = preg_replace('/\s+/', ' ', $css); $css = str_replace(['; ', ' {', '{ ', ' }', '} ', ': '], [';', '{', '{', '}', '}', ':'], $css); // Leerzeichen um Operatoren entfernen $css = preg_replace('/\s*([{}:;,>~+^$])\s*/', '$1', $css); return trim($css); } /** * JavaScript minifizieren */ private function minifyJS($js) { // Kommentare entfernen $js = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $js); $js = preg_replace('/\/\/.*$/m', '', $js); // Whitespace entfernen $js = preg_replace('/\s+/', ' ', $js); $js = preg_replace('/\s*([{}:;,()])\s*/', '$1', $js); return trim($js); } /** * Asset-Versioning */ public function getAssetVersion($file) { $filePath = __DIR__ . '/../../public/' . $file; if (file_exists($filePath)) { return filemtime($filePath); } return time(); } /** * Lazy Loading für Bilder */ public function addLazyLoading($html) { // data-src zu src konvertieren für Lazy Loading $html = preg_replace('/]*?)src=(["\'])([^"\']+)\2([^>]*?)>/i', '', $html); // Lazy Loading JavaScript hinzufügen $lazyScript = ' '; return $html . $lazyScript; } /** * CDN-URL generieren */ public function getCdnUrl($path) { $cdnUrl = getenv('CDN_URL'); if ($cdnUrl) { return rtrim($cdnUrl, '/') . '/' . ltrim($path, '/'); } return $path; } /** * Critical CSS extrahieren */ public function extractCriticalCSS($html, $css) { // Einfache Critical CSS Extraktion $criticalSelectors = [ 'body', 'html', '.container', '.navbar', '.header', '.footer', '.btn', '.btn-primary', '.form-control', '.card', '.alert' ]; $criticalCSS = ''; $lines = explode("\n", $css); foreach ($lines as $line) { foreach ($criticalSelectors as $selector) { if (strpos($line, $selector) !== false) { $criticalCSS .= $line . "\n"; break; } } } return $this->minifyCSS($criticalCSS); } /** * Service Worker generieren */ public function generateServiceWorker() { $cacheName = 'webshop-v1'; $assets = [ '/css/bootstrap.min.css', '/js/bootstrap.bundle.min.js', '/img/logo.png' ]; $sw = " const CACHE_NAME = '$cacheName'; const urlsToCache = " . json_encode($assets) . "; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } return fetch(event.request); } ) ); }); "; return $sw; } /** * Bytes formatieren */ private function formatBytes($bytes, $precision = 2) { $units = ['B', 'KB', 'MB', 'GB', 'TB']; for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, $precision) . ' ' . $units[$i]; } /** * Performance-Header setzen */ public function setPerformanceHeaders() { // Security Headers header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: SAMEORIGIN'); header('X-XSS-Protection: 1; mode=block'); // Performance Headers header('Connection: keep-alive'); header('Keep-Alive: timeout=5, max=1000'); } /** * Datenbank-Optimierung */ public function optimizeDatabase() { try { $connectionParams = [ 'dbname' => getenv('DB_DATABASE') ?: 'freeshop', 'user' => getenv('DB_USERNAME') ?: 'freeshop_user', 'password' => getenv('DB_PASSWORD') ?: 'freeshop_password', 'host' => getenv('DB_HOST') ?: 'db', 'driver' => 'pdo_mysql', 'port' => getenv('DB_PORT') ?: 3306, 'charset' => 'utf8mb4', ]; $conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams); // Tabellen optimieren $tables = ['ws_product', 'ws_category', 'ws_order', 'ws_customer']; foreach ($tables as $table) { $conn->executeStatement("OPTIMIZE TABLE $table"); } return true; } catch (Exception $e) { return false; } } }