Newwebshop/app/Core/Performance.php

462 lines
13 KiB
PHP

<?php
/**
* Copyright seit 2024 Webshop System
*
* Performance-Core-Klasse für das Webshop-System
*
* @author Webshop System
* @license GPL v3
*/
namespace App\Core;
class Performance
{
private $startTime;
private $memoryStart;
private $queries = [];
private $cache;
public function __construct()
{
$this->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('/<img([^>]*?)src=(["\'])([^"\']+)\2([^>]*?)>/i',
'<img$1src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-src="$3"$4>', $html);
// Lazy Loading JavaScript hinzufügen
$lazyScript = '
<script>
document.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].slice.call(document.querySelectorAll("img[data-src]"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
</script>';
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;
}
}
}