أساسيات PHP

إعدادات PHP وأفضل الممارسات

13 دقيقة الدرس 29 من 45

إعدادات PHP وأفضل الممارسات

الإعداد الصحيح لـ PHP واتباع أفضل الممارسات أمر بالغ الأهمية لبناء تطبيقات آمنة وعالية الأداء وقابلة للصيانة. يغطي هذا الدرس إعدادات التكوين الأساسية ومعايير البرمجة.

فهم php.ini

ملف php.ini يحتوي على توجيهات التكوين التي تتحكم في سلوك PHP:

; موقع php.ini: ; - Linux: /etc/php/8.x/php.ini ; - Windows: C:\php\php.ini ; - macOS: /usr/local/etc/php/8.x/php.ini ; العثور على موقع php.ini الخاص بك: php --ini ; عرض جميع إعدادات PHP: php -i | grep "Configuration File"

إعدادات php.ini الأساسية

; === معالجة الأخطاء === ; بيئة التطوير display_errors = On display_startup_errors = On error_reporting = E_ALL ; بيئة الإنتاج display_errors = Off display_startup_errors = Off error_reporting = E_ALL log_errors = On error_log = /var/log/php/error.log ; === إعدادات الأمان === expose_php = Off ; إخفاء إصدار PHP allow_url_fopen = Off ; تعطيل الوصول للملفات البعيدة allow_url_include = Off ; تعطيل تضمين الملفات البعيدة disable_functions = exec,passthru,shell_exec,system,proc_open,popen open_basedir = /var/www:/tmp ; تقييد الوصول للملفات ; === إعدادات الأداء === max_execution_time = 30 ; الحد الأقصى لوقت التنفيذ (بالثواني) max_input_time = 60 ; الحد الأقصى لوقت تحليل المدخلات memory_limit = 128M ; الحد الأقصى للذاكرة لكل سكريبت post_max_size = 8M ; الحد الأقصى لحجم بيانات POST upload_max_filesize = 2M ; الحد الأقصى لحجم الملف المرفوع ; === إعدادات الجلسة === session.cookie_httponly = 1 ; منع وصول JavaScript للكوكيز session.cookie_secure = 1 ; إرسال الكوكيز فقط عبر HTTPS session.use_strict_mode = 1 ; رفض معرفات الجلسة غير المهيأة session.gc_maxlifetime = 1440 ; تنظيف الجلسة (24 دقيقة) ; === OPcache (الإنتاج) === opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 8 opcache.max_accelerated_files = 10000 opcache.validate_timestamps = 0 ; تعطيل للإنتاج
تحذير: لا تضع أبداً display_errors = On في الإنتاج! يمكن أن يكشف معلومات حساسة للمهاجمين.

التكوين أثناء التشغيل بـ ini_set()

يمكنك تغيير بعض الإعدادات أثناء التشغيل باستخدام ini_set():

<?php // تغيير الإعدادات أثناء التشغيل ini_set('display_errors', 1); ini_set('memory_limit', '256M'); ini_set('max_execution_time', 300); ini_set('error_log', '/custom/path/errors.log'); // الحصول على قيمة الإعداد الحالي echo ini_get('memory_limit'); // 256M echo ini_get('max_execution_time'); // 300 // التحقق مما إذا كان يمكن تغيير التوجيه $directives = [ 'display_errors', 'memory_limit', 'max_execution_time' ]; foreach ($directives as $directive) { $mode = ini_get_all($directive)[$directive]['access']; echo "$directive يمكن تغييره: "; echo ($mode & INI_USER) ? 'نعم' : 'لا'; echo "\n"; } // التكوين حسب البيئة if ($_SERVER['SERVER_NAME'] === 'localhost') { // التطوير ini_set('display_errors', 1); error_reporting(E_ALL); } else { // الإنتاج ini_set('display_errors', 0); ini_set('log_errors', 1); } ?>
ملاحظة: ليس كل توجيهات php.ini يمكن تغييرها بـ ini_set(). بعضها يتطلب تغييرات في php.ini أو .htaccess.

أفضل ممارسات الأمان

<?php // 1. التحقق من المدخلات function validateEmail($email) { return filter_var($email, FILTER_VALIDATE_EMAIL) !== false; } function validateAge($age) { return filter_var($age, FILTER_VALIDATE_INT, [ 'options' => ['min_range' => 0, 'max_range' => 150] ]) !== false; } // 2. تجنب المخرجات $userInput = "<script>alert('XSS')</script>"; echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); // 3. منع حقن SQL // ❌ لا تفعل هذا أبداً: $sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'"; // ✅ استخدم دائماً الاستعلامات المحضرة: $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$_POST['email']]); // 4. أمان كلمة المرور $password = $_POST['password']; // تجزئة كلمة المرور $hash = password_hash($password, PASSWORD_DEFAULT); // التحقق من كلمة المرور if (password_verify($password, $hash)) { echo "كلمة المرور صحيحة"; } // 5. الحماية من CSRF session_start(); // توليد الرمز if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // في النموذج: // <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>"> // التحقق من الرمز if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) { die('فشل التحقق من رمز CSRF'); } // 6. إدارة جلسة آمنة ini_set('session.cookie_httponly', 1); ini_set('session.cookie_secure', 1); // فقط لـ HTTPS ini_set('session.use_strict_mode', 1); session_start(); // تجديد معرّف الجلسة بعد تسجيل الدخول session_regenerate_id(true); ?>

أفضل ممارسات تنظيم الكود

<?php // 1. استخدم أسماء متغيرات واضحة // ❌ سيء $d = 86400; $arr = [1, 2, 3]; // ✅ جيد $secondsPerDay = 86400; $userIds = [1, 2, 3]; // 2. استخدم الثوابت للقيم الثابتة define('TAX_RATE', 0.15); define('MAX_LOGIN_ATTEMPTS', 5); // أو استخدم const (أفضل للفئات) const DATABASE_HOST = 'localhost'; const DATABASE_NAME = 'myapp'; // 3. فئة واحدة لكل ملف // الملف: User.php class User { private $id; private $email; public function __construct($id, $email) { $this->id = $id; $this->email = $email; } } // 4. استخدام مساحات الأسماء بشكل صحيح namespace App\Models; class Product { // تنفيذ الفئة } // 5. استخدم تصريحات النوع (PHP 7+) function addNumbers(int $a, int $b): int { return $a + $b; } function getUser(int $id): ?User { // يعيد User أو null return $user ?? null; } // 6. مبدأ DRY (لا تكرر نفسك) // ❌ سيء - كود متكرر if ($user['role'] === 'admin') { // كود المسؤول } if ($user['role'] === 'admin') { // نفس الفحص مرة أخرى } // ✅ جيد - مسؤولية واحدة function isAdmin($user): bool { return $user['role'] === 'admin'; } if (isAdmin($user)) { // كود المسؤول } ?>

أفضل ممارسات معالجة الأخطاء

<?php // 1. استخدم الاستثناءات لمعالجة الأخطاء class ValidationException extends Exception {} function registerUser($email, $password) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new ValidationException("عنوان بريد إلكتروني غير صحيح"); } if (strlen($password) < 8) { throw new ValidationException("كلمة المرور يجب أن تكون 8 أحرف على الأقل"); } // منطق التسجيل } try { registerUser($_POST['email'], $_POST['password']); } catch (ValidationException $e) { echo "خطأ في التحقق: " . $e->getMessage(); } catch (Exception $e) { error_log($e->getMessage()); echo "حدث خطأ. يرجى المحاولة مرة أخرى."; } // 2. الفشل السريع function processPayment($amount) { if ($amount <= 0) { throw new InvalidArgumentException("المبلغ يجب أن يكون موجباً"); } if (!isUserAuthenticated()) { throw new Exception("يجب أن يكون المستخدم مصادق عليه"); } // معالجة الدفع } // 3. تسجيل الأخطاء بشكل صحيح function logError($message, $context = []) { $timestamp = date('Y-m-d H:i:s'); $contextJson = json_encode($context); error_log("[$timestamp] $message | السياق: $contextJson"); } try { $result = riskyOperation(); } catch (Exception $e) { logError("فشلت العملية", [ 'exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); } ?>

أفضل ممارسات الأداء

<?php // 1. استخدم OPcache في الإنتاج // التفعيل في php.ini: // opcache.enable=1 // opcache.memory_consumption=128 // 2. تجنب استدعاءات الدوال المتكررة // ❌ سيء for ($i = 0; $i < count($items); $i++) { echo $items[$i]; } // ✅ جيد $count = count($items); for ($i = 0; $i < $count; $i++) { echo $items[$i]; } // 3. استخدم الدوال المدمجة // ❌ سيء - تنفيذ مخصص function arraySum($arr) { $sum = 0; foreach ($arr as $val) { $sum += $val; } return $sum; } // ✅ جيد - دالة مدمجة $sum = array_sum($arr); // 4. تحسين استعلامات قاعدة البيانات // ❌ سيء - مشكلة N+1 $users = $db->query("SELECT * FROM users"); foreach ($users as $user) { $orders = $db->query("SELECT * FROM orders WHERE user_id = " . $user['id']); } // ✅ جيد - استعلام واحد مع JOIN $users = $db->query(" SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id "); // 5. استخدم التحميل الكسول class User { private $orders = null; public function getOrders() { if ($this->orders === null) { $this->orders = $this->loadOrders(); } return $this->orders; } } // 6. قم بتخزين العمليات المكلفة مؤقتاً function getPopularProducts() { $cacheKey = 'popular_products'; $cached = cache_get($cacheKey); if ($cached !== false) { return $cached; } $products = expensiveDatabaseQuery(); cache_set($cacheKey, $products, 3600); // التخزين لمدة ساعة return $products; } ?>
نصيحة: فعّل OPcache في الإنتاج لتخزين كود PHP المجمّع وتحسين الأداء بشكل كبير.

إدارة التبعيات مع Composer

# تثبيت Composer curl -sS https://getcomposer.org/installer | php # إنشاء composer.json { "require": { "monolog/monolog": "^2.0", "guzzlehttp/guzzle": "^7.0" }, "autoload": { "psr-4": { "App\\": "src/" } } } # تثبيت التبعيات composer install # تحديث التبعيات composer update # التحميل التلقائي للفئات require 'vendor/autoload.php';
<?php // استخدام التحميل التلقائي لـ Composer require __DIR__ . '/vendor/autoload.php'; // استخدام الحزم المثبتة use Monolog\Logger; use Monolog\Handler\StreamHandler; $log = new Logger('app'); $log->pushHandler(new StreamHandler('app.log', Logger::WARNING)); $log->warning('هذا تحذير'); // التحميل التلقائي لفئاتك الخاصة use App\Models\User; use App\Controllers\UserController; $user = new User(); $controller = new UserController(); ?>

تكوين البيئة

<?php // ملف .env (لا تضعه أبداً في git!) // DB_HOST=localhost // DB_NAME=myapp // DB_USER=root // DB_PASS=secret // APP_ENV=production // APP_DEBUG=false // تحميل متغيرات البيئة function loadEnv($path) { if (!file_exists($path)) { throw new Exception("ملف .env غير موجود"); } $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { if (strpos(trim($line), '#') === 0) { continue; // تخطي التعليقات } list($name, $value) = explode('=', $line, 2); $_ENV[trim($name)] = trim($value); putenv(trim($name) . '=' . trim($value)); } } loadEnv(__DIR__ . '/.env'); // الوصول لمتغيرات البيئة $dbHost = $_ENV['DB_HOST'] ?? 'localhost'; $dbName = getenv('DB_NAME'); $isDebug = $_ENV['APP_DEBUG'] === 'true'; // التكوين حسب البيئة $config = [ 'database' => [ 'host' => $_ENV['DB_HOST'], 'name' => $_ENV['DB_NAME'], 'user' => $_ENV['DB_USER'], 'pass' => $_ENV['DB_PASS'] ], 'app' => [ 'env' => $_ENV['APP_ENV'], 'debug' => $_ENV['APP_DEBUG'] === 'true' ] ]; ?>

توثيق الكود

<?php /** * حساب السعر الإجمالي للعناصر في السلة * * تطبق هذه الدالة الخصومات والضريبة لحساب * السعر الإجمالي النهائي لجميع العناصر في سلة التسوق. * * @param array $items مصفوفة عناصر السلة مع السعر والكمية * @param float $taxRate معدل الضريبة كعدد عشري (مثلاً، 0.15 لـ 15%) * @param float $discount مبلغ الخصم للطرح من الإجمالي * @return float السعر الإجمالي النهائي شاملاً الضريبة والخصم * @throws InvalidArgumentException إذا كان معدل الضريبة سالباً */ function calculateTotal(array $items, float $taxRate = 0.15, float $discount = 0): float { if ($taxRate < 0) { throw new InvalidArgumentException("معدل الضريبة لا يمكن أن يكون سالباً"); } $subtotal = 0; foreach ($items as $item) { $subtotal += $item['price'] * $item['quantity']; } $total = $subtotal - $discount; $total += $total * $taxRate; return round($total, 2); } /** * نموذج المستخدم يمثل مستخدماً مسجلاً * * @property int $id معرّف المستخدم * @property string $email عنوان البريد الإلكتروني للمستخدم * @property string $name الاسم الكامل للمستخدم */ class User { /** @var int معرّف المستخدم */ private $id; /** @var string بريد المستخدم الإلكتروني */ private $email; /** * إنشاء نسخة مستخدم جديدة * * @param int $id معرّف المستخدم * @param string $email بريد المستخدم الإلكتروني */ public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } } ?>

أفضل ممارسات الاختبار

<?php // تثبيت PHPUnit // composer require --dev phpunit/phpunit // ملف الاختبار: tests/CalculatorTest.php use PHPUnit\Framework\TestCase; class CalculatorTest extends TestCase { public function testAddition() { $calculator = new Calculator(); $result = $calculator->add(2, 3); $this->assertEquals(5, $result); } public function testDivisionByZero() { $this->expectException(DivisionByZeroError::class); $calculator = new Calculator(); $calculator->divide(10, 0); } public function testMultiplication() { $calculator = new Calculator(); $this->assertEquals(6, $calculator->multiply(2, 3)); $this->assertEquals(0, $calculator->multiply(5, 0)); $this->assertEquals(-10, $calculator->multiply(5, -2)); } } // تشغيل الاختبارات // vendor/bin/phpunit tests ?>

معايير البرمجة (PSR-12)

<?php // معيار البرمجة PSR-12 // 1. وسم فتح PHP ومساحة الأسماء <?php namespace App\Controllers; // 2. تصريحات الاستخدام use App\Models\User; use App\Services\EmailService; // 3. تصريح الفئة class UserController { // 4. الخصائص private $emailService; protected $users = []; // 5. المُنشئ public function __construct(EmailService $emailService) { $this->emailService = $emailService; } // 6. الطرق public function createUser(string $email, string $name): User { // 7. هياكل التحكم if (empty($email)) { throw new InvalidArgumentException('البريد الإلكتروني مطلوب'); } // 8. المسافة البادئة: 4 مسافات $user = new User(); $user->setEmail($email); $user->setName($name); // 9. تسلسل الطرق $user->setEmail($email) ->setName($name) ->setStatus('active'); return $user; } // 10. تنسيق المصفوفة private function getConfig(): array { return [ 'smtp_host' => 'localhost', 'smtp_port' => 587, 'from_email' => 'noreply@example.com', ]; } } ?>
تمرين:
  1. أنشئ فئة تكوين تحمّل الإعدادات من ملف .env
  2. نفّذ نظام تسجيل دخول آمن مع تجزئة كلمات المرور وحماية CSRF
  3. اكتب فئة مُتحقق قابلة لإعادة الاستخدام مع طرق للتحققات الشائعة
  4. أنشئ فئة مسجل بسيطة تكتب في ملفات مختلفة بناءً على مستوى السجل
  5. اصنع فئة غلاف لقاعدة البيانات باستخدام PDO مع الاستعلامات المحضرة

قائمة فحص نشر الإنتاج

قبل النشر في الإنتاج:
  • ✓ ضع display_errors = Off في php.ini
  • ✓ فعّل تسجيل الأخطاء: log_errors = On
  • ✓ ضع أذونات الملفات المناسبة (644 للملفات، 755 للمجلدات)
  • ✓ احذف أدوات التطوير وكود التصحيح
  • ✓ فعّل OPcache لأداء أفضل
  • ✓ استخدم متغيرات البيئة للبيانات الحساسة
  • ✓ نفّذ حماية CSRF و XSS
  • ✓ استخدم HTTPS (شهادات SSL/TLS)
  • ✓ ضع معاملات كوكيز جلسة آمنة
  • ✓ حافظ على تحديث PHP والتبعيات
  • ✓ نفّذ تحديد المعدل لواجهات برمجة التطبيقات
  • ✓ أعد النسخ الاحتياطي التلقائي

الأخطاء الشائعة التي يجب تجنبها

<?php // ❌ 1. عرض الأخطاء في الإنتاج ini_set('display_errors', 1); // ❌ 2. عدم استخدام الاستعلامات المحضرة $sql = "SELECT * FROM users WHERE id = " . $_GET['id']; // ❌ 3. تخزين كلمات المرور بنص عادي $password = $_POST['password']; $sql = "INSERT INTO users (password) VALUES ('$password')"; // ❌ 4. عدم التحقق من مدخلات المستخدم $email = $_POST['email']; sendEmail($email); // ❌ 5. استخدام دوال مهجورة mysql_connect(); // تم إزالتها في PHP 7 ereg(); // تم إزالتها في PHP 7 // ✅ النهج الصحيح ini_set('display_errors', 0); ini_set('log_errors', 1); $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_GET['id']]); $hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT); if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { sendEmail($_POST['email']); } mysqli_connect(); // استخدم mysqli أو PDO preg_match(); // استخدم دوال PCRE ?>
تذكر: يجب مراعاة الأمان والأداء من بداية مشروعك، وليس إضافتهما لاحقاً. اتبع هذه الممارسات بشكل متسق لبناء تطبيقات PHP قوية.