تحسين أداء تطبيقات PHP: استراتيجيات متقدمة

PHP

تحسين أداء تطبيقات PHP: استراتيجيات متقدمة
دليل شامل لتحسين أداء تطبيقات PHP مع تقنيات الـ caching، تحسين قواعد البيانات، وأفضل الممارسات.
#PHP#Performance#Optimization#Caching#Database#Speed

تحسين أداء تطبيقات PHP: استراتيجيات متقدمة

دليل متخصص من علاء عامر - مطور ومصمم مواقع وتطبيقات احترافية

تحسين الأداء هو جانب حاسم في تطوير تطبيقات PHP الناجحة. سرعة التطبيق تؤثر مباشرة على تجربة المستخدم وترتيب محركات البحث.


1️⃣ مقاييس الأداء الأساسية

المقياسالهدف المثاليطريقة القياس
وقت الاستجابة< 200msApache Bench, Load Testing
استهلاك الذاكرة< 32MB لكل طلبmemory_get_peak_usage()
استعلامات قاعدة البيانات< 10 لكل صفحةQuery Log Analysis
Time to First Byte< 100msBrowser DevTools

2️⃣ تحسين إعدادات PHP

أهم إعدادات php.ini للأداء:

; php.ini للإنتاج
; الذاكرة
memory_limit = 512M
max_execution_time = 30
max_input_time = 60

; OPcache (أهم شيء لتحسين الأداء)
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 12
opcache.max_accelerated_files = 60000
opcache.revalidate_freq = 0
opcache.validate_timestamps = 0  ; للإنتاج فقط
opcache.save_comments = 0
opcache.fast_shutdown = 1

; تحسين Session
session.save_handler = redis
session.save_path = "tcp://localhost:6379"

; إيقاف الأخطاء في الإنتاج
display_errors = 0
error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING
log_errors = 1
error_log = /var/log/php_errors.log

; تحسين رفع الملفات
post_max_size = 50M
upload_max_filesize = 50M
max_file_uploads = 20

; تحسين الشبكة
default_socket_timeout = 5

فئة لمراقبة الأداء:

<?php
// classes/PerformanceMonitor.php

class PerformanceMonitor
{
    private static $startTime;
    private static $startMemory;
    private static $queries = [];
    private static $checkpoints = [];

    public static function start()
    {
        self::$startTime = microtime(true);
        self::$startMemory = memory_get_usage(true);
    }

    public static function checkpoint($name)
    {
        self::$checkpoints[$name] = [
            'time' => microtime(true) - self::$startTime,
            'memory' => memory_get_usage(true) - self::$startMemory,
            'peak_memory' => memory_get_peak_usage(true)
        ];
    }

    public static function logQuery($sql, $executionTime)
    {
        self::$queries[] = [
            'sql' => $sql,
            'time' => $executionTime,
            'timestamp' => microtime(true)
        ];
    }

    public static function getReport()
    {
        $totalTime = microtime(true) - self::$startTime;
        $totalMemory = memory_get_usage(true) - self::$startMemory;
        $peakMemory = memory_get_peak_usage(true);

        return [
            'total_execution_time' => round($totalTime * 1000, 2) . 'ms',
            'memory_usage' => self::formatBytes($totalMemory),
            'peak_memory' => self::formatBytes($peakMemory),
            'query_count' => count(self::$queries),
            'queries' => self::$queries,
            'checkpoints' => self::$checkpoints,
            'opcache_status' => function_exists('opcache_get_status') ? opcache_get_status() : null
        ];
    }

    private static function formatBytes($bytes)
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        return round($bytes / (1024 ** $pow), 2) . ' ' . $units[$pow];
    }

    public static function profileFunction($callback, $name = 'anonymous')
    {
        $startTime = microtime(true);
        $startMemory = memory_get_usage(true);

        $result = call_user_func($callback);

        $endTime = microtime(true);
        $endMemory = memory_get_usage(true);

        error_log(sprintf(
            "Profile [%s]: Time: %.2fms, Memory: %s",
            $name,
            ($endTime - $startTime) * 1000,
            self::formatBytes($endMemory - $startMemory)
        ));

        return $result;
    }
}

// الاستخدام
PerformanceMonitor::start();

// في بداية العملية
PerformanceMonitor::checkpoint('after_bootstrap');

// عند الانتهاء
PerformanceMonitor::checkpoint('before_response');
$report = PerformanceMonitor::getReport();

3️⃣ تحسين قاعدة البيانات

Connection Pool للـ Database:

<?php
// classes/DatabasePool.php

class DatabasePool
{
    private static $pools = [];
    private static $config = [];
    private static $maxConnections = 10;
    private static $activeConnections = 0;

    public static function configure($config)
    {
        self::$config = $config;
        self::$maxConnections = $config['max_connections'] ?? 10;
    }

    public static function getConnection($poolName = 'default')
    {
        if (!isset(self::$pools[$poolName])) {
            self::$pools[$poolName] = [];
        }

        // إعادة استخدام اتصال موجود
        if (!empty(self::$pools[$poolName])) {
            return array_pop(self::$pools[$poolName]);
        }

        // إنشاء اتصال جديد إذا لم نصل للحد الأقصى
        if (self::$activeConnections < self::$maxConnections) {
            self::$activeConnections++;
            return self::createConnection();
        }

        throw new Exception('Maximum database connections reached');
    }

    public static function returnConnection($connection, $poolName = 'default')
    {
        if (!isset(self::$pools[$poolName])) {
            self::$pools[$poolName] = [];
        }

        // إعادة الاتصال للمجموعة
        self::$pools[$poolName][] = $connection;
    }

    private static function createConnection()
    {
        $dsn = sprintf(
            '%s:host=%s;port=%s;dbname=%s;charset=utf8mb4',
            self::$config['driver'],
            self::$config['host'],
            self::$config['port'],
            self::$config['database']
        );

        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'"
        ];

        return new PDO($dsn, self::$config['username'], self::$config['password'], $options);
    }
}

Query Builder محسن للأداء:

<?php
// classes/OptimizedQueryBuilder.php

class OptimizedQueryBuilder
{
    private $pdo;
    private $table;
    private $select = ['*'];
    private $where = [];
    private $joins = [];
    private $orderBy = [];
    private $limit;
    private $offset;
    private $cache = [];

    public function __construct($pdo, $table)
    {
        $this->pdo = $pdo;
        $this->table = $table;
    }

    public function select($columns)
    {
        $this->select = is_array($columns) ? $columns : func_get_args();
        return $this;
    }

    public function where($column, $operator, $value = null)
    {
        if ($value === null) {
            $value = $operator;
            $operator = '=';
        }

        $this->where[] = [$column, $operator, $value];
        return $this;
    }

    public function join($table, $first, $operator, $second)
    {
        $this->joins[] = "INNER JOIN {$table} ON {$first} {$operator} {$second}";
        return $this;
    }

    public function orderBy($column, $direction = 'ASC')
    {
        $this->orderBy[] = "{$column} {$direction}";
        return $this;
    }

    public function limit($limit)
    {
        $this->limit = $limit;
        return $this;
    }

    public function offset($offset)
    {
        $this->offset = $offset;
        return $this;
    }

    public function get($useCache = true)
    {
        $sql = $this->buildSelectQuery();
        $cacheKey = md5($sql . serialize($this->getBindValues()));

        if ($useCache && isset($this->cache[$cacheKey])) {
            return $this->cache[$cacheKey];
        }

        $startTime = microtime(true);
        $stmt = $this->pdo->prepare($sql);

        $bindValues = $this->getBindValues();
        foreach ($bindValues as $key => $value) {
            $stmt->bindValue($key + 1, $value);
        }

        $stmt->execute();
        $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $executionTime = microtime(true) - $startTime;
        PerformanceMonitor::logQuery($sql, $executionTime);

        if ($useCache) {
            $this->cache[$cacheKey] = $result;
        }

        return $result;
    }

    public function first($useCache = true)
    {
        $this->limit(1);
        $results = $this->get($useCache);
        return !empty($results) ? $results[0] : null;
    }

    private function buildSelectQuery()
    {
        $sql = 'SELECT ' . implode(', ', $this->select) . ' FROM ' . $this->table;

        if (!empty($this->joins)) {
            $sql .= ' ' . implode(' ', $this->joins);
        }

        if (!empty($this->where)) {
            $conditions = [];
            foreach ($this->where as $where) {
                $conditions[] = "{$where[0]} {$where[1]} ?";
            }
            $sql .= ' WHERE ' . implode(' AND ', $conditions);
        }

        if (!empty($this->orderBy)) {
            $sql .= ' ORDER BY ' . implode(', ', $this->orderBy);
        }

        if ($this->limit) {
            $sql .= ' LIMIT ' . $this->limit;
        }

        if ($this->offset) {
            $sql .= ' OFFSET ' . $this->offset;
        }

        return $sql;
    }

    private function getBindValues()
    {
        $values = [];
        foreach ($this->where as $where) {
            $values[] = $where[2];
        }
        return $values;
    }
}

4️⃣ نظام Caching متقدم

Redis Cache Implementation:

<?php
// classes/CacheManager.php

class CacheManager
{
    private $redis;
    private $defaultTtl = 3600; // ساعة واحدة
    private $keyPrefix = 'app:';

    public function __construct($redisHost = 'localhost', $redisPort = 6379)
    {
        $this->redis = new Redis();
        $this->redis->connect($redisHost, $redisPort);
    }

    public function get($key, $default = null)
    {
        $value = $this->redis->get($this->keyPrefix . $key);

        if ($value === false) {
            return $default;
        }

        return json_decode($value, true);
    }

    public function set($key, $value, $ttl = null)
    {
        $ttl = $ttl ?? $this->defaultTtl;
        $encodedValue = json_encode($value);

        return $this->redis->setex($this->keyPrefix . $key, $ttl, $encodedValue);
    }

    public function remember($key, $callback, $ttl = null)
    {
        $value = $this->get($key);

        if ($value !== null) {
            return $value;
        }

        $value = call_user_func($callback);
        $this->set($key, $value, $ttl);

        return $value;
    }

    public function forget($key)
    {
        return $this->redis->del($this->keyPrefix . $key);
    }

    public function flush()
    {
        $keys = $this->redis->keys($this->keyPrefix . '*');
        if (!empty($keys)) {
            return $this->redis->del($keys);
        }
        return true;
    }

    // Cache Tags للتجميع
    public function tags($tags)
    {
        return new TaggedCache($this, (array)$tags);
    }

    // Cache بناءً على العمليات
    public function cacheQuery($sql, $params, $callback, $ttl = 600)
    {
        $key = 'query:' . md5($sql . serialize($params));

        return $this->remember($key, $callback, $ttl);
    }

    // Cache للصفحات الكاملة
    public function cachePage($uri, $callback, $ttl = 1800)
    {
        $key = 'page:' . md5($uri);

        return $this->remember($key, $callback, $ttl);
    }
}

class TaggedCache
{
    private $cache;
    private $tags;

    public function __construct(CacheManager $cache, array $tags)
    {
        $this->cache = $cache;
        $this->tags = $tags;
    }

    public function get($key, $default = null)
    {
        return $this->cache->get($this->taggedKey($key), $default);
    }

    public function set($key, $value, $ttl = null)
    {
        $taggedKey = $this->taggedKey($key);

        // إضافة المفتاح لكل tag
        foreach ($this->tags as $tag) {
            $this->cache->redis->sadd("tag:{$tag}", $taggedKey);
        }

        return $this->cache->set($taggedKey, $value, $ttl);
    }

    public function flush()
    {
        foreach ($this->tags as $tag) {
            $keys = $this->cache->redis->smembers("tag:{$tag}");
            if (!empty($keys)) {
                $this->cache->redis->del($keys);
                $this->cache->redis->del("tag:{$tag}");
            }
        }
    }

    private function taggedKey($key)
    {
        return 'tagged:' . implode(':', $this->tags) . ':' . $key;
    }
}

نظام File Cache للبيانات الثقيلة:

<?php
// classes/FileCache.php

class FileCache
{
    private $cacheDir;
    private $defaultTtl = 3600;

    public function __construct($cacheDir = 'cache')
    {
        $this->cacheDir = rtrim($cacheDir, '/');

        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }

    public function get($key, $default = null)
    {
        $filename = $this->getFilename($key);

        if (!file_exists($filename)) {
            return $default;
        }

        $data = file_get_contents($filename);
        $cached = unserialize($data);

        // التحقق من انتهاء الصلاحية
        if ($cached['expires'] > 0 && $cached['expires'] < time()) {
            unlink($filename);
            return $default;
        }

        return $cached['value'];
    }

    public function set($key, $value, $ttl = null)
    {
        $ttl = $ttl ?? $this->defaultTtl;
        $expires = $ttl > 0 ? time() + $ttl : 0;

        $cached = [
            'value' => $value,
            'expires' => $expires,
            'created' => time()
        ];

        $filename = $this->getFilename($key);
        $this->ensureDirectoryExists(dirname($filename));

        return file_put_contents($filename, serialize($cached), LOCK_EX) !== false;
    }

    public function delete($key)
    {
        $filename = $this->getFilename($key);

        if (file_exists($filename)) {
            return unlink($filename);
        }

        return true;
    }

    public function clear()
    {
        $this->deleteDirectory($this->cacheDir);
        mkdir($this->cacheDir, 0755, true);
        return true;
    }

    // Cache للصور المصغرة
    public function cacheImage($originalPath, $width, $height, $quality = 80)
    {
        $key = "image:" . md5($originalPath . $width . $height . $quality);
        $filename = $this->getFilename($key, 'jpg');

        if (file_exists($filename) && filemtime($filename) > filemtime($originalPath)) {
            return $filename;
        }

        // إنشاء صورة مصغرة
        $this->createThumbnail($originalPath, $filename, $width, $height, $quality);

        return $filename;
    }

    private function getFilename($key, $extension = 'cache')
    {
        $hash = md5($key);
        $dir1 = substr($hash, 0, 2);
        $dir2 = substr($hash, 2, 2);

        return "{$this->cacheDir}/{$dir1}/{$dir2}/{$hash}.{$extension}";
    }

    private function ensureDirectoryExists($dir)
    {
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
    }

    private function deleteDirectory($dir)
    {
        if (!is_dir($dir)) {
            return true;
        }

        $files = array_diff(scandir($dir), ['.', '..']);

        foreach ($files as $file) {
            $path = $dir . '/' . $file;
            is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
        }

        return rmdir($dir);
    }

    private function createThumbnail($source, $destination, $width, $height, $quality)
    {
        $imageInfo = getimagesize($source);
        $srcWidth = $imageInfo[0];
        $srcHeight = $imageInfo[1];
        $mimeType = $imageInfo['mime'];

        // إنشاء resource حسب نوع الصورة
        switch ($mimeType) {
            case 'image/jpeg':
                $srcImage = imagecreatefromjpeg($source);
                break;
            case 'image/png':
                $srcImage = imagecreatefrompng($source);
                break;
            case 'image/gif':
                $srcImage = imagecreatefromgif($source);
                break;
            default:
                return false;
        }

        // حساب الأبعاد الجديدة مع الحفاظ على النسب
        $aspectRatio = $srcWidth / $srcHeight;

        if ($width / $height > $aspectRatio) {
            $width = $height * $aspectRatio;
        } else {
            $height = $width / $aspectRatio;
        }

        // إنشاء الصورة الجديدة
        $destImage = imagecreatetruecolor($width, $height);
        imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight);

        // حفظ الصورة
        $this->ensureDirectoryExists(dirname($destination));
        imagejpeg($destImage, $destination, $quality);

        // تحرير الذاكرة
        imagedestroy($srcImage);
        imagedestroy($destImage);

        return true;
    }
}

5️⃣ تحسين الـ Frontend Assets

Asset Optimizer:

<?php
// classes/AssetOptimizer.php

class AssetOptimizer
{
    private $publicPath;
    private $cacheDir;

    public function __construct($publicPath = 'public', $cacheDir = 'cache/assets')
    {
        $this->publicPath = rtrim($publicPath, '/');
        $this->cacheDir = rtrim($cacheDir, '/');

        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }

    public function minifyCSS($files)
    {
        $hash = md5(serialize($files));
        $outputFile = "{$this->cacheDir}/css_{$hash}.css";

        if (file_exists($outputFile) && $this->isFresh($files, $outputFile)) {
            return $this->getAssetUrl($outputFile);
        }

        $minifiedCSS = '';

        foreach ($files as $file) {
            $filePath = $this->publicPath . '/' . ltrim($file, '/');

            if (file_exists($filePath)) {
                $css = file_get_contents($filePath);
                $minifiedCSS .= $this->compressCSS($css);
            }
        }

        file_put_contents($outputFile, $minifiedCSS);

        return $this->getAssetUrl($outputFile);
    }

    public function minifyJS($files)
    {
        $hash = md5(serialize($files));
        $outputFile = "{$this->cacheDir}/js_{$hash}.js";

        if (file_exists($outputFile) && $this->isFresh($files, $outputFile)) {
            return $this->getAssetUrl($outputFile);
        }

        $minifiedJS = '';

        foreach ($files as $file) {
            $filePath = $this->publicPath . '/' . ltrim($file, '/');

            if (file_exists($filePath)) {
                $js = file_get_contents($filePath);
                $minifiedJS .= $this->compressJS($js) . ';';
            }
        }

        file_put_contents($outputFile, $minifiedJS);

        return $this->getAssetUrl($outputFile);
    }

    private function compressCSS($css)
    {
        // إزالة التعليقات
        $css = preg_replace('/\/\*.*?\*\//s', '', $css);

        // إزالة المسافات الزائدة
        $css = preg_replace('/\s+/', ' ', $css);

        // إزالة المسافات حول الأقواس والنقطتين
        $css = str_replace([' {', '{ ', ' }', ': ', '; ', ', '], ['{', '{', '}', ':', ';', ','], $css);

        return trim($css);
    }

    private function compressJS($js)
    {
        // إزالة التعليقات
        $js = preg_replace('/\/\*.*?\*\//s', '', $js);
        $js = preg_replace('/\/\/.*$/m', '', $js);

        // إزالة المسافات الزائدة
        $js = preg_replace('/\s+/', ' ', $js);

        return trim($js);
    }

    private function isFresh($sourceFiles, $outputFile)
    {
        $outputTime = filemtime($outputFile);

        foreach ($sourceFiles as $file) {
            $filePath = $this->publicPath . '/' . ltrim($file, '/');

            if (file_exists($filePath) && filemtime($filePath) > $outputTime) {
                return false;
            }
        }

        return true;
    }

    private function getAssetUrl($filePath)
    {
        return '/' . ltrim(str_replace($this->publicPath, '', $filePath), '/');
    }
}

6️⃣ HTTP Response Optimization

<?php
// classes/ResponseOptimizer.php

class ResponseOptimizer
{
    public static function enableGzipCompression()
    {
        if (!headers_sent() && extension_loaded('zlib') && !ob_get_length()) {
            ini_set('zlib.output_compression', 'On');
            ini_set('zlib.output_compression_level', 6);
        }
    }

    public static function setCacheHeaders($maxAge = 3600, $etag = null)
    {
        if (headers_sent()) return;

        // Cache Control
        header("Cache-Control: public, max-age={$maxAge}");
        header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $maxAge) . ' GMT');

        // ETag
        if ($etag) {
            header("ETag: \"{$etag}\"");

            if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
                $_SERVER['HTTP_IF_NONE_MATCH'] === "\"{$etag}\"") {
                http_response_code(304);
                exit;
            }
        }

        // Last Modified
        $lastModified = gmdate('D, d M Y H:i:s', time()) . ' GMT';
        header("Last-Modified: {$lastModified}");
    }

    public static function optimizeImages($imagePath, $quality = 80)
    {
        if (!file_exists($imagePath)) {
            return false;
        }

        $imageInfo = getimagesize($imagePath);
        $mimeType = $imageInfo['mime'];

        // تحقق من دعم المتصفح لـ WebP
        $supportsWebP = strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'image/webp') !== false;

        if ($supportsWebP && $mimeType !== 'image/webp') {
            $webpPath = preg_replace('/\.[^.]+$/', '.webp', $imagePath);

            if (!file_exists($webpPath) || filemtime($imagePath) > filemtime($webpPath)) {
                self::convertToWebP($imagePath, $webpPath, $quality);
            }

            if (file_exists($webpPath)) {
                return $webpPath;
            }
        }

        return $imagePath;
    }

    private static function convertToWebP($source, $destination, $quality)
    {
        $imageInfo = getimagesize($source);
        $mimeType = $imageInfo['mime'];

        switch ($mimeType) {
            case 'image/jpeg':
                $image = imagecreatefromjpeg($source);
                break;
            case 'image/png':
                $image = imagecreatefrompng($source);
                break;
            default:
                return false;
        }

        if ($image) {
            imagewebp($image, $destination, $quality);
            imagedestroy($image);
            return true;
        }

        return false;
    }

    public static function sendOptimizedResponse($content, $contentType = 'text/html')
    {
        self::enableGzipCompression();

        $etag = md5($content);
        self::setCacheHeaders(3600, $etag);

        header("Content-Type: {$contentType}; charset=utf-8");
        header('Content-Length: ' . strlen($content));

        echo $content;
    }
}

💡 نصائح سريعة لتحسين الأداء

  1. استخدم OPcache دائماً - يحسن الأداء بنسبة 50-80%
  2. قم بالـ Profiling المنتظم - استخدم Xdebug أو Blackfire
  3. حسن استعلامات قاعدة البيانات - استخدم EXPLAIN لفهم الاستعلامات
  4. طبق Lazy Loading - لا تحمل البيانات إلا عند الحاجة
  5. استخدم CDN للملفات الثابتة
  6. فعل HTTP/2 لتحسين تحميل الأصول المتعددة

الخطوة التالية

تعلم Load Balancing و Microservices Architecture للتطبيقات الكبيرة.

📩 تحتاج مساعدة في تحسين أداء تطبيقك؟

aboutservicesprojectsBlogscontact