تحسين أداء تطبيقات PHP: استراتيجيات متقدمة
دليل متخصص من علاء عامر - مطور ومصمم مواقع وتطبيقات احترافية
تحسين الأداء هو جانب حاسم في تطوير تطبيقات PHP الناجحة. سرعة التطبيق تؤثر مباشرة على تجربة المستخدم وترتيب محركات البحث.
1️⃣ مقاييس الأداء الأساسية
| المقياس | الهدف المثالي | طريقة القياس |
|---|---|---|
| وقت الاستجابة | < 200ms | Apache Bench, Load Testing |
| استهلاك الذاكرة | < 32MB لكل طلب | memory_get_peak_usage() |
| استعلامات قاعدة البيانات | < 10 لكل صفحة | Query Log Analysis |
| Time to First Byte | < 100ms | Browser 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;
}
}
💡 نصائح سريعة لتحسين الأداء
- استخدم OPcache دائماً - يحسن الأداء بنسبة 50-80%
- قم بالـ Profiling المنتظم - استخدم Xdebug أو Blackfire
- حسن استعلامات قاعدة البيانات - استخدم EXPLAIN لفهم الاستعلامات
- طبق Lazy Loading - لا تحمل البيانات إلا عند الحاجة
- استخدم CDN للملفات الثابتة
- فعل HTTP/2 لتحسين تحميل الأصول المتعددة
الخطوة التالية
تعلم Load Balancing و Microservices Architecture للتطبيقات الكبيرة.
