تم إصدار PHP 8.2! اطلع على الميزات الجديدة

دليل PHP العربي

المرجع الشامل لمطوري PHP العرب

توثيق PHP الشامل

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

أساسيات PHP

PHP هي لغة برمجة نصية مفتوحة المصدر مصممة خصيصًا لتطوير تطبيقات الويب. تم إطلاقها في عام 1994 على يد راسموس ليردورف، وأصبحت واحدة من أكثر لغات البرمجة استخدامًا في تطوير الويب. دعنا نبدأ بالتعرف على أساسيات اللغة.

بناء الجملة الأساسي

ملفات PHP يمكن أن تحتوي على HTML وCSS وJavaScript بالإضافة إلى كود PHP. كود PHP يتم تنفيذه على الخادم، وينتج HTML يتم إرساله إلى المتصفح.

وسوم PHP

تبدأ وتنتهي كتابة كود PHP بوسوم خاصة:

<?php
// كود PHP هنا
?>
1
وسم فتح PHP - يخبر المترجم أن الكود التالي هو كود PHP.
2
تعليق في PHP - أي نص بعد الشرطتين المائلتين // سيتم تجاهله من قبل المترجم.
3
وسم إغلاق PHP - يشير إلى نهاية كود PHP. هذا اختياري إذا كان الملف يحتوي على PHP فقط.

صيغة الملف

ملفات PHP يمكن أن تحتوي على كود HTML وPHP معًا:

<!DOCTYPE html>
<html>
<head>
    <title>صفحة PHP</title>
</head>
<body>
    <h1>مرحباً بالعالم!</h1>
    
    <?php
    echo "هذا نص من PHP!";
    ?>
</body>
</html>
1-7
كود HTML عادي يتم إرساله مباشرة إلى المتصفح.
8
بداية كود PHP.
9
أمر echo يستخدم لإخراج النص أو القيم إلى HTML. هنا سيتم عرض "هذا نص من PHP!" في المتصفح.
10-12
إغلاق كود PHP ومتابعة كود HTML.

التعليقات

التعليقات في PHP تساعد على توثيق الكود وشرحه:

<?php
// هذا تعليق سطري واحد

# هذا أيضًا تعليق سطري واحد

/*
هذا تعليق
متعدد الأسطر
*/

echo "مرحباً بالعالم!"; // يمكن وضع تعليق بعد الكود
?>
2
تعليق سطري واحد باستخدام الشرطتين //.
4
تعليق سطري واحد باستخدام الرمز #.
6-9
تعليق متعدد الأسطر يبدأ بـ /* وينتهي بـ */.
11
كود PHP متبوع بتعليق في نفس السطر.

نصائح سريعة

  • في PHP، يجب أن تنتهي كل عبارة بفاصلة منقوطة (;).
  • وسم إغلاق PHP (?>) اختياري في الملفات التي تحتوي على PHP فقط.
  • يفضل ترك وسم الإغلاق لتجنب إرسال مسافات بيضاء غير مقصودة.
  • الملفات التي تحتوي على كود PHP فقط يُفضل أن تُحفظ بدون وسم إغلاق PHP.

المتغيرات والأنواع

المتغيرات في PHP هي حاويات لتخزين المعلومات. على عكس العديد من لغات البرمجة الأخرى، PHP هي لغة ذات أنواع ضعيفة، مما يعني أنه لا يلزم تحديد نوع المتغير عند تعريفه.

تعريف المتغيرات

في PHP، تبدأ جميع أسماء المتغيرات بالرمز $ متبوعًا باسم المتغير:

<?php
$name = "محمد";
$age = 25;
$is_student = true;
$height = 1.75;

echo "الاسم: " . $name . "
"; echo "العمر: " . $age . "
"; echo "طالب: " . ($is_student ? "نعم" : "لا") . "
"; echo "الطول: " . $height . " متر"; ?>
2
تعريف متغير نصي (string) باسم $name وإعطائه قيمة "محمد".
3
تعريف متغير عددي صحيح (integer) باسم $age وإعطائه قيمة 25.
4
تعريف متغير منطقي (boolean) باسم $is_student وإعطائه قيمة true.
5
تعريف متغير عددي عشري (float) باسم $height وإعطائه قيمة 1.75.
7-10
است استخدام أمر echo لعرض قيم المتغيرات. نستخدم عامل الربط "." لدمج النصوص مع المتغيرات.

قواعد تسمية المتغيرات

هناك عدة قواعد يجب اتباعها عند تسمية المتغيرات في PHP:

  • يجب أن يبدأ اسم المتغير بالرمز $ متبوعًا بحرف أو شرطة سفلية (_).
  • يمكن أن يحتوي اسم المتغير على أحرف وأرقام وشرطات سفلية.
  • لا يمكن أن يبدأ اسم المتغير برقم.
  • أسماء المتغيرات في PHP حساسة لحالة الأحرف (case-sensitive).
<?php
// صحيح
$name = "أحمد";
$_name = "محمد";
$name1 = "علي";
$firstName = "خالد";

// خطأ
$1name = "عمر";  // لا يمكن أن يبدأ اسم المتغير برقم
$name-first = "سعيد";  // لا يمكن استخدام الشرطة (-)

// متغيرات مختلفة بسبب حساسية حالة الأحرف
$Name = "حسن";
$name = "حسين";
echo $Name;  // سيطبع "حسن"
echo $name;  // سيطبع "حسين"
?>
2-6
أمثلة على أسماء متغيرات صحيحة في PHP.
9-10
أمثلة على أسماء متغيرات غير صحيحة مع شرح سبب الخطأ.
13-16
مثال يوضح أن PHP حساسة لحالة الأحرف حيث $Name و $name هما متغيران مختلفان.

أنواع البيانات في PHP

PHP يدعم ثمانية أنواع أساسية من البيانات:

<?php
// 1. النصوص (String)
$name = "أحمد علي";

// 2. الأعداد الصحيحة (Integer)
$age = 25;

// 3. الأعداد العشرية (Float/Double)
$height = 1.75;

// 4. القيم المنطقية (Boolean)
$is_active = true;

// 5. المصفوفات (Array)
$colors = ["أحمر", "أخضر", "أزرق"];
$user = [
    "name" => "محمد",
    "age" => 30,
    "email" => "[email protected]"
];

// 6. الكائنات (Object)
class Person {
    public $name;
    public function __construct($name) {
        $this->name = $name;
    }
}
$person = new Person("سارة");

// 7. NULL
$empty_var = NULL;

// 8. الموارد (Resource)
$file = fopen("example.txt", "r");

// التحقق من نوع المتغير
echo gettype($name);      // string
echo gettype($age);       // integer
echo gettype($is_active); // boolean
echo gettype($colors);    // array
echo gettype($person);    // object
echo gettype($empty_var); // NULL
?>
2-3
النوع الأول: النصوص (String) - سلسلة من الأحرف.
5-6
النوع الثاني: الأعداد الصحيحة (Integer) - أعداد بدون كسور.
8-9
النوع الثالث: الأعداد العشرية (Float/Double) - أعداد بكسور.
11-12
النوع الرابع: القيم المنطقية (Boolean) - إما true أو false.
14-19
النوع الخامس: المصفوفات (Array) - مجموعة من القيم. يمكن أن تكون مصفوفة عددية (مثل $colors) أو مصفوفة ترابطية (مثل $user).
21-27
النوع السادس: الكائنات (Object) - نسخة من فئة معرفة.
29-30
النوع السابع: NULL - متغير بدون قيمة.
32-33
النوع الثامن: الموارد (Resource) - مراجع خاصة لموارد خارجية (مثل اتصالات قواعد البيانات وملفات).
35-40
استخدام الدالة gettype() للتحقق من نوع المتغير.

تحويل الأنواع

PHP يسمح بتحويل البيانات من نوع إلى آخر بطريقتين:

<?php
// 1. التحويل الضمني
$num_str = "10";
$num = $num_str + 5;  // $num_str يتم تحويله ضمنيًا إلى رقم
echo $num;  // 15

// 2. التحويل الصريح (Type Casting)
$x = 10;
$x_float = (float) $x;  // تحويل إلى عشري
$x_str = (string) $x;   // تحويل إلى نص
$x_bool = (bool) $x;    // تحويل إلى منطقي

// دوال التحويل
$y = "15.5";
$y_int = intval($y);    // تحويل إلى عدد صحيح: 15
$y_float = floatval($y); // تحويل إلى عدد عشري: 15.5
$y_bool = boolval($y);   // تحويل إلى قيمة منطقية: true
?>
2-5
التحويل الضمني: PHP تلقائيًا تحول "10" من نص إلى رقم عند استخدامه في عملية حسابية.
7-11
التحويل الصريح: استخدام (type) قبل المتغير لتحويله إلى النوع المطلوب.
13-17
دوال التحويل: استخدام دوال مثل intval() و floatval() و boolval() لتحويل القيم.

نصائح سريعة

  • استخدم الوظيفة var_dump() لعرض معلومات مفصلة عن المتغير بما في ذلك نوعه وقيمته.
  • يمكن استخدام is_string(), is_int(), is_bool(), is_array() للتحقق من نوع المتغير.
  • تذكر أن PHP يحول أنواع البيانات تلقائيًا في بعض الحالات، مما قد يؤدي إلى نتائج غير متوقعة.
  • استخدم التحويل الصريح عندما تحتاج إلى التأكد من نوع البيانات المستخدمة.

العوامل والمعاملات

العوامل في PHP هي رموز تستخدم لإجراء عمليات على المتغيرات والقيم. تدعم PHP مجموعة متنوعة من العوامل للعمليات المختلفة.

عوامل حسابية

تستخدم لإجراء العمليات الحسابية الأساسية:

<?php
$a = 10;
$b = 3;

$sum = $a + $b;       // الجمع: 13
$difference = $a - $b; // الطرح: 7
$product = $a * $b;    // الضرب: 30
$quotient = $a / $b;   // القسمة: 3.33333...
$remainder = $a % $b;  // باقي القسمة: 1
$power = $a ** $b;     // الأس: 10^3 = 1000

// عوامل الزيادة والنقصان
$c = 5;
$c++;  // زيادة بعدية: استخدام $c ثم زيادته بواحد
++$c;  // زيادة قبلية: زيادة $c بواحد ثم استخدامه
$c--;  // نقصان بعدي: استخدام $c ثم نقصه بواحد
--$c;  // نقصان قبلي: نقص $c بواحد ثم استخدامه
?>
2-3
تعريف متغيرين $a و $b بقيم 10 و 3 لاستخدامهما في الأمثلة.
5-10
العوامل الحسابية الأساسية: الجمع (+)، الطرح (-)، الضرب (*)، القسمة (/)، باقي القسمة (%)، الأس (**).
12-17
عوامل الزيادة (++) والنقصان (--) بنوعيها القبلي والبعدي.

عوامل الإسناد

تستخدم لإسناد قيم للمتغيرات:

<?php
$a = 10;          // الإسناد البسيط
$a += 5;          // يساوي: $a = $a + 5; (النتيجة: 15)
$a -= 3;          // يساوي: $a = $a - 3; (النتيجة: 12)
$a *= 2;          // يساوي: $a = $a * 2; (النتيجة: 24)
$a /= 4;          // يساوي: $a = $a / 4; (النتيجة: 6)
$a %= 4;          // يساوي: $a = $a % 4; (النتيجة: 2)

// إسناد لسلاسل النصوص
$greeting = "مرحباً ";
$greeting .= "بالعالم!";  // يساوي: $greeting = $greeting . "بالعالم!";
echo $greeting;  // "مرحباً بالعالم!"
?>
2-7
عوامل الإسناد المختصرة للعمليات الحسابية. كل عامل يقوم بالعملية ثم يسند النتيجة للمتغير.
9-12
عامل الإسناد المختصر للنصوص (.=) لإضافة نص إلى نص موجود.

عوامل المقارنة

تستخدم لمقارنة قيمتين وإرجاع قيمة منطقية (true أو false):

<?php
$a = 10;
$b = "10";
$c = 5;

var_dump($a == $b);   // يساوي (مع تحويل النوع): true
var_dump($a === $b);  // يساوي تمامًا (نفس القيمة ونفس النوع): false
var_dump($a != $b);   // لا يساوي (مع تحويل النوع): false
var_dump($a !== $b);  // لا يساوي تمامًا (القيمة أو النوع مختلف): true
var_dump($a > $c);    // أكبر من: true
var_dump($a < $c);    // أصغر من: false
var_dump($a >= $b);   // أكبر من أو يساوي: true
var_dump($a <= $c);   // أصغر من أو يساوي: false

// عامل المقارنة الثلاثي (Spaceship operator) في PHP 7+
var_dump($a <=> $c);  // يرجع 1 لأن $a أكبر من $c
var_dump($c <=> $a);  // يرجع -1 لأن $c أصغر من $a
var_dump($a <=> $b);  // يرجع 0 لأن $a يساوي $b (مع تحويل النوع)
?>
2-4
تعريف متغيرات للمقارنة: $a (عدد)، $b (نص)، $c (عدد).
6-13
عوامل المقارنة الأساسية وشرح الفرق بين (==) التي تقارن القيمة فقط و(===) التي تقارن القيمة والنوع.
15-18
عامل المقارنة الثلاثي (<=>): يرجع 1 إذا كان الطرف الأيسر أكبر، -1 إذا كان أصغر، 0 إذا كانا متساويين.

عوامل منطقية

تستخدم لإجراء عمليات منطقية وتجميع شروط:

<?php
$a = true;
$b = false;

var_dump($a && $b);  // AND (و): false
var_dump($a || $b);  // OR (أو): true
var_dump(!$a);       // NOT (ليس): false

// يمكن أيضًا استخدام and, or, xor
var_dump($a and $b); // نفس && : false
var_dump($a or $b);  // نفس || : true
var_dump($a xor $b); // XOR (أو حصري) - true إذا كان أحدهما فقط true: true

// أمثلة عملية
$age = 25;
$is_student = true;

// التحقق من شرطين
$eligible = ($age >= 18) && $is_student;

// التحقق من أحد الشرطين على الأقل
$can_apply = ($age >= 18) || $is_student;
?>
2-3
تعريف متغيرات منطقية للأمثلة.
5-7
العوامل المنطقية الأساسية: && (و)، || (أو)، ! (ليس).
9-11
البدائل النصية للعوامل المنطقية: and, or, xor.
13-20
أمثلة عملية على استخدام العوامل المنطقية في شروط مركبة.

نصائح سريعة

  • انتبه للفرق بين == و === عند المقارنة. استخدم === عندما تريد التأكد من تطابق النوع والقيمة.
  • العوامل && و || لها أولوية تقييم قصيرة - لن يتم تقييم الجزء الثاني إذا كانت النتيجة معروفة من الجزء الأول.
  • هناك فرق في الأولوية بين && و and، حيث && لها أولوية أعلى.
  • عند استخدام عامل الأس (**)، تأكد من أنك تستخدم PHP 5.6 أو أحدث.

الثوابت

الثوابت هي معرفات (أسماء) لقيم بسيطة لا تتغير خلال تنفيذ البرنامج. على عكس المتغيرات، الثوابت لا تبدأ بالرمز $.

تعريف الثوابت

يمكن تعريف الثوابت باستخدام الدالة define() أو باستخدام الكلمة المفتاحية const:

<?php
// تعريف الثوابت باستخدام define()
define("DB_HOST", "localhost");
define("DB_USER", "root");
define("DB_PASS", "password123");
define("PI", 3.14159);
define("IS_ADMIN", true);

// تعريف الثوابت باستخدام const (متاح منذ PHP 5.3)
const APP_NAME = "نظام إدارة المحتوى";
const MAX_UPLOAD_SIZE = 10485760; // 10 ميجابايت
const DEBUG_MODE = false;

// استخدام الثوابت
echo "اسم التطبيق: " . APP_NAME . "
"; echo "قيمة باي: " . PI . "
"; // الثوابت المعرفة مسبقًا في PHP echo "إصدار PHP: " . PHP_VERSION . "
"; echo "نظام التشغيل: " . PHP_OS . "
"; ?>
2-6
تعريف الثوابت باستخدام الدالة define(). الوسيط الأول هو اسم الثابت والثاني هو قيمته.
8-11
تعريف الثوابت باستخدام الكلمة المفتاحية const، وهي متاحة منذ PHP 5.3.
13-14
استخدام الثوابت المعرفة بكتابة اسمها مباشرة بدون رمز $ أو أي علامات أخرى.
16-18
استخدام الثوابت المعرفة مسبقًا في PHP مثل PHP_VERSION و PHP_OS.

الفرق بين define() و const

هناك عدة اختلافات بين طريقتي تعريف الثوابت:

<?php
// define() يمكن استخدامه في بنية شرطية
if (!defined('MAX_ATTEMPTS')) {
    define('MAX_ATTEMPTS', 5);
}

// const لا يمكن استخدامه في بنية شرطية
// هذا سيسبب خطأ:
/*
if (!defined('MIN_LENGTH')) {
    const MIN_LENGTH = 8; // خطأ!
}
*/

// const لديه نطاق (scope) مختلف
class User {
    // يمكن استخدام const داخل الفئات
    const STATUS_ACTIVE = 1;
    const STATUS_INACTIVE = 0;
    
    // هذا سيسبب خطأ:
    // define('USER_ROLE', 'admin'); // خطأ!
}

// الوصول إلى ثابت الفئة
echo User::STATUS_ACTIVE;
?>
2-4
يمكن استخدام define() داخل بنيات التحكم مثل الشروط والحلقات.
6-11
لا يمكن استخدام const داخل بنيات التحكم، لأنها تُقيّم في وقت الترجمة وليس وقت التنفيذ.
13-21
يمكن استخدام const داخل الفئات (كثوابت الفئة) بينما لا يمكن استخدام define() داخل الفئات.
23-24
الوصول إلى ثابت الفئة باستخدام رمز المجال :: (نقطتين).

الثوابت السحرية

PHP يوفر عددًا من الثوابت السحرية التي تتغير قيمتها حسب سياق استخدامها:

<?php
// الثوابت السحرية في PHP
echo "اسم الملف: " . __FILE__ . "
"; // المسار الكامل للملف الحالي echo "الدليل: " . __DIR__ . "
"; // المسار الكامل للدليل الحالي echo "السطر: " . __LINE__ . "
"; // رقم السطر الحالي في الملف echo "الدالة: " . __FUNCTION__ . "
"; // اسم الدالة الحالية echo "الفئة: " . __CLASS__ . "
"; // اسم الفئة الحالية echo "الطريقة: " . __METHOD__ . "
"; // اسم طريقة الفئة الحالية echo "المساحة: " . __NAMESPACE__ . "
"; // اسم مساحة الأسماء الحالية // مثال استخدام __LINE__ في رسائل الخطأ function logError($message) { $error = "خطأ في السطر " . __LINE__ . ": " . $message; error_log($error); return false; } ?>
2-8
أمثلة على الثوابت السحرية في PHP وشرح لكل منها.
10-14
مثال عملي على استخدام الثابت السحري __LINE__ في وظيفة لتسجيل الأخطاء.

نصائح سريعة

  • أسماء الثوابت عادةً تكون بأحرف كبيرة مع استخدام الشرطة السفلية للفصل (مثل MAX_SIZE).
  • لا يمكن تغيير قيمة الثابت بعد تعريفه، محاولة ذلك ستؤدي إلى خطأ.
  • استخدم الدالة defined() للتحقق مما إذا كان الثابت معرفًا بالفعل.
  • الثوابت لها نطاق عام (global scope) افتراضيًا ويمكن الوصول إليها من أي مكان في البرنامج.
  • في PHP 7.0+، يمكن تعريف مصفوفات كثوابت باستخدام كلا الطريقتين define() و const.

هياكل التحكم

هياكل التحكم هي أساس البرمجة لأنها تتيح لك التحكم في تدفق التنفيذ في البرنامج. تسمح لك بتنفيذ أجزاء معينة من الكود بناءً على شروط محددة أو تكرار مجموعة من التعليمات عدة مرات. PHP تدعم بنيات التحكم الأساسية الموجودة في معظم لغات البرمجة.

الجمل الشرطية

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

عبارة if

تُستخدم عبارة if لتنفيذ كود معين إذا كان الشرط المحدد صحيحًا (true):

<?php
$hour = date('H'); // الحصول على الساعة الحالية (بتنسيق 24 ساعة)

if ($hour < 12) {
    echo "صباح الخير!";
}

// يمكن استخدام if مع else
if ($hour < 12) {
    echo "صباح الخير!";
} else {
    echo "مساء الخير!";
}

// يمكن تسلسل الشروط باستخدام elseif
if ($hour < 12) {
    echo "صباح الخير!";
} elseif ($hour < 17) {
    echo "مساء الخير!";
} else {
    echo "مساء سعيد!";
}

// الصيغة المختصرة للعبارة if
$status = ($hour < 12) ? "صباحًا" : "مساءً"; // العامل الثلاثي
echo "الوقت الآن: " . $hour . " " . $status;
?>
2
استخدام دالة date() للحصول على الساعة الحالية بتنسيق 24 ساعة.
4-6
عبارة if بسيطة: إذا كانت الساعة أقل من 12، يتم عرض "صباح الخير!".
8-12
عبارة if مع else: إذا كانت الساعة أقل من 12، يتم عرض "صباح الخير!"، وإلا يتم عرض "مساء الخير!".
14-20
عبارة if مع elseif وelse: إضافة شرط وسيط للتحقق مما إذا كانت الساعة بين 12 و17.
22-23
العامل الثلاثي (?) هو صيغة مختصرة للعبارة if-else. تقييم الشرط وإرجاع "صباحًا" إذا كان صحيحًا وإلا إرجاع "مساءً".

عبارة if المتداخلة

يمكن تضمين عبارات if داخل بعضها البعض:

<?php
$hour = date('H');
$isWeekend = (date('N') >= 6); // 6 = السبت، 7 = الأحد

if ($isWeekend) {
    if ($hour < 12) {
        echo "استمتع بعطلة نهاية الأسبوع! وقت مناسب للإفطار.";
    } else {
        echo "استمتع بعطلة نهاية الأسبوع! هل لديك خطط للمساء؟";
    }
} else {
    if ($hour < 9) {
        echo "يوم عمل جديد! استعد للإنتاجية.";
    } elseif ($hour >= 17) {
        echo "انتهى يوم العمل! استمتع بوقتك.";
    } else {
        echo "منتصف يوم العمل! استمر في العمل الجيد.";
    }
}
?>
2-3
الحصول على الساعة الحالية وتحديد ما إذا كان اليوم هو عطلة نهاية الأسبوع (السبت أو الأحد).
5-10
التحقق أولاً مما إذا كان نهاية الأسبوع، ثم استخدام عبارة if متداخلة للتحقق من الوقت من اليوم.
11-19
جزء else مع تعبير if-elseif-else متداخل لليوم العادي، مع رسائل مختلفة بناءً على وقت اليوم.

استخدام الأقواس المتعرجة ومشكلات محتملة

عند استخدام عبارة if، يمكن حذف الأقواس المتعرجة إذا كان هناك أمر واحد فقط للتنفيذ، ولكن هذا يمكن أن يسبب أخطاء منطقية:

<?php
// بدون أقواس متعرجة (أمر واحد فقط)
if ($hour < 12)
    echo "صباح الخير!";
    
// خطأ منطقي محتمل - الجملة الثانية ستنفذ دائمًا
if ($hour < 12)
    echo "صباح الخير!";
    echo "الساعة الآن: " . $hour; // هذا السطر سيتم تنفيذه دائمًا بغض النظر عن الشرط
    
// الصيغة الصحيحة باستخدام الأقواس المتعرجة
if ($hour < 12) {
    echo "صباح الخير!";
    echo "الساعة الآن: " . $hour;
}

// أو باستخدام elseif/else بدون أقواس
if ($hour < 12)
    echo "صباح الخير!";
elseif ($hour < 17)
    echo "مساء الخير!";
else
    echo "مساء سعيد!";
?>
2-4
استخدام if بدون أقواس متعرجة. فقط الأمر المباشر التالي لـ if مرتبط بالشرط.
6-9
مثال على خطأ شائع: يتم تنفيذ الأمر الثاني دائمًا لأنه ليس جزءًا من كتلة if (بدون أقواس متعرجة).
11-14
الاستخدام الصحيح للأقواس المتعرجة لتجميع عدة أوامر في كتلة if.
16-21
استخدام if/elseif/else بدون أقواس متعرجة عندما يكون لديك أمر واحد فقط لكل حالة.

نصائح سريعة

  • دائمًا استخدم الأقواس المتعرجة {} للتعبيرات الشرطية حتى لو كان هناك بيان واحد فقط، لتجنب الأخطاء المنطقية في المستقبل.
  • استخدم العامل الثلاثي (؟:) لعبارات if-else البسيطة لجعل الكود أكثر إيجازًا.
  • تأكد من أن شروطك مكتوبة بشكل صحيح لتجنب نتائج غير متوقعة.
  • تجنب التداخل العميق للتعبيرات الشرطية لأنها قد تصبح صعبة الفهم - ضع في اعتبارك تقسيم المنطق إلى دوال منفصلة.

حلقات التكرار

حلقات التكرار تسمح لك بتنفيذ مجموعة من الأوامر مرارًا وتكرارًا بناءً على شرط معين.

حلقة for

تُستخدم حلقة for عندما تعرف مسبقًا عدد المرات التي تريد تنفيذ الكود فيها:

<?php
// الهيكل الأساسي: for (قيمة البداية; الشرط; التحديث)
for ($i = 0; $i < 5; $i++) {
    echo "العنصر رقم: " . $i . "
"; } // استخدام حلقة for لطباعة جدول الضرب echo "

جدول ضرب الرقم 5

"; for ($i = 1; $i <= 10; $i++) { echo "5 × " . $i . " = " . (5 * $i) . "
"; } // استخدام متغيرات في إعلان الحلقة $start = 1; $end = 5; for ($i = $start; $i <= $end; $i++) { echo "المربع: " . $i * $i . "
"; } // حلقة for بالعد التنازلي for ($countdown = 10; $countdown > 0; $countdown--) { echo $countdown . "...
"; } echo "انطلق!"; ?>
2-5
الهيكل الأساسي لحلقة for: تحديد قيمة البداية ($i = 0)، شرط الاستمرار ($i < 5)، وكيفية تحديث المتغير بعد كل تكرار ($i++).
7-10
استخدام حلقة for لإنشاء جدول ضرب للرقم 5 من 1 إلى 10.
12-16
استخدام متغيرات لتحديد نطاق الحلقة بدلاً من القيم المباشرة.
18-22
استخدام حلقة for للعد التنازلي باستخدام النقصان (--).

حلقة while

تُستخدم حلقة while لتنفيذ مجموعة من الأوامر طالما أن الشرط المحدد صحيح:

<?php
// الهيكل الأساسي لحلقة while
$count = 1;
while ($count <= 5) {
    echo "العداد: " . $count . "
"; $count++; // زيادة العداد لتجنب الحلقة اللانهائية } // توليد رقم عشوائي حتى الحصول على 6 $attempts = 0; $dice = 0; while ($dice != 6) { $dice = rand(1, 6); // توليد رقم عشوائي بين 1 و 6 $attempts++; echo "المحاولة " . $attempts . ": تم الحصول على الرقم " . $dice . "
"; } echo "تم الحصول على الرقم 6 بعد " . $attempts . " محاولات!"; // مثال على حلقة while قد تنتهي دون تنفيذ الكود داخلها $condition = false; while ($condition) { echo "لن يتم تنفيذ هذا الكود أبدًا."; } ?>
2-6
الهيكل الأساسي لحلقة while: يتم فحص الشرط قبل تنفيذ كتلة الكود. إذا كان الشرط صحيحًا، يتم تنفيذ الكود.
8-17
استخدام حلقة while لتوليد أرقام عشوائية حتى الحصول على الرقم 6. عدد التكرارات غير معروف مسبقًا.
19-23
مثال يوضح أنه إذا كان الشرط غير صحيح من البداية، فلن يتم تنفيذ كود while أبدًا.

حلقة do-while

على عكس حلقة while، تنفذ حلقة do-while كتلة الكود مرة واحدة على الأقل قبل التحقق من الشرط:

<?php
// الهيكل الأساسي لحلقة do-while
$count = 1;
do {
    echo "العداد: " . $count . "
"; $count++; } while ($count <= 5); // مثال على do-while حتى عندما يكون الشرط غير صحيح من البداية $condition = false; do { echo "سيتم تنفيذ هذا الكود مرة واحدة على الأقل، حتى لو كان الشرط خاطئًا.
"; } while ($condition); // استخدام do-while لتأكيد الإدخال /* $input = ''; do { $input = readline("أدخل رقمًا بين 1 و 10: "); } while ($input < 1 || $input > 10); echo "لقد أدخلت: " . $input; */ ?>
2-7
الهيكل الأساسي لحلقة do-while: تنفيذ الكود أولًا، ثم التحقق من الشرط.
9-13
مثال يوضح أن do-while تنفذ الكود مرة واحدة على الأقل، حتى لو كان الشرط غير صحيح في البداية.
15-21
مثال عملي (معلق) لاستخدام do-while للتحقق من إدخال المستخدم: الاستمرار في طلب الإدخال حتى يتم إدخال قيمة صحيحة.

حلقة foreach

تُستخدم حلقة foreach للتكرار على عناصر المصفوفات (arrays) والكائنات (objects):

<?php
// التكرار على مصفوفة بسيطة
$colors = ["أحمر", "أخضر", "أزرق"];

foreach ($colors as $color) {
    echo "اللون: " . $color . "
"; } // التكرار على مصفوفة مع الفهرس foreach ($colors as $index => $color) { echo "الفهرس " . $index . ": اللون " . $color . "
"; } // التكرار على مصفوفة ترابطية (associative array) $person = [ "name" => "أحمد", "age" => 30, "city" => "القاهرة", "job" => "مطور ويب" ]; foreach ($person as $key => $value) { echo $key . ": " . $value . "
"; } // تغيير قيم المصفوفة باستخدام الإشارة المرجعية (&) $numbers = [1, 2, 3, 4, 5]; foreach ($numbers as &$number) { $number *= 2; // مضاعفة كل رقم } // لا تنس إلغاء المرجع بعد الانتهاء unset($number); echo "المصفوفة بعد المضاعفة: "; foreach ($numbers as $number) { echo $number . " "; } ?>
2-6
استخدام foreach للتكرار على مصفوفة بسيطة، حيث يتم تخزين قيمة العنصر الحالي في متغير $color.
8-10
التكرار مع الحصول على الفهرس (index) وقيمة كل عنصر.
12-20
استخدام foreach مع مصفوفة ترابطية (associative array) للحصول على المفتاح والقيمة لكل زوج.
22-32
استخدام الإشارة المرجعية (&) لتعديل قيم المصفوفة الأصلية أثناء التكرار. لاحظ استخدام unset() لإلغاء المرجع بعد الاستخدام.

التحكم في الحلقة: break و continue

يمكنك استخدام عبارتي break و continue للتحكم في سلوك الحلقات:

<?php
// استخدام break للخروج من الحلقة
for ($i = 1; $i <= 10; $i++) {
    if ($i == 5) {
        echo "تم الوصول إلى 5. سنخرج من الحلقة.
"; break; // الخروج من الحلقة تمامًا } echo "الرقم الحالي: " . $i . "
"; } // استخدام continue لتخطي التكرار الحالي for ($i = 1; $i <= 10; $i++) { if ($i % 2 == 0) { continue; // تخطي الأرقام الزوجية } echo "رقم فردي: " . $i . "
"; } // استخدام break في حلقات متداخلة $found = false; for ($i = 1; $i <= 5; $i++) { for ($j = 1; $j <= 5; $j++) { echo "[$i, $j] "; if ($i * $j == 9) { echo "وجدنا النتيجة 9 عند $i × $j!
"; $found = true; break 2; // الخروج من الحلقتين معًا } } echo "
"; } if (!$found) { echo "لم نجد المطلوب."; } ?>
2-8
استخدام break للخروج من الحلقة تمامًا عند تحقق شرط معين. هنا سيتم الخروج عند وصول $i إلى 5.
10-16
استخدام continue لتخطي التكرار الحالي والانتقال إلى التكرار التالي. هنا سيتم تخطي الأرقام الزوجية.
18-31
استخدام break مع رقم (break 2) للخروج من حلقات متداخلة متعددة. هنا نخرج من الحلقتين عند العثور على $i و $j بحيث $i × $j = 9.

نصائح سريعة

  • استخدم حلقة for عندما تعرف عدد التكرارات مسبقًا.
  • استخدم حلقة while عندما لا تعرف عدد التكرارات ويعتمد الاستمرار على شرط معين.
  • استخدم حلقة do-while عندما تحتاج إلى تنفيذ الكود مرة واحدة على الأقل قبل التحقق من الشرط.
  • استخدم حلقة foreach للتكرار على عناصر المصفوفات والكائنات.
  • احرص على تجنب الحلقات اللانهائية بالتأكد من تحديث متغيرات الشرط داخل الحلقة.
  • استخدم break عندما تريد الخروج من الحلقة بشكل كامل.
  • استخدم continue عندما تريد تخطي التكرار الحالي فقط.

جملة switch

جملة switch هي بديل للعديد من جمل if-elseif-else المتتالية. فهي تجعل الكود أكثر وضوحًا عند مقارنة متغير واحد مع قيم متعددة.

الصيغة الأساسية

جملة switch تقارن قيمة متغير مع قيم مختلفة وتنفذ كود مختلف بناءً على المطابقة:

<?php
$day = date("D"); // الحصول على اختصار اليوم الحالي (Sun, Mon, Tue, ...)

switch ($day) {
    case "Mon":
        echo "اليوم هو الاثنين.";
        break;
    case "Tue":
        echo "اليوم هو الثلاثاء.";
        break;
    case "Wed":
        echo "اليوم هو الأربعاء.";
        break;
    case "Thu":
        echo "اليوم هو الخميس.";
        break;
    case "Fri":
        echo "اليوم هو الجمعة.";
        break;
    case "Sat":
    case "Sun":
        echo "اليوم هو نهاية الأسبوع.";
        break;
    default:
        echo "قيمة غير معروفة!";
}
?>
2
استخدام دالة date() للحصول على اختصار اليوم الحالي.
4-21
بناء جملة switch مع عدة حالات (cases) لأيام الأسبوع المختلفة.
15-17
يمكن ضم عد يمكن ضم عدة حالات معًا (case "Sat" و case "Sun") لتنفيذ نفس الكود لقيم مختلفة.
18-20
حالة default: يتم تنفيذها عندما لا تتطابق قيمة المتغير مع أي من الحالات المحددة.

أهمية عبارة break

من المهم استخدام break في نهاية كل حالة، وإلا فسيستمر التنفيذ في الحالات التالية:

<?php
$grade = 'B';

switch ($grade) {
    case 'A':
        echo "ممتاز!";
        break;
    case 'B': // بدون break
        echo "جيد جدًا!";
        // لاحظ: نسيان break هنا
    case 'C':
        echo "جيد!";
        break;
    case 'D':
        echo "مقبول!";
        break;
    default:
        echo "راسب!";
}
// النتيجة: "جيد جدًا!جيد!"
// لأن التنفيذ يستمر في حالة C بعد حالة B
?>
2
تعريف متغير $grade بقيمة 'B'.
7-9
حالة 'B' بدون break في نهايتها، مما يؤدي إلى استمرار التنفيذ في الحالة التالية.
17-19
توضيح النتيجة: سيتم طباعة النص من حالة 'B' ومن حالة 'C' أيضًا بسبب نسيان break.

استخدام التعبيرات في switch

يمكن استخدام التعبيرات في switch، ولكن يجب أن تعطي نتائج قابلة للمقارنة:

<?php
$score = 85;

switch (true) {
    case ($score >= 90):
        echo "ممتاز (A)";
        break;
    case ($score >= 80):
        echo "جيد جدًا (B)";
        break;
    case ($score >= 70):
        echo "جيد (C)";
        break;
    case ($score >= 60):
        echo "مقبول (D)";
        break;
    default:
        echo "راسب (F)";
}
// النتيجة: "جيد جدًا (B)" لأن $score = 85
?>
2
تعريف متغير $score بقيمة 85.
4
استخدام switch(true) يعني أن كل حالة ستقارن تعبيرها مع true، وبالتالي يتم تنفيذ أول حالة تعطي تعبيرها نتيجة true.
5-16
تحديد حالات مختلفة بناءً على قيمة $score.

نصائح سريعة

  • تأكد دائمًا من وضع break في نهاية كل حالة، إلا إذا كنت تريد تحديدًا الاستمرار في الحالة التالية.
  • جملة switch تستخدم == (مساواة) وليس === (تطابق تام) للمقارنة، لذا ستتم تحويلات النوع.
  • استخدم default للتعامل مع الحالات غير المتوقعة أو غير المعروفة.
  • يمكن استخدام return بدلاً من break إذا كنت داخل دالة.
  • استخدم if-elseif-else بدلاً من switch إذا كنت تقارن قيمًا مختلفة أو تستخدم عوامل مقارنة مختلفة.

الدوال

الدوال هي كتل من الكود يمكن استدعاؤها للقيام بمهمة معينة. وهي تساعد في تنظيم الكود وجعله قابلاً لإعادة الاستخدام وأكثر قابلية للصيانة. في PHP، يمكنك تعريف دوالك الخاصة بالإضافة إلى استخدام الدوال المدمجة في اللغة.

تعريف الدوال

يتم تعريف الدوال في PHP باستخدام الكلمة المفتاحية function، متبوعة باسم الدالة و قوسين () وكتلة من الكود محاطة بأقواس متعرجة {}.

الصيغة الأساسية

فيما يلي الصيغة الأساسية لتعريف الدالة:

<?php
// تعريف دالة بسيطة بدون وسائط
function sayHello() {
    echo "مرحباً بالعالم!";
}

// استدعاء الدالة
sayHello(); // ستطبع: مرحباً بالعالم!

// دالة مع وسائط (parameters)
function greetUser($name) {
    echo "مرحباً يا " . $name . "!";
}

// استدعاء الدالة مع تمرير قيمة
greetUser("أحمد"); // ستطبع: مرحباً يا أحمد!

// دالة تقوم بإرجاع قيمة
function addNumbers($a, $b) {
    return $a + $b;
}

// تخزين القيمة المرجعة في متغير
$sum = addNumbers(5, 3);
echo "المجموع: " . $sum; // ستطبع: المجموع: 8

// يمكن أيضًا استخدام القيمة المرجعة مباشرة
echo "5 + 10 = " . addNumbers(5, 10); // ستطبع: 5 + 10 = 15
?>
2-5
تعريف دالة بسيطة بدون وسائط تقوم بطباعة نص.
7-8
استدعاء الدالة باستخدام اسمها متبوعًا بقوسين.
10-13
تعريف دالة مع وسيط واحد ($name) لتخصيص الرسالة.
15-16
استدعاء الدالة مع تمرير قيمة للوسيط.
18-21
تعريف دالة تقوم بإرجاع قيمة باستخدام الكلمة المفتاحية return.
23-28
استخدام القيمة المرجعة من الدالة إما بتخزينها في متغير أو استخدامها مباشرة.

تعريف وتسمية الدوال

هناك بعض القواعد والممارسات التي يجب اتباعها عند تعريف الدوال:

<?php
// أسماء الدوال ليست حساسة لحالة الأحرف (case-insensitive)
function HelloWorld() {
    echo "مرحباً بالعالم!";
}
helloworld(); // ستعمل رغم اختلاف حالة الأحرف

// قواعد تسمية الدوال:
// 1. يجب أن تبدأ بحرف أو underscore (_)
// 2. يمكن أن تحتوي على أحرف وأرقام و underscores

// ✓ أسماء دوال صحيحة
function calculate_total() { /* كود */ }
function _privateFunction() { /* كود */ }
function getUserData123() { /* كود */ }

// ✗ أسماء دوال غير صحيحة
// function 123getData() { /* كود */ } // لا يمكن أن يبدأ الاسم برقم
// function get-data() { /* كود */ } // لا يمكن استخدام الشرطة (-)

// لا يمكن تعريف دالتين بنفس الاسم
function test() {
    echo "اختبار 1";
}

/* هذا سيسبب خطأ
function test() {
    echo "اختبار 2";
}
*/
?>
2-6
أسماء الدوال في PHP ليست حساسة لحالة الأحرف، لكن يفضل الالتزام بأسلوب تسمية متسق.
8-17
قواعد وأمثلة على أسماء الدوال الصحيحة والخاطئة في PHP.
19-28
لا يمكن تعريف دالتين بنفس الاسم في نفس النطاق، وإلا فستحدث أخطاء.

تسميات شائعة للدوال

اتفاقيات التسمية الشائعة للدوال في PHP:

  • camelCase: أسلوب شائع حيث تبدأ الكلمة الأولى بحرف صغير والكلمات التالية تبدأ بحرف كبير (مثل: getUserInfo()).
  • snake_case: استخدام الشرطة السفلية لفصل الكلمات، وجميع الأحرف صغيرة (مثل: get_user_info()).
  • PascalCase: كل كلمة تبدأ بحرف كبير (مثل: GetUserInfo()). يستخدم عادة للفئات (classes) أكثر من الدوال.
  • يُفضل اختيار أسلوب واحد والالتزام به في المشروع بأكمله لتحقيق الاتساق.

الوسائط والمعاملات

الوسائط (Parameters) هي المتغيرات المعرفة في تعريف الدالة، بينما المعاملات (Arguments) هي القيم التي يتم تمريرها للدالة عند استدعائها.

الوسائط الإلزامية والاختيارية

يمكن تعريف وسائط إلزامية ووسائط اختيارية في PHP:

<?php
// دالة بوسائط إلزامية
function divide($dividend, $divisor) {
    return $dividend / $divisor;
}

// يجب تمرير قيمتين عند استدعاء هذه الدالة
$result = divide(10, 2); // النتيجة: 5
// $result = divide(10); // خطأ: ينقص وسيط

// دالة بوسائط اختيارية (مع قيم افتراضية)
function power($base, $exponent = 2) {
    return pow($base, $exponent);
}

// يمكن استدعاء هذه الدالة مع وسيط واحد أو اثنين
$square = power(5);     // النتيجة: 25 (5^2)
$cube = power(5, 3);    // النتيجة: 125 (5^3)

// الوسائط الإلزامية يجب أن تأتي قبل الوسائط الاختيارية
function greeting($name, $title = "Mr./Ms.", $suffix = "") {
    return "Hello, $title $name $suffix";
}

echo greeting("Ahmed");                  // Hello, Mr./Ms. Ahmed
echo greeting("Ahmed", "Dr.");           // Hello, Dr. Ahmed
echo greeting("Ahmed", "Dr.", "Ph.D.");  // Hello, Dr. Ahmed Ph.D.
?>
2-5
تعريف دالة مع وسائط إلزامية: $dividend و $divisor.
7-9
استدعاء الدالة مع القيم المطلوبة. محاولة استدعائها بدون جميع الوسائط ستؤدي إلى خطأ.
11-14
تعريف دالة مع وسيط اختياري ($exponent) له قيمة افتراضية 2.
16-18
استدعاء الدالة مع وسيط واحد (تستخدم القيمة الافتراضية للوسيط الثاني) أو مع وسيطين (تجاوز القيمة الافتراضية).
20-27
مثال أكثر تعقيدًا مع عدة وسائط اختيارية وطرق استدعاء مختلفة.

تمرير الوسائط بالقيمة وبالمرجع

افتراضيًا، يتم تمرير الوسائط بالقيمة في PHP. يمكنك أيضًا تمريرها بالمرجع باستخدام &:

<?php
// تمرير بالقيمة (الافتراضي) - لا يتم تغيير المتغير الأصلي
function increment($num) {
    $num++;
    echo "داخل الدالة: $num
"; } $value = 5; increment($value); echo "خارج الدالة: $value
"; // النتيجة: // داخل الدالة: 6 // خارج الدالة: 5 (لم يتغير المتغير الأصلي) // تمرير بالمرجع - يتم تغيير المتغير الأصلي function incrementByReference(&$num) { $num++; echo "داخل الدالة: $num
"; } $value = 5; incrementByReference($value); echo "خارج الدالة: $value
"; // النتيجة: // داخل الدالة: 6 // خارج الدالة: 6 (تم تغيير المتغير الأصلي) // مثال عملي: تبديل قيم متغيرين function swap(&$a, &$b) { $temp = $a; $a = $b; $b = $temp; } $x = 5; $y = 10; echo "قبل التبديل: x = $x, y = $y
"; swap($x, $y); echo "بعد التبديل: x = $x, y = $y
"; // النتيجة: // قبل التبديل: x = 5, y = 10 // بعد التبديل: x = 10, y = 5 ?>
2-10
مثال على تمرير الوسيط بالقيمة: تغييرات الوسيط داخل الدالة لا تؤثر على المتغير الأصلي خارج الدالة.
14-22
مثال على تمرير الوسيط بالمرجع (باستخدام &): تغييرات الوسيط داخل الدالة تؤثر على المتغير الأصلي خارج الدالة.
24-35
مثال عملي: دالة تبديل قيم متغيرين باستخدام التمرير بالمرجع.

عدد متغير من الوسائط

يمكنك تعريف دوال تقبل عددًا متغيرًا من الوسائط باستخدام ... (spread operator):

<?php
// دالة مع عدد متغير من الوسائط (PHP 5.6+)
function sum(...$numbers) {
    $total = 0;
    foreach ($numbers as $number) {
        $total += $number;
    }
    return $total;
}

// استدعاء الدالة مع عدد مختلف من المعاملات
echo sum(1, 2);            // 3
echo sum(1, 2, 3, 4, 5);   // 15
echo sum();                // 0

// يمكن دمج الوسائط الثابتة مع عدد متغير من الوسائط
function calculateTotal($taxRate, ...$prices) {
    $total = 0;
    foreach ($prices as $price) {
        $total += $price;
    }
    return $total * (1 + $taxRate);
}

// استدعاء الدالة مع الوسيط الثابت وعدد من الأسعار
echo calculateTotal(0.1, 100, 50, 75);  // (100 + 50 + 75) * 1.1 = 247.5

// يمكن أيضًا تمرير مصفوفة باستخدام عامل التوسيع
$values = [10, 20, 30];
echo sum(...$values);  // 60
?>
2-9
تعريف دالة تقبل عددًا متغيرًا من الوسائط باستخدام عامل التوسيع (...).
11-14
استدعاء الدالة مع أعداد مختلفة من المعاملات.
16-29
مثال أكثر تعقيدًا: دالة تجمع بين وسيط ثابت (معدل الضريبة) وعدد متغير من الوسائط (الأسعار).
31-33
استخدام عامل التوسيع (...) لتمرير عناصر مصفوفة كوسائط منفصلة.

نصائح سريعة

  • استخدم الوسائط الاختيارية للقيم التي غالبًا ما تكون لها قيمة معينة.
  • استخدم التمرير بالمرجع فقط عندما تحتاج إلى تعديل المتغيرات الأصلية.
  • كن حذرًا عند استخدام عدد متغير من الوسائط، وتأكد من التحقق من قيم الوسائط قبل استخدامها.
  • في PHP 7.0+، يمكنك تحديد نوع الوسائط (type declarations) لزيادة موثوقية الكود.
  • احرص على وضع الوسائط الإلزامية قبل الوسائط الاختيارية، ووسيط "..." دائمًا في النهاية.

إرجاع القيم

الدوال في PHP يمكنها إرجاع قيم باستخدام الكلمة المفتاحية return. يمكن للدالة أن ترجع أي نوع من البيانات.

إرجاع القيم الأساسية

أمثلة على إرجاع أنواع مختلفة من القيم:

<?php
// إرجاع قيمة عددية
function square($number) {
    return $number * $number;
}

// إرجاع قيمة نصية
function getGreeting($name) {
    return "مرحباً " . $name . "!";
}

// إرجاع قيمة منطقية
function isEven($number) {
    return $number % 2 == 0;
}

// إرجاع مصفوفة
function getRange($start, $end) {
    $result = [];
    for ($i = $start; $i <= $end; $i++) {
        $result[] = $i;
    }
    return $result;
}

// استخدام القيم المرجعة
$area = square(5);
echo "المساحة: " . $area . "
"; // المساحة: 25 $message = getGreeting("أحمد"); echo $message . "
"; // مرحباً أحمد! if (isEven(4)) { echo "4 هو رقم زوجي
"; } $numbers = getRange(1, 5); echo "الأرقام: " . implode(", ", $numbers); // الأرقام: 1, 2, 3, 4, 5 ?>
2-5
دالة ترجع قيمة عددية (مربع الرقم).
7-10
دالة ترجع قيمة نصية (تحية).
12-15
دالة ترجع قيمة منطقية (true إذا كان الرقم زوجيًا، false إذا كان فرديًا).
17-24
دالة ترجع مصفوفة (نطاق من الأرقام).
26-36
أمثلة على استخدام القيم المرجعة من الدوال.

قيم الإرجاع المتعددة

في PHP، يمكن للدالة أن ترجع قيمة واحدة فقط. لإرجاع عدة قيم، يمكن استخدام مصفوفة أو كائن:

<?php
// إرجاع عدة قيم باستخدام مصفوفة
function getCircleProperties($radius) {
    $area = pi() * $radius * $radius;
    $circumference = 2 * pi() * $radius;
    
    return [
        'radius' => $radius,
        'area' => $area,
        'circumference' => $circumference
    ];
}

// استخدام القيم المرجعة
$properties = getCircleProperties(5);
echo "نصف القطر: " . $properties['radius'] . "
"; echo "المساحة: " . $properties['area'] . "
"; echo "المحيط: " . $properties['circumference'] . "
"; // استخراج القيم باستخدام list() مع PHP 7.1+ function getCoordinates() { return [10, 20]; // x, y } // طريقة قديمة $coordinates = getCoordinates(); $x = $coordinates[0]; $y = $coordinates[1]; // طريقة حديثة باستخدام list() list($x, $y) = getCoordinates(); // أو في PHP 7.1+: // [$x, $y] = getCoordinates(); echo "X: $x, Y: $y"; // X: 10, Y: 20 // إرجاع عدة قيم باستخدام كائن class Point { public $x; public $y; public function __construct($x, $y) { $this->x = $x; $this->y = $y; } } function createPoint($x, $y) { return new Point($x, $y); } $point = createPoint(5, 10); echo "النقطة: (" . $point->x . ", " . $point->y . ")"; // النقطة: (5, 10) ?>
1-3
استخدام صيغة array destructuring في PHP 7.1 وما بعده لاستخراج القيم.
5-19
إرجاع عدة قيم باستخدام كائن: تعريف فئة Point ودالة تقوم بإنشاء وإرجاع كائن من هذه الفئة.
21-22
استخدام الكائن المرجع للوصول إلى خصائصه المختلفة.

إعلان نوع الإرجاع (Return Type Declarations)

بدءًا من PHP 7.0، يمكن تحديد نوع القيمة المرجعة من الدالة:

<?php
// إعلان نوع الإرجاع
function sum(int $a, int $b): int {
    return $a + $b;
}

// إرجاع نص
function greet(string $name): string {
    return "مرحباً " . $name;
}

// إرجاع مصفوفة
function getNumbers(): array {
    return [1, 2, 3, 4, 5];
}

// إرجاع كائن
function createUser(string $name): stdClass {
    $user = new stdClass();
    $user->name = $name;
    $user->created_at = date('Y-m-d H:i:s');
    return $user;
}

// إرجاع قيمة منطقية
function isValidEmail(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

// استخدام void للدوال التي لا ترجع قيمة (PHP 7.1+)
function logMessage(string $message): void {
    echo "[" . date('Y-m-d H:i:s') . "] " . $message . "
"; // لا حاجة لـ return هنا } // استخدام ?Type للإشارة إلى أن الدالة قد ترجع null (PHP 7.1+) function findUser(int $id): ?stdClass { if ($id <= 0) { return null; } $user = new stdClass(); $user->id = $id; $user->name = "مستخدم " . $id; return $user; } ?>
2-5
تحديد نوع الإرجاع int للدالة التي ترجع قيمة عددية صحيحة.
7-20
أمثلة على دوال مع أنواع إرجاع مختلفة: string، array، stdClass.
22-25
دالة ترجع قيمة منطقية (bool) للتحقق من صحة البريد الإلكتروني.
27-31
استخدام void للدوال التي لا ترجع أي قيمة (متاح في PHP 7.1 وما بعده).
33-43
استخدام ?Type للدوال التي قد ترجع null أو نوعًا معينًا (متاح في PHP 7.1 وما بعده).

نصائح سريعة

  • استخدم return؛ (بدون قيمة) للخروج من الدالة دون إرجاع قيمة.
  • دائمًا ارجع نفس نوع البيانات للحفاظ على اتساق الكود.
  • استخدم إعلانات نوع الإرجاع عندما تستخدم PHP 7.0 أو أحدث لتحسين قابلية الكود للفهم والصيانة.
  • لإرجاع قيم متعددة، استخدم المصفوفات أو الكائنات.
  • يمكن استخدام الدالة في أي مكان ضمن جملة أخرى طالما أنها ترجع القيمة المتوقعة.

نطاق المتغيرات

نطاق المتغير (Variable Scope) هو المنطقة في الكود حيث يمكن الوصول إلى المتغير. في PHP، هناك نطاقات مختلفة للمتغيرات.

النطاق المحلي والعام

هناك نوعان رئيسيان من نطاقات المتغيرات في PHP:

<?php
// متغير عام (global)
$globalVar = "أنا متغير عام";

function testScope() {
    // متغير محلي (local)
    $localVar = "أنا متغير محلي";
    
    echo "داخل الدالة:
"; echo "المتغير المحلي: " . $localVar . "
"; // محاولة الوصول إلى المتغير العام مباشرة echo "المتغير العام: " . $globalVar . "
"; // خطأ: المتغير غير معرف } testScope(); // محاولة الوصول إلى المتغير المحلي خارج الدالة echo "خارج الدالة:
"; echo "المتغير العام: " . $globalVar . "
"; echo "المتغير المحلي: " . $localVar . "
"; // خطأ: المتغير غير معرف ?>
2-3
تعريف متغير عام (global) في النطاق الرئيسي للبرنامج.
5-13
تعريف دالة مع متغير محلي (local). محاولة الوصول إلى المتغير العام داخل الدالة ستفشل لأنه ليس متاحًا في هذا النطاق.
17-20
محاولة الوصول إلى المتغير العام خارج الدالة تنجح، لكن محاولة الوصول إلى المتغير المحلي ستفشل لأنه غير متاح خارج الدالة.

استخدام الكلمة المفتاحية global

يمكن استخدام الكلمة المفتاحية global للوصول إلى المتغيرات العامة داخل الدوال:

<?php
$name = "أحمد";
$age = 30;

function updateUser() {
    global $name, $age; // إعلان المتغيرات كعامة
    
    // يمكن الآن الوصول إلى المتغيرات العامة وتعديلها
    $name = "محمد";
    $age = 35;
    
    echo "داخل الدالة: " . $name . ", " . $age . "
"; } echo "قبل استدعاء الدالة: " . $name . ", " . $age . "
"; updateUser(); echo "بعد استدعاء الدالة: " . $name . ", " . $age . "
"; // النتيجة: // قبل استدعاء الدالة: أحمد, 30 // داخل الدالة: محمد, 35 // بعد استدعاء الدالة: محمد, 35 ?>
2-3
تعريف متغيرات عامة في النطاق الرئيسي.
5-12
استخدام الكلمة المفتاحية global للإشارة إلى أننا نريد استخدام المتغيرات العامة داخل الدالة.
14-22
تغييرات المتغيرات داخل الدالة تؤثر على القيم العامة لأننا استخدمنا global.

استخدام مصفوفة $GLOBALS

طريقة أخرى للوصول إلى المتغيرات العامة هي استخدام مصفوفة $GLOBALS المدمجة:

<?php
$counter = 0;

function increment() {
    // استخدام مصفوفة $GLOBALS للوصول إلى المتغير العام
    $GLOBALS['counter']++;
    
    echo "القيمة داخل الدالة: " . $GLOBALS['counter'] . "
"; } echo "القيمة قبل استدعاء الدالة: " . $counter . "
"; increment(); increment(); echo "القيمة بعد استدعاء الدالة: " . $counter . "
"; // النتيجة: // القيمة قبل استدعاء الدالة: 0 // القيمة داخل الدالة: 1 // القيمة داخل الدالة: 2 // القيمة بعد استدعاء الدالة: 2 ?>
2
تعريف متغير عام $counter.
4-9
استخدام مصفوفة $GLOBALS للوصول إلى المتغير العام وتعديله داخل الدالة.
11-20
استدعاء الدالة مرتين وإظهار تأثير ذلك على المتغير العام.

المتغيرات الثابتة (Static Variables)

المتغيرات الثابتة تحتفظ بقيمتها بين استدعاءات الدالة المتعددة:

<?php
function counter() {
    // متغير محلي عادي - يتم إعادة تهيئته في كل استدعاء
    $normalCounter = 0;
    $normalCounter++;
    
    // متغير ثابت - يحتفظ بقيمته بين الاستدعاءات
    static $staticCounter = 0;
    $staticCounter++;
    
    echo "العداد العادي: " . $normalCounter . "
"; echo "العداد الثابت: " . $staticCounter . "
"; } counter(); counter(); counter(); // النتيجة: // العداد العادي: 1 // العداد الثابت: 1 // العداد العادي: 1 // العداد الثابت: 2 // العداد العادي: 1 // العداد الثابت: 3 ?>
2-12
تعريف دالة تستخدم متغيرين: متغير محلي عادي ومتغير ثابت (static).
3-5
المتغير المحلي العادي يتم إعادة تهيئته في كل مرة يتم فيها استدعاء الدالة.
7-9
المتغير الثابت (static) يتم تهيئته مرة واحدة فقط عند أول استدعاء للدالة، ثم يحتفظ بقيمته بين الاستدعاءات.
14-25
استدعاء الدالة ثلاث مرات يظهر أن المتغير العادي يظل دائمًا 1، بينما يزيد المتغير الثابت مع كل استدعاء.

نصائح سريعة

  • تجنب استخدام المتغيرات العامة حيثما أمكن لتقليل التداخل بين أجزاء البرنامج.
  • استخدم وسائط الدوال لتمرير البيانات إلى داخل الدالة والقيم المرجعة لإخراج البيانات.
  • استخدم المتغيرات الثابتة (static) عندما تحتاج إلى الاحتفاظ بالحالة بين استدعاءات الدالة.
  • لا يمكن تعريف المتغيرات الثابتة باستخدام تعبيرات متغيرة، يجب أن تكون القيم ثابتة.
  • كن حذرًا عند استخدام global لأنه قد يجعل من الصعب تتبع من يقوم بتغيير المتغيرات وأين.

الدوال المجهولة والإغلاقات

الدوال المجهولة (Anonymous Functions) والإغلاقات (Closures) هي دوال بدون اسم يمكن تخزينها في متغيرات أو تمريرها كوسائط إلى دوال أخرى.

الدوال المجهولة الأساسية

الدوال المجهولة هي دوال بدون اسم:

<?php
// تعريف دالة مجهولة وتخزينها في متغير
$greet = function($name) {
    return "مرحباً يا " . $name;
};

// استدعاء الدالة المجهولة
echo $greet("أحمد") . "
"; // مرحباً يا أحمد // استخدام الدوال المجهولة كوسائط function executeCallback($callback, $value) { return $callback($value); } $square = function($number) { return $number * $number; }; $double = function($number) { return $number * 2; }; echo executeCallback($square, 5) . "
"; // 25 echo executeCallback($double, 5) . "
"; // 10 // دوال مجهولة كقيم مرجعة function getProcessor($type) { if ($type === "مربع") { return function($number) { return $number * $number; }; } else { return function($number) { return $number * 2; }; } } $processor = getProcessor("مربع"); echo $processor(4) . "
"; // 16 ?>
2-6
تعريف دالة مجهولة وتخزينها في متغير $greet.
8-9
استدعاء الدالة المجهولة كما لو كانت دالة عادية.
11-22
تمرير الدوال المجهولة كوسائط إلى دالة أخرى.
24-34
إرجاع دوال مجهولة من دالة أخرى.
36-37
الحصول على دالة مجهولة واستدعائها.

الإغلاقات (Closures) وuse

الإغلاقات هي دوال مجهولة يمكنها الوصول إلى متغيرات من النطاق المحيط باستخدام التعبير use:

<?php
$message = "مرحباً، ";

// الدالة المجهولة العادية لا يمكنها الوصول إلى $message
$greet1 = function($name) {
    return $message . $name; // خطأ: $message غير معرف
};

// استخدام use للوصول إلى المتغيرات من النطاق المحيط
$greet2 = function($name) use ($message) {
    return $message . $name; // صحيح
};

echo $greet2("أحمد") . "
"; // مرحباً، أحمد // يتم نسخ قيمة المتغير وقت تعريف الإغلاق $message = "أهلاً، "; echo $greet2("محمد") . "
"; // ما زال: مرحباً، محمد // للحصول على القيمة المحدثة، استخدم التمرير بالمرجع $counter = 0; $incrementer = function() use (&$counter) { return ++$counter; }; echo $incrementer() . "
"; // 1 echo $incrementer() . "
"; // 2 echo "قيمة المتغير الخارجي: " . $counter . "
"; // 2 ?>
2-7
محاولة الوصول إلى متغير من النطاق المحيط دون استخدام use ستؤدي إلى خطأ.
9-12
استخدام use للوصول إلى المتغير $message من النطاق المحيط.
16-17
تغيير قيمة المتغير الخارجي لا يؤثر على الإغلاق، لأن القيمة يتم نسخها وقت تعريف الإغلاق.
19-26
استخدام التمرير بالمرجع (&) للوصول إلى المتغير الخارجي وتعديله.

استخدامات الدوال المجهولة

الدوال المجهولة مفيدة في العديد من الحالات، وخاصة مع دوال معالجة المصفوفات:

<?php
$numbers = [1, 2, 3, 4, 5];

// استخدام دالة مجهولة مع array_map
$squared = array_map(function($n) {
    return $n * $n;
}, $numbers);

echo "المصفوفة الأصلية: " . implode(", ", $numbers) . "
"; echo "المصفوفة المربعة: " . implode(", ", $squared) . "
"; // استخدام دالة مجهولة مع array_filter $evenNumbers = array_filter($numbers, function($n) { return $n % 2 == 0; }); echo "الأرقام الزوجية: " . implode(", ", $evenNumbers) . "
"; // استخدام دالة مجهولة مع usort $users = [ ['name' => 'أحمد', 'age' => 30], ['name' => 'محمد', 'age' => 25], ['name' => 'سارة', 'age' => 28] ]; usort($users, function($a, $b) { return $a['age'] - $b['age']; // ترتيب تصاعدي حسب العمر }); echo "المستخدمون مرتبين حسب العمر:
"; foreach ($users as $user) { echo $user['name'] . " - " . $user['age'] . " سنة
"; } ?>
2-9
استخدام دالة مجهولة مع array_map لتطبيق عملية على كل عنصر في المصفوفة.
11-15
استخدام دالة مجهولة مع array_filter لتصفية المصفوفة بناءً على شرط معين.
17-30
استخدام دالة مجهولة مع usort لفرز مصفوفة مخصصة.

الدوال السهمية (Arrow Functions)

بدءًا من PHP 7.4، يمكن استخدام الدوال السهمية (Arrow Functions) كبديل مختصر للدوال المجهولة:

<?php
// الدالة المجهولة التقليدية
$square = function($number) {
    return $number * $number;
};

// الدالة السهمية المكافئة (PHP 7.4+)
$square = fn($number) => $number * $number;

// مثال على استخدام الدوال السهمية مع array_map
$numbers = [1, 2, 3, 4, 5];
$squared = array_map(fn($n) => $n * $n, $numbers);

echo "المصفوفة المربعة: " . implode(", ", $squared) . "
"; // الدوال السهمية تلتقط المتغيرات من النطاق المحيط تلقائيًا $factor = 2; $multiplied = array_map(fn($n) => $n * $factor, $numbers); echo "المصفوفة المضروبة: " . implode(", ", $multiplied) . "
"; // يمكن استخدام الدوال السهمية في التعبيرات المعقدة $users = [ ['name' => 'أحمد', 'age' => 30], ['name' => 'محمد', 'age' => 25], ['name' => 'سارة', 'age' => 28] ]; $names = array_map(fn($user) => $user['name'], $users); echo "أسماء المستخدمين: " . implode(", ", $names) . "
"; ?>
2-9
مقارنة بين الدالة المجهولة التقليدية والدالة السهمية المختصرة.
11-14
استخدام الدالة السهمية مع array_map.
16-19
الدوال السهمية تلتقط المتغيرات من النطاق المحيط تلقائيًا دون الحاجة إلى use.
21-28
استخدام الدوال السهمية مع مصفوفات الكائنات.

نصائح سريعة

  • استخدم الدوال المجهولة عندما تحتاج إلى دالة لاستخدام مرة واحدة أو لفترة قصيرة.
  • استخدم use للوصول إلى المتغيرات الخارجية في الدوال المجهولة.
  • استخدم الدوال السهمية (PHP 7.4+) للحصول على صيغة أكثر إيجازًا وسهولة في القراءة.
  • الدوال السهمية تلتقط المتغيرات الخارجية تلقائيًا بينما تتطلب الدوال المجهولة التقليدية استخدام use.
  • الدوال السهمية محدودة بتعبير واحد فقط ولا يمكنها استخدام عبارات متعددة مثل الدوال المجهولة التقليدية.
  • يمكن استخدام الدوال المجهولة والسهمية بشكل فعال مع دوال معالجة المصفوفات مثل array_map و array_filter و usort.

المصفوفات

المصفوفات هي نوع من أنواع البيانات في PHP تسمح بتخزين مجموعة من القيم في متغير واحد. تعتبر المصفوفات من أكثر أنواع البيانات استخدامًا في PHP نظرًا لمرونتها وقوتها. يمكن استخدام المصفوفات لتخزين قوائم بسيطة من القيم، أو بيانات معقدة متعددة الأبعاد، أو حتى مجموعات من البيانات المنظمة بزوج المفتاح والقيمة.

إنشاء المصفوفات

هناك عدة طرق لإنشاء المصفوفات في PHP. دعنا نستكشف الطرق المختلفة وأنواع المصفوفات.

إنشاء المصفوفات العددية

المصفوفات العددية تستخدم أرقامًا متسلسلة كمفاتيح (تبدأ من 0):

<?php
// الطريقة 1: استخدام الدالة array()
$fruits = array("تفاح", "موز", "برتقال");

// الطريقة 2: استخدام الصيغة المختصرة [] (متاح منذ PHP 5.4)
$colors = ["أحمر", "أخضر", "أزرق"];

// إنشاء مصفوفة فارغة ثم إضافة عناصر
$numbers = [];
$numbers[] = 10;  // إضافة عنصر في نهاية المصفوفة
$numbers[] = 20;
$numbers[] = 30;

// الوصول إلى عناصر المصفوفة باستخدام الفهرس
echo $fruits[0] . "
"; // تفاح echo $colors[1] . "
"; // أخضر echo $numbers[2] . "
"; // 30 // التحقق من طول المصفوفة echo "عدد الفواكه: " . count($fruits) . "
"; // التكرار على المصفوفة foreach ($colors as $color) { echo "اللون: " . $color . "
"; } // التكرار مع الفهرس foreach ($numbers as $index => $value) { echo "العنصر " . $index . ": " . $value . "
"; } ?>
2-3
إنشاء مصفوفة باستخدام الدالة array().
5-6
إنشاء مصفوفة باستخدام الصيغة المختصرة [] (متاح منذ PHP 5.4).
8-12
إنشاء مصفوفة فارغة ثم إضافة عناصر باستخدام عامل [].
14-17
الوصول إلى عناصر المصفوفة باستخدام الفهرس (يبدأ من 0).
19-20
استخدام الدالة count() للحصول على عدد العناصر في المصفوفة.
22-30
التكرار على عناصر المصفوفة باستخدام foreach، مع أو بدون الفهرس.

إنشاء المصفوفات الترابطية

المصفوفات الترابطية تستخدم سلاسل نصية كمفاتيح بدلاً من الأرقام:

<?php
// إنشاء مصفوفة ترابطية
$person = array(
    "name" => "أحمد",
    "age" => 30,
    "city" => "القاهرة"
);

// الصيغة المختصرة
$car = [
    "brand" => "تويوتا",
    "model" => "كورولا",
    "year" => 2020
];

// إضافة عناصر جديدة
$person["email"] = "[email protected]";
$car["color"] = "أبيض";

// الوصول إلى العناصر
echo $person["name"] . "
"; // أحمد echo $car["model"] . "
"; // كورولا // التكرار على المصفوفة الترابطية foreach ($person as $key => $value) { echo $key . ": " . $value . "
"; } // فحص وجود مفتاح if (array_key_exists("email", $person)) { echo "البريد الإلكتروني: " . $person["email"] . "
"; } // فحص وجود قيمة if (in_array("القاهرة", $person)) { echo "يعيش في القاهرة
"; } // الحصول على مفاتيح المصفوفة $keys = array_keys($person); echo "المفاتيح: " . implode(", ", $keys) . "
"; // الحصول على قيم المصفوفة $values = array_values($person); echo "القيم: " . implode(", ", $values) . "
"; ?>
2-7
إنشاء مصفوفة ترابطية باستخدام الدالة array() مع تحديد المفاتيح والقيم.
9-14
إنشاء مصفوفة ترابطية باستخدام الصيغة المختصرة [].
16-18
إضافة عناصر جديدة إلى المصفوفات الترابطية بتحديد المفتاح.
20-22
الوصول إلى عناصر المصفوفة الترابطية باستخدام المفتاح.
24-27
التكرار على المصفوفة الترابطية للحصول على المفتاح والقيمة.
29-38
استخدام array_key_exists() للتحقق من وجود مفتاح، وin_array() للتحقق من وجود قيمة.
40-45
استخدام array_keys() وarray_values() للحصول على مفاتيح وقيم المصفوفة.

مصفوفات مختلطة

يمكن أن تحتوي المصفوفات في PHP على مزيج من المفاتيح العددية والنصية:

<?php
// مصفوفة تجمع بين المفاتيح العددية والنصية
$mixed = [
    "name" => "محمد",
    42 => "الإجابة",
    "colors" => ["أحمر", "أخضر"],
    99
];

// ملاحظة: بعد 99، الفهرس التالي سيكون 100
$mixed[] = "عنصر جديد";  // سيكون المفتاح 100

// طباعة المصفوفة
print_r($mixed);

/*
النتيجة:
Array
(
    [name] => محمد
    [42] => الإجابة
    [colors] => Array
        (
            [0] => أحمر
            [1] => أخضر
        )
    [43] => 99
    [100] => عنصر جديد
)
*/
?>
2-8
إنشاء مصفوفة مختلطة تجمع بين المفاتيح العددية والنصية وحتى مصفوفات كقيم.
10-11
عند إضافة عنصر جديد بدون تحديد مفتاح، يتم استخدام أكبر مفتاح عددي موجود + 1.
13-28
استخدام الدالة print_r() لعرض بنية المصفوفة بشكل مفصل.

نصائح سريعة

  • استخدم الصيغة المختصرة [] بدلاً من array() للقراءة الأفضل (إذا كنت تستخدم PHP 5.4 أو أحدث).
  • لا تنس أن المفاتيح العددية في PHP تبدأ من 0.
  • تحافظ PHP على ترتيب إدراج العناصر في المصفوفات الترابطية منذ PHP 7.0+.
  • استخدم isset() للتحقق من وجود مفتاح وقيمته ليست NULL، وarray_key_exists() للتحقق من وجود المفتاح فقط.
  • يمكن استخدام var_dump() أو print_r() لعرض محتويات المصفوفة للتصحيح.

التعامل مع المصفوفات

توفر PHP العديد من الدوال المدمجة للتعامل مع المصفوفات، مما يسمح لك بإجراء عمليات مختلفة بسهولة.

إضافة وإزالة العناصر

يمكن إضافة وإزالة العناصر من المصفوفات بعدة طرق:

<?php
// إنشاء مصفوفة
$fruits = ["تفاح", "موز", "برتقال"];

// إضافة عناصر في نهاية المصفوفة
$fruits[] = "عنب";
array_push($fruits, "فراولة", "كيوي");  // إضافة عدة عناصر

// إضافة عناصر في بداية المصفوفة
array_unshift($fruits, "مانجو", "أناناس");

echo "المصفوفة بعد الإضافة: " . implode(", ", $fruits) . "
"; // مانجو, أناناس, تفاح, موز, برتقال, عنب, فراولة, كيوي // إزالة آخر عنصر $last = array_pop($fruits); echo "آخر عنصر: " . $last . "
"; // كيوي // إزالة أول عنصر $first = array_shift($fruits); echo "أول عنصر: " . $first . "
"; // مانجو echo "المصفوفة بعد الإزالة: " . implode(", ", $fruits) . "
"; // أناناس, تفاح, موز, برتقال, عنب, فراولة // إزالة عنصر بواسطة الفهرس unset($fruits[2]); // إزالة "موز" // ملاحظة: unset لا يعيد ترتيب المفاتيح echo "المصفوفة بعد unset: "; print_r($fruits); // Array ( [0] => أناناس [1] => تفاح [3] => برتقال [4] => عنب [5] => فراولة ) // إعادة ترتيب المفاتيح $fruits = array_values($fruits); echo "المصفوفة بعد إعادة الترتيب: "; print_r($fruits); // Array ( [0] => أناناس [1] => تفاح [2] => برتقال [3] => عنب [4] => فراولة ) ?>
2-3
إنشاء مصفوفة بسيطة للتجربة.
5-7
إضافة عناصر في نهاية المصفوفة باستخدام [] وarray_push().
9-10
إضافة عناصر في بداية المصفوفة باستخدام array_unshift().
14-21
إزالة عناصر من بداية ونهاية المصفوفة باستخدام array_pop() وarray_shift().
25-32
استخدام unset() لإزالة عنصر بواسطة الفهرس. لاحظ أن unset لا يعيد ترتيب المفاتيح.
34-38
استخدام array_values() لإعادة ترتيب المفاتيح بعد إزالة العناصر.

دمج وتقسيم المصفوفات

يمكن دمج وتقسيم المصفوفات بعدة طرق مختلفة:

<?php
// دمج مصفوفتين
$fruits = ["تفاح", "موز"];
$vegetables = ["جزر", "طماطم"];

// الطريقة 1: استخدام عامل +
$foods = $fruits + $vegetables;
print_r($foods);
// Array ( [0] => تفاح [1] => موز )
// ملاحظة: + يحتفظ فقط بالعناصر ذات المفاتيح الفريدة، والمفاتيح المكررة من المصفوفة الثانية يتم تجاهلها

// الطريقة 2: استخدام array_merge
$foods = array_merge($fruits, $vegetables);
print_r($foods);
// Array ( [0] => تفاح [1] => موز [2] => جزر [3] => طماطم )

// دمج مصفوفات ترابطية
$person1 = ["name" => "أحمد", "age" => 25];
$person2 = ["name" => "محمد", "email" => "[email protected]"];

// باستخدام +، القيم المتكررة من المصفوفة الأولى تبقى
$merged1 = $person1 + $person2;
print_r($merged1);
// Array ( [name] => أحمد [age] => 25 [email] => [email protected] )

// باستخدام array_merge، القيم المتكررة من المصفوفة الثانية تحل محل الأولى
$merged2 = array_merge($person1, $person2);
print_r($merged2);
// Array ( [name] => محمد [age] => 25 [email] => [email protected] )

// تقسيم المصفوفة
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// الحصول على جزء من المصفوفة
$subset = array_slice($numbers, 3, 4);  // البداية: 3، العدد: 4
print_r($subset);
// Array ( [0] => 4 [1] => 5 [2] => 6 [3] => 7 )

// الحصول على جزء مع الحفاظ على المفاتيح
$subset = array_slice($numbers, 3, 4, true);
print_r($subset);
// Array ( [3] => 4 [4] => 5 [5] => 6 [6] => 7 )

// تقسيم المصفوفة إلى أجزاء أصغر
$chunks = array_chunk($numbers, 3);
print_r($chunks);
// Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [1] => Array ( [0] => 4 [1] => 5 [2] => 6 ) [2] => Array ( [0] => 7 [1] => 8 [2] => 9 ) [3] => Array ( [0] => 10 ) )
?>
2-4
إنشاء مصفوفتين بسيطتين للتجربة.
6-9
استخدام عامل + لدمج المصفوفات. لاحظ أنه يحتفظ فقط بالعناصر ذات المفاتيح الفريدة.
11-13
استخدام array_merge() لدمج المصفوفات، وهذا يحتفظ بجميع العناصر ويعيد ترتيب المفاتيح العددية.
15-25
مقارنة بين + وarray_merge() عند دمج المصفوفات الترابطية.
27-40
استخدام array_slice() للحصول على جزء من المصفوفة، مع خيار الحفاظ على المفاتيح الأصلية.
42-45
استخدام array_chunk() لتقسيم المصفوفة إلى مجموعات أصغر بحجم محدد.

تحويل المصفوفات

يمكن إجراء تحويلات مختلفة على المصفوفات:

<?php
// عكس المصفوفة
$numbers = [1, 2, 3, 4, 5];
$reversed = array_reverse($numbers);
print_r($reversed);  // Array ( [0] => 5 [1] => 4 [2] => 3 [3] => 2 [4] => 1 )

// عكس المصفوفة مع الحفاظ على المفاتيح
$reversed = array_reverse($numbers, true);
print_r($reversed);  // Array ( [4] => 5 [3] => 4 [2] => 3 [1] => 2 [0] => 1 )

// تبديل المفاتيح والقيم
$countries = ["EG" => "مصر", "SA" => "السعودية", "AE" => "الإمارات"];
$codes = array_flip($countries);
print_r($codes);  // Array ( [مصر] => EG [السعودية] => SA [الإمارات] => AE )

// استخراج القيم فقط
$values = array_values($countries);
print_r($values);  // Array ( [0] => مصر [1] => السعودية [2] => الإمارات )

// استخراج المفاتيح فقط
$keys = array_keys($countries);
print_r($keys);  // Array ( [0] => EG [1] => SA [2] => AE )

// ملء المصفوفة بقيمة
$filled = array_fill(0, 5, "قيمة");
print_r($filled);  // Array ( [0] => قيمة [1] => قيمة [2] => قيمة [3] => قيمة [4] => قيمة )

// إنشاء مصفوفة ترابطية من مصفوفتين
$keys = ["name", "age", "city"];
$values = ["علي", 28, "دبي"];
$person = array_combine($keys, $values);
print_r($person);  // Array ( [name] => علي [age] => 28 [city] => دبي )

// تكرار عناصر المصفوفة
$array = [1, 2];
$repeated = array_repeat($array, 3);
print_r($repeated);  // Array ( [0] => 1 [1] => 2 [2] => 1 [3] => 2 [4] => 1 [5] => 2 )
?>
2-8
استخدام array_reverse() لعكس ترتيب عناصر المصفوفة، مع خيار الحفاظ على المفاتيح.
10-13
استخدام array_flip() لتبديل المفاتيح والقيم في المصفوفة.
15-21
استخدام array_values() وarray_keys() لاستخراج القيم والمفاتيح كمصفوفات منفصلة.
23-25
استخدام array_fill() لإنشاء مصفوفة وملئها بقيمة معينة.
27-31
استخدام array_combine() لإنشاء مصفوفة ترابطية من مصفوفتين: واحدة للمفاتيح والأخرى للقيم.
33-36
استخدام array_repeat() لتكرار عناصر المصفوفة عدة مرات.

نصائح سريعة

  • استخدم array_push() عندما تريد إضافة عدة عناصر دفعة واحدة، واستخدم $array[] = لإضافة عنصر واحد.
  • عند إزالة عناصر باستخدام unset()، تذكر أن المفاتيح لن يتم إعادة ترتيبها تلقائيًا.
  • استخدم array_merge() بدلاً من عامل + للدمج إلا إذا كنت تريد تحديدًا سلوك عامل +.
  • لدمج أكثر من مصفوفتين، يمكنك تمرير مصفوفات متعددة إلى array_merge().
  • تحقق دائمًا من أن المصفوفات المستخدمة مع دوال مثل array_combine() لها نفس الطول، وإلا فستحصل على أخطاء.

فرز المصفوفات

توفر PHP العديد من الدوال لفرز المصفوفات بطرق مختلفة. يمكن فرز المصفوفات حسب القيمة أو المفتاح، بترتيب تصاعدي أو تنازلي.

دوال الفرز الأساسية

هناك دوال متعددة للفرز بطرق مختلفة:

<?php
// فرز المصفوفة تصاعديًا (حسب القيمة)
$numbers = [5, 3, 8, 2, 1, 9];
sort($numbers);
echo "بعد sort(): " . implode(", ", $numbers) . "
"; // 1, 2, 3, 5, 8, 9 // فرز المصفوفة تنازليًا (حسب القيمة) $numbers = [5, 3, 8, 2, 1, 9]; rsort($numbers); echo "بعد rsort(): " . implode(", ", $numbers) . "
"; // 9, 8, 5, 3, 2, 1 // فرز المصفوفة الترابطية تصاعديًا (حسب القيمة، مع الحفاظ على العلاقة بين المفاتيح والقيم) $fruits = [ "d" => "برتقال", "a" => "تفاح", "b" => "موز", "c" => "عنب" ]; asort($fruits); echo "بعد asort(): "; print_r($fruits); // Array ( [a] => تفاح [b] => موز [d] => برتقال [c] => عنب ) // فرز المصفوفة الترابطية تنازليًا (حسب القيمة، مع الحفاظ على العلاقة بين المفاتيح والقيم) arsort($fruits); echo "بعد arsort(): "; print_r($fruits); // Array قال [d] => برتقال [c] => عنب ) // فرز المصفوفة الترابطية تصاعديًا (حسب المفتاح) ksort($fruits); echo "بعد ksort(): "; print_r($fruits); // Array ( [a] => تفاح [b] => موز [c] => عنب [d] => برتقال ) // فرز المصفوفة الترابطية تنازليًا (حسب المفتاح) krsort($fruits); echo "بعد krsort(): "; print_r($fruits); // Array ( [d] => برتقال [c] => عنب [b] => موز [a] => تفاح ) // فرز المصفوفة بشكل طبيعي (يعامل الأرقام في النصوص كأرقام) $files = ["file1.txt", "file10.txt", "file2.txt"]; sort($files); // الفرز الأبجدي العادي echo "الفرز الأبجدي العادي: " . implode(", ", $files) . "
"; // file1.txt, file10.txt, file2.txt natsort($files); // الفرز الطبيعي echo "الفرز الطبيعي: " . implode(", ", $files) . "
"; // file1.txt, file2.txt, file10.txt ?>
2-6
استخدام sort() لفرز المصفوفة تصاعديًا حسب القيمة. يعيد ترتيب المفاتيح.
8-12
استخدام rsort() لفرز المصفوفة تنازليًا حسب القيمة. يعيد ترتيب المفاتيح.
14-24
استخدام asort() لفرز المصفوفة الترابطية تصاعديًا حسب القيمة، مع الحفاظ على العلاقة بين المفاتيح والقيم.
26-30
استخدام arsort() لفرز المصفوفة الترابطية تنازليًا حسب القيمة، مع الحفاظ على العلاقة بين المفاتيح والقيم.
32-40
استخدام ksort() وkrsort() لفرز المصفوفات الترابطية حسب المفتاح بدلاً من القيمة.
42-49
مقارنة بين sort() وnatsort() لإظهار الفرق بين الفرز الأبجدي العادي والفرز الطبيعي الذي يتعامل مع الأرقام في النصوص بشكل منطقي.

دوال الفرز المخصصة

يمكن استخدام دوال مخصصة للفرز عندما نحتاج إلى معايير فرز معقدة:

<?php
// مثال على usort() - فرز مصفوفة باستخدام دالة مقارنة مخصصة
$fruits = ["تفاح", "موز", "برتقال", "عنب"];

// فرز حسب طول النص
usort($fruits, function($a, $b) {
    return mb_strlen($a) - mb_strlen($b);
});

echo "فرز حسب الطول: ";
print_r($fruits);
// Array ( [0] => عنب [1] => موز [2] => تفاح [3] => برتقال )

// مثال على uasort() - فرز مصفوفة ترابطية مع الحفاظ على المفاتيح
$people = [
    "عمر" => ["العمر" => 25, "الطول" => 180],
    "سارة" => ["العمر" => 22, "الطول" => 165],
    "أحمد" => ["العمر" => 30, "الطول" => 175]
];

// فرز حسب العمر
uasort($people, function($a, $b) {
    return $a["العمر"] - $b["العمر"];
});

echo "فرز حسب العمر: ";
print_r($people);
// Array ( [سارة] => Array ( [العمر] => 22 [الطول] => 165 ) [عمر] => Array ( [العمر] => 25 [الطول] => 180 ) [أحمد] => Array ( [العمر] => 30 [الطول] => 175 ) )

// مثال على uksort() - فرز حسب المفاتيح باستخدام دالة مقارنة مخصصة
$scores = [
    "محمد علي" => 85,
    "أحمد محمد" => 92,
    "علي أحمد" => 78
];

// فرز حسب الاسم الأخير ثم الأول
uksort($scores, function($a, $b) {
    $a_parts = explode(" ", $a);
    $b_parts = explode(" ", $b);
    
    // مقارنة الاسم الأخير أولاً
    $last_name_cmp = strcmp($a_parts[1], $b_parts[1]);
    if ($last_name_cmp != 0) {
        return $last_name_cmp;
    }
    
    // إذا كان الاسم الأخير متساويًا، قارن الاسم الأول
    return strcmp($a_parts[0], $b_parts[0]);
});

echo "فرز حسب الاسم: ";
print_r($scores);
// Array ( [علي أحمد] => 78 [أحمد محمد] => 92 [محمد علي] => 85 )

// مثال على array_multisort() - فرز مصفوفات متعددة في وقت واحد
$names = ["محمد", "أحمد", "علي"];
$ages = [30, 25, 28];

// فرز الأسماء ثم الأعمار بناءً على ترتيب الأسماء
array_multisort($names, SORT_ASC, $ages);

echo "بعد array_multisort():
"; echo "الأسماء: " . implode(", ", $names) . "
"; echo "الأعمار: " . implode(", ", $ages) . "
"; // الأسماء: أحمد, علي, محمد // الأعمار: 25, 28, 30 ?>
2-11
استخدام usort() لفرز مصفوفة باستخدام دالة مقارنة مخصصة. في هذا المثال، نقوم بفرز الفواكه حسب طول النص.
13-26
استخدام uasort() لفرز مصفوفة ترابطية مع الحفاظ على المفاتيح. في هذا المثال، نقوم بفرز الأشخاص حسب العمر.
28-53
استخدام uksort() لفرز مصفوفة حسب المفاتيح باستخدام دالة مقارنة مخصصة. في هذا المثال، نقوم بفرز حسب الاسم الأخير ثم الاسم الأول.
55-65
استخدام array_multisort() لفرز مصفوفات متعددة في وقت واحد. المصفوفات الإضافية يتم فرزها بنفس ترتيب المصفوفة الأولى.

نصائح سريعة

  • استخدم sort() وrsort() للمصفوفات العادية و asort() وarsort() للمصفوفات الترابطية عندما تريد الفرز حسب القيمة.
  • استخدم ksort() وkrsort() عندما تريد فرز المصفوفات الترابطية حسب المفتاح.
  • استخدم usort() وuasort() وuksort() عندما تحتاج إلى معايير فرز مخصصة.
  • دالة المقارنة المخصصة يجب أن ترجع قيمة سالبة إذا كان a < b، وصفر إذا كان a = b، وقيمة موجبة إذا كان a > b.
  • تذكر أن دوال الفرز (sort, rsort, usort) تعيد ترتيب المفاتيح، بينما دوال الفرز (asort, arsort, uasort, ksort, krsort, uksort) تحافظ على المفاتيح.

المصفوفات متعددة الأبعاد

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

إنشاء المصفوفات متعددة الأبعاد

يمكن إنشاء المصفوفات متعددة الأبعاد بعدة طرق:

<?php
// مصفوفة ثنائية الأبعاد (مصفوفة من المصفوفات)
$matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// الوصول إلى العناصر
echo $matrix[0][1] . "
"; // 2 echo $matrix[2][0] . "
"; // 7 // مصفوفة ثنائية الأبعاد مع مفاتيح مخصصة $students = [ "أحمد" => [ "age" => 20, "grade" => "A", "courses" => ["رياضيات", "فيزياء", "برمجة"] ], "سارة" => [ "age" => 22, "grade" => "B+", "courses" => ["إدارة", "اقتصاد", "محاسبة"] ] ]; // الوصول إلى العناصر echo $students["أحمد"]["age"] . "
"; // 20 echo $students["سارة"]["grade"] . "
"; // B+ echo $students["أحمد"]["courses"][2] . "
"; // برمجة // التكرار على مصفوفة ثنائية الأبعاد foreach ($matrix as $row) { foreach ($row as $element) { echo $element . " "; } echo "
"; } // 1 2 3 // 4 5 6 // 7 8 9 // التكرار على مصفوفة ترابطية متعددة الأبعاد foreach ($students as $name => $info) { echo "الطالب: " . $name . "
"; echo "العمر: " . $info["age"] . "
"; echo "الدرجة: " . $info["grade"] . "
"; echo "المواد: " . implode(", ", $info["courses"]) . "

"; } ?>
2-7
إنشاء مصفوفة ثنائية الأبعاد (مصفوفة من المصفوفات) تمثل مصفوفة رياضية 3×3.
9-11
الوصول إلى عناصر المصفوفة ثنائية الأبعاد باستخدام فهرسين متتاليين: الأول للصف والثاني للعمود.
13-25
إنشاء مصفوفة ثنائية الأبعاد أكثر تعقيدًا باستخدام مفاتيح ترابطية ومصفوفات متداخلة.
27-30
الوصول إلى العناصر في المصفوفة متعددة الأبعاد المعقدة باستخدام سلسلة من المفاتيح.
32-39
التكرار على مصفوفة ثنائية الأبعاد باستخدام حلقتي foreach متداخلتين.
41-47
التكرار على مصفوفة ترابطية متعددة الأبعاد، مع الوصول إلى المفاتيح والقيم المتداخلة.

معالجة المصفوفات متعددة الأبعاد

يمكن معالجة المصفوفات متعددة الأبعاد باستخدام مجموعة متنوعة من الأساليب:

<?php
// إنشاء جدول بيانات
$data = [
    ["الاسم" => "أحمد", "العمر" => 25, "المدينة" => "القاهرة"],
    ["الاسم" => "محمد", "العمر" => 30, "المدينة" => "الرياض"],
    ["الاسم" => "سارة", "العمر" => 28, "المدينة" => "دبي"],
    ["الاسم" => "فاطمة", "العمر" => 22, "المدينة" => "عمان"]
];

// استخراج عمود واحد من الجدول
$names = array_column($data, "الاسم");
echo "الأسماء: " . implode(", ", $names) . "
"; // الأسماء: أحمد, محمد, سارة, فاطمة // استخراج عمود مع استخدام عمود آخر كمفتاح $ages_by_name = array_column($data, "العمر", "الاسم"); print_r($ages_by_name); // Array ( [أحمد] => 25 [محمد] => 30 [سارة] => 28 [فاطمة] => 22 ) // البحث في مصفوفة متعددة الأبعاد function findByName($data, $name) { foreach ($data as $person) { if ($person["الاسم"] === $name) { return $person; } } return null; } $person = findByName($data, "سارة"); print_r($person); // Array ( [الاسم] => سارة [العمر] => 28 [المدينة] => دبي ) // فرز مصفوفة متعددة الأبعاد حسب عمود معين function sortByColumn($data, $column) { $sortCol = []; foreach ($data as $key => $row) { $sortCol[$key] = $row[$column]; } array_multisort($sortCol, SORT_ASC, $data); return $data; } // فرز حسب العمر $sorted_data = sortByColumn($data, "العمر"); echo "الأشخاص مرتبين حسب العمر:
"; foreach ($sorted_data as $person) { echo $person["الاسم"] . " - " . $person["العمر"] . " سنة
"; } // فاطمة - 22 سنة // أحمد - 25 سنة // سارة - 28 سنة // محمد - 30 سنة ?>
2-8
إنشاء مصفوفة متعددة الأبعاد تمثل جدول بيانات للأشخاص.
10-17
استخدام array_column() لاستخراج عمود واحد من الجدول، وإمكانية استخدام عمود آخر كمفتاح.
19-28
تنفيذ دالة للبحث في مصفوفة متعددة الأبعاد بناءً على قيمة معينة في أحد الأعمدة.
30-47
تنفيذ دالة لفرز مصفوفة متعددة الأبعاد حسب عمود معين باستخدام array_multisort().

نصائح سريعة

  • استخدم array_column() للحصول على عمود محدد من مصفوفة متعددة الأبعاد.
  • عند التعامل مع المصفوفات متعددة الأبعاد المعقدة، فكر في تقسيمها إلى وظائف مساعدة للبحث والفرز والمعالجة.
  • استخدم التكرار المتداخل (foreach داخل foreach) للوصول إلى جميع العناصر في المصفوفات متعددة الأبعاد.
  • يمكن استخدام دوال مثل json_encode() وjson_decode() لتحويل المصفوفات متعددة الأبعاد إلى سلاسل JSON والعكس.
  • للتعامل مع البيانات التي تشبه الجداول، فكر في استخدام مكتبات مثل PDO للتعامل مع قواعد البيانات بدلاً من المصفوفات المعقدة.

البرمجة كائنية التوجه

البرمجة كائنية التوجه (Object-Oriented Programming - OOP) هي نموذج برمجي يعتمد على مفهوم "الكائنات" التي يمكنها تخزين البيانات والسلوكيات. توفر PHP دعمًا كاملًا للبرمجة كائنية التوجه، مما يسمح بإنشاء تطبيقات قابلة للصيانة وإعادة الاستخدام والتوسع.

الفئات والكائنات

الفئة (Class) هي قالب أو مخطط يحدد خصائص وسلوكيات مجموعة من الكائنات المتشابهة. الكائن (Object) هو نسخة ملموسة من الفئة.

تعريف الفئات وإنشاء الكائنات

يمكننا تعريف فئة وإنشاء كائنات منها كما يلي:

<?php
// تعريف فئة User
class User {
    // خصائص (properties)
    public $name;
    public $email;
    public $isActive = true;  // قيمة افتراضية
    
    // الدالة البانية (constructor)
    public function __construct($name, $email) {
        $this->name = $name;
        $this->email = $email;
        echo "تم إنشاء مستخدم جديد: {$this->name}
"; } // طريقة (method) public function login() { if ($this->isActive) { echo "{$this->name} قام بتسجيل الدخول
"; return true; } else { echo "الحساب غير نشط!
"; return false; } } // طريقة أخرى public function getInfo() { return "الاسم: {$this->name}, البريد الإلكتروني: {$this->email}"; } // الدالة المدمرة (destructor) public function __destruct() { echo "تم تدمير كائن المستخدم {$this->name}
"; } } // إنشاء كائن من الفئة User $user1 = new User("أحمد علي", "[email protected]"); // الوصول إلى الخصائص echo $user1->name . "
"; // أحمد علي echo $user1->email . "
"; // [email protected] // تعديل خاصية $user1->isActive = false; // استدعاء الطرق $user1->login(); // الحساب غير نشط! echo $user1->getInfo() . "
"; // الاسم: أحمد علي, البريد الإلكتروني: [email protected] // إنشاء كائن آخر $user2 = new User("سارة محمد", "[email protected]"); $user2->login(); // سارة محمد قامت بتسجيل الدخول ?>
2-32
تعريف فئة User مع خصائص وطرق مختلفة، بما في ذلك الدالة البانية __construct والدالة المدمرة __destruct.
9-13
الدالة البانية تُستدعى تلقائيًا عند إنشاء كائن جديد. يتم استخدامها لتهيئة الخصائص الأساسية للكائن.
16-23
تعريف طريقة login لتنفيذ عملية تسجيل الدخول بناءً على حالة الحساب.
34-49
إنشاء كائنات من الفئة، والوصول إلى خصائصها واستدعاء طرقها.

دوال السحر (Magic Methods)

توفر PHP مجموعة من دوال السحر التي يمكن تعريفها داخل الفئات لتنفيذ سلوكيات خاصة:

<?php
class Product {
    private $data = [];
    
    // تسمح بإضافة خصائص ديناميكية
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
    
    // تسمح بالوصول إلى الخصائص الديناميكية
    public function __get($name) {
        if (isset($this->data[$name])) {
            return $this->data[$name];
        }
        return null;
    }
    
    // تتحقق من وجود خاصية
    public function __isset($name) {
        return isset($this->data[$name]);
    }
    
    // تمسح خاصية
    public function __unset($name) {
        unset($this->data[$name]);
    }
    
    // تُستدعى عند محاولة طباعة الكائن كنص
    public function __toString() {
        return "منتج: " . ($this->data['name'] ?? 'غير معروف');
    }
    
    // تُستدعى عند استدعاء الكائن كدالة
    public function __invoke($arg) {
        return "تم استدعاء الكائن كدالة مع الوسيط: $arg";
    }
    
    // تُستخدم للتعامل مع استنساخ الكائن
    public function __clone() {
        $this->data['name'] = $this->data['name'] . " (نسخة)";
    }
}

// استخدام الفئة
$product = new Product();

// استخدام __set
$product->name = "هاتف ذكي";
$product->price = 1000;

// استخدام __get
echo $product->name . "
"; // هاتف ذكي echo $product->price . "
"; // 1000 // استخدام __isset و __unset var_dump(isset($product->name)); // bool(true) unset($product->price); var_dump(isset($product->price)); // bool(false) // استخدام __toString echo $product . "
"; // منتج: هاتف ذكي // استخدام __invoke echo $product("تجربة") . "
"; // تم استدعاء الكائن كدالة مع الوسيط: تجربة // استخدام __clone $product2 = clone $product; echo $product2->name . "
"; // هاتف ذكي (نسخة) ?>
2-41
تعريف فئة Product تستخدم العديد من دوال السحر لتوفير سلوكيات خاصة.
6-13
__set و__get تسمحان بإنشاء خصائص ديناميكية والوصول إليها.
28-31
__toString تحدد كيفية تمثيل الكائن كسلسلة نصية عند محاولة طباعته.
43-64
أمثلة على استخدام دوال السحر المختلفة والسلوكيات التي توفرها.

نصائح سريعة

  • استخدم أسماء الفئات بصيغة المفرد (User وليس Users) واستخدم الحالة الجملية (PascalCase).
  • استفد من دوال السحر لتوفير واجهة استخدام سهلة ومرنة للكائنات.
  • الدالة البانية __construct لا ترجع قيمة، وتستخدم لإعداد الحالة الأولية للكائن.
  • استخدم $this للإشارة إلى الكائن الحالي داخل الفئة.
  • في PHP 8.0+، يمكنك استخدام خصائص المعاملات في الدالة البانية للإعلان عن الخصائص وتهيئتها في خطوة واحدة.

الخصائص والطرق

الخصائص (Properties) هي المتغيرات الخاصة بالفئة، بينما الطرق (Methods) هي الدوال التي يمكن للكائنات تنفيذها.

مستويات الوصول (Access Modifiers)

تتحكم مستويات الوصول في رؤية وإمكانية الوصول إلى الخصائص والطرق:

<?php
class BankAccount {
    // الخصائص العامة يمكن الوصول إليها من أي مكان
    public $accountName;
    
    // الخصائص المحمية يمكن الوصول إليها من داخل الفئة والفئات الوارثة
    protected $accountType;
    
    // الخصائص الخاصة يمكن الوصول إليها فقط من داخل الفئة
    private $balance;
    
    public function __construct($name, $type, $initialBalance) {
        $this->accountName = $name;
        $this->accountType = $type;
        $this->balance = $initialBalance;
    }
    
    // طريقة عامة
    public function deposit($amount) {
        if ($amount > 0) {
            $this->balance += $amount;
            return true;
        }
        return false;
    }
    
    // طريقة عامة تستخدم خاصية خاصة
    public function getBalance() {
        return $this->balance;
    }
    
    // طريقة محمية
    protected function calculateInterest($rate) {
        return $this->balance * $rate / 100;
    }
    
    // طريقة خاصة
    private function sendNotification($message) {
        // إرسال إشعار للمستخدم (تنفيذ وهمي)
        echo "إرسال إشعار: $message
"; } // طريقة عامة تستخدم طريقة محمية وخاصة public function addInterest($rate) { $interest = $this->calculateInterest($rate); $this->balance += $interest; $this->sendNotification("تمت إضافة فائدة بقيمة $interest"); return $interest; } } // إنشاء حساب $account = new BankAccount("أحمد محمد", "توفير", 1000); // الوصول إلى خاصية عامة echo $account->accountName . "
"; // أحمد محمد // محاولة الوصول إلى خاصية محمية (ستنتج خطأ) // echo $account->accountType; // Fatal error // محاولة الوصول إلى خاصية خاصة (ستنتج خطأ) // echo $account->balance; // Fatal error // استخدام الطرق العامة $account->deposit(500); echo "الرصيد: " . $account->getBalance() . "
"; // الرصيد: 1500 // إضافة فائدة باستخدام طريقة عامة تستدعي طرقًا محمية وخاصة $interest = $account->addInterest(5); echo "الفائدة: $interest
"; // الفائدة: 75 echo "الرصيد الجديد: " . $account->getBalance() . "
"; // الرصيد الجديد: 1575 // محاولة استدعاء طريقة محمية (ستنتج خطأ) // $account->calculateInterest(5); // Fatal error // محاولة استدعاء طريقة خاصة (ستنتج خطأ) // $account->sendNotification("رسالة"); // Fatal error ?>
2-13
تعريف فئة BankAccount مع خصائص ذات مستويات وصول مختلفة (public، protected، private).
15-43
تعريف طرق بمستويات وصول مختلفة. الطرق العامة يمكن استدعاؤها من خارج الفئة، بينما الطرق المحمية والخاصة تستخدم داخليًا فقط.
45-68
استخدام الفئة وإظهار تأثير مستويات الوصول المختلفة.

الخصائص والطرق الثابتة (Static)

الخصائص والطرق الثابتة تنتمي إلى الفئة نفسها وليس إلى كائنات محددة:

<?php
class MathUtils {
    // ثابت الفئة (class constant)
    const PI = 3.14159;
    
    // خاصية ثابتة
    public static $counter = 0;
    
    // طريقة ثابتة
    public static function add($a, $b) {
        self::$counter++;  // زيادة العداد
        return $a + $b;
    }
    
    // طريقة ثابتة أخرى
    public static function multiply($a, $b) {
        self::$counter++;  // زيادة العداد
        return $a * $b;
    }
    
    // طريقة ثابتة تستخدم الثابت
    public static function circleArea($radius) {
        self::$counter++;  // زيادة العداد
        return self::PI * $radius * $radius;
    }
    
    // طريقة عادية
    public function getCounter() {
        return self::$counter;  // الوصول إلى خاصية ثابتة من داخل طريقة عادية
    }
}

// استخدام ثابت الفئة
echo "قيمة باي: " . MathUtils::PI . "
"; // قيمة باي: 3.14159 // استخدام الطرق الثابتة $sum = MathUtils::add(5, 3); echo "المجموع: $sum
"; // المجموع: 8 $product = MathUtils::multiply(4, 2); echo "الناتج: $product
"; // الناتج: 8 $area = MathUtils::circleArea(5); echo "مساحة الدائرة: $area
"; // مساحة الدائرة: 78.53975 // الوصول إلى الخاصية الثابتة echo "عدد العمليات: " . MathUtils::$counter . "
"; // عدد العمليات: 3 // يمكن أيضًا استخدام الخاصية الثابتة داخل كائن $utils = new MathUtils(); echo "عدد العمليات من الكائن: " . $utils->getCounter() . "
"; // عدد العمليات من الكائن: 3 ?>
2-29
تعريف فئة MathUtils مع ثابت وخصائص وطرق ثابتة.
8-25
تعريف طرق ثابتة تستخدم الكلمة المفتاحية self:: للوصول إلى الثوابت والخصائص الثابتة في الفئة.
31-47
استخدام الثوابت والخصائص والطرق الثابتة مباشرة من خلال اسم الفئة دون الحاجة إلى إنشاء كائن.

نصائح سريعة

  • استخدم مستوى الوصول private للخصائص وأكشفها فقط من خلال طرق الوصول (getters/setters) إذا كان ذلك ضروريًا.
  • استخدم protected للخصائص والطرق التي تحتاج الفئات الوارثة إلى الوصول إليها.
  • استخدم const للقيم التي لن تتغير أبدًا، واستخدم static للخصائص المشتركة بين جميع الكائنات.
  • استخدم self:: للوصول إلى الثوابت والخصائص والطرق الثابتة داخل الفئة نفسها.
  • تذكر أن الخصائص الثابتة تشترك بين جميع كائنات الفئة، بينما الخصائص العادية تكون فريدة لكل كائن.

الوراثة

الوراثة (Inheritance) هي آلية في البرمجة كائنية التوجه تسمح بإنشاء فئة جديدة تعتمد على فئة موجودة. الفئة الجديدة (الفئة الابنة) ترث خصائص وطرق الفئة الأصلية (الفئة الأم) مع إمكانية إضافة خصائص وطرق جديدة أو تجاوز الموجودة.

أساسيات الوراثة

يمكن تنفيذ الوراثة في PHP باستخدام الكلمة المفتاحية extends:

<?php
// الفئة الأساسية (الأم)
class Vehicle {
    // خصائص
    protected $brand;
    protected $model;
    protected $year;
    protected $isRunning = false;
    
    // الدالة البانية
    public function __construct($brand, $model, $year) {
        $this->brand = $brand;
        $this->model = $model;
        $this->year = $year;
        echo "تم إنشاء مركبة جديدة
"; } // طرق public function start() { $this->isRunning = true; echo "المركبة تعمل الآن
"; } public function stop() { $this->isRunning = false; echo "تم إيقاف المركبة
"; } public function getInfo() { return "النوع: {$this->brand}, الموديل: {$this->model}, السنة: {$this->year}"; } } // الفئة الابنة ترث من الفئة الأم class Car extends Vehicle { // إضافة خصائص جديدة private $numDoors; private $fuelType; // تجاوز الدالة البانية للفئة الأم public function __construct($brand, $model, $year, $numDoors, $fuelType) { // استدعاء الدالة البانية للفئة الأم parent::__construct($brand, $model, $year); // إضافة الخصائص الجديدة $this->numDoors = $numDoors; $this->fuelType = $fuelType; echo "تم إنشاء سيارة جديدة
"; } // إضافة طرق جديدة public function honk() { echo "بيب بيب!
"; } // تجاوز طريقة من الفئة الأم public function getInfo() { // استخدام طريقة الفئة الأم $parentInfo = parent::getInfo(); // إضافة معلومات إضافية return $parentInfo . ", عدد الأبواب: {$this->numDoors}, نوع الوقود: {$this->fuelType}"; } } // إنشاء كائن من الفئة الابنة $car = new Car("تويوتا", "كورولا", 2022, 4, "بنزين"); // استخدام الطرق الموروثة من الفئة الأم $car->start(); // المركبة تعمل الآن // استخدام الطرق المضافة في الفئة الابنة $car->honk(); // بيب بيب! // استخدام الطريقة المجاوزة echo $car->getInfo() . "
"; // النوع: تويوتا, الموديل: كورولا, السنة: 2022, عدد الأبواب: 4, نوع الوقود: بنزين // إنشاء مركبة عادية من الفئة الأم $vehicle = new Vehicle("مرسيدس", "أكتروس", 2021); echo $vehicle->getInfo() . "
"; // النوع: مرسيدس, الموديل: أكتروس, السنة: 2021 ?>
2-27
تعريف الفئة الأساسية Vehicle التي تحتوي على خصائص وطرق أساسية للمركبات.
30-58
تعريف فئة Car التي ترث من Vehicle باستخدام الكلمة المفتاحية extends. تضيف خصائص وطرق جديدة وتتجاوز بعض الطرق الموجودة.
39-45
استخدام parent::__construct لاستدعاء الدالة البانية للفئة الأم قبل تنفيذ الإضافات الخاصة بالفئة الابنة.
53-58
تجاوز طريقة getInfo بحيث تستخدم نتيجة الطريقة الأصلية من الفئة الأم باستخدام parent::getInfo() ثم تضيف إليها معلومات إضافية.

الفئات النهائية والطرق النهائية

يمكن تحديد الفئات والطرق كنهائية (final) لمنع توريثها أو تجاوزها:

<?php
// فئة يمكن توريثها
class BaseClass {
    // طريقة يمكن تجاوزها
    public function normalMethod() {
        echo "طريقة عادية يمكن تجاوزها
"; } // طريقة نهائية لا يمكن تجاوزها final public function finalMethod() { echo "طريقة نهائية لا يمكن تجاوزها
"; } } // فئة ترث من BaseClass class ChildClass extends BaseClass { // تجاوز الطريقة العادية مسموح public function normalMethod() { echo "طريقة معدلة في الفئة الابنة
"; } // محاولة تجاوز الطريقة النهائية ستؤدي إلى خطأ // public function finalMethod() { // echo "لن يعمل هذا!
"; // } } // فئة نهائية لا يمكن توريثها final class FinalClass { public function test() { echo "هذه فئة نهائية لا يمكن توريثها
"; } } // محاولة توريث من فئة نهائية ستؤدي إلى خطأ // class AttemptClass extends FinalClass { // // لن يعمل هذا! // } // استخدام الفئات $base = new BaseClass(); $base->normalMethod(); // طريقة عادية يمكن تجاوزها $base->finalMethod(); // طريقة نهائية لا يمكن تجاوزها $child = new ChildClass(); $child->normalMethod(); // طريقة معدلة في الفئة الابنة $child->finalMethod(); // طريقة نهائية لا يمكن تجاوزها (وراثة من الفئة الأم) $final = new FinalClass(); $final->test(); // هذه فئة نهائية لا يمكن توريثها ?>
2-13
تعريف فئة أساسية مع طريقة عادية وطريقة نهائية (final) لا يمكن تجاوزها في الفئات الوارثة.
16-25
تعريف فئة ترث من الفئة الأساسية وتتجاوز الطريقة العادية. المحاولة المعلقة لتجاوز الطريقة النهائية ستؤدي إلى خطأ.
28-32
تعريف فئة نهائية (final) لا يمكن توريثها في أي فئة أخرى.

نصائح سريعة

  • تذكر أن PHP تدعم فقط الوراثة الأحادية، بمعنى أن الفئة يمكنها الوراثة من فئة واحدة فقط.
  • استخدم الكلمة المفتاحية parent:: للوصول إلى طرق الفئة الأم عند تجاوزها.
  • الخصائص والطرق الخاصة (private) في الفئة الأم لا يمكن الوصول إليها مباشرة من الفئة الابنة.
  • استخدم final لمنع تجاوز الطرق أو توريث الفئات عندما تريد ضمان سلوك محدد.
  • عند تجاوز دالة في الفئة الابنة، يجب أن يكون توقيعها متوافقًا مع توقيع الدالة الأصلية (اسم الدالة، عدد الوسائط، مستوى الوصول).

الواجهات

الواجهة (Interface) هي عقد يحدد مجموعة من الطرق التي يجب على الفئة التي تنفذها تطبيقها. تساعد الواجهات في تحقيق تعدد الأشكال (Polymorphism) وتوفير بنية ثابتة للفئات المختلفة.

تعريف واستخدام الواجهات

يمكن تعريف واجهة في PHP باستخدام الكلمة المفتاحية interface وتنفيذها في الفئات باستخدام implements:

<?php
// تعريف واجهة
interface Drawable {
    // تعريف طرق دون تنفيذ
    public function draw();
    public function getArea();
}

// تعريف واجهة أخرى
interface Resizable {
    public function resize($percentage);
}

// فئة تنفذ واجهة واحدة
class Circle implements Drawable {
    private $radius;
    
    public function __construct($radius) {
        $this->radius = $radius;
    }
    
    // تنفيذ طرق الواجهة
    public function draw() {
        echo "رسم دائرة بنصف قطر {$this->radius}
"; } public function getArea() { return pi() * $this->radius * $this->radius; } } // فئة تنفذ واجهتين class Rectangle implements Drawable, Resizable { private $width; private $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } // تنفيذ طرق واجهة Drawable public function draw() { echo "رسم مستطيل بعرض {$this->width} وارتفاع {$this->height}
"; } public function getArea() { return $this->width * $this->height; } // تنفيذ طرق واجهة Resizable public function resize($percentage) { $factor = $percentage / 100; $this->width *= $factor; $this->height *= $factor; echo "تم تغيير حجم المستطيل إلى: {$this->width} × {$this->height}
"; } } // دالة تقبل أي كائن ينفذ واجهة Drawable function drawShape(Drawable $shape) { $shape->draw(); echo "المساحة: " . $shape->getArea() . "
"; } // إنشاء كائنات $circle = new Circle(5); $rectangle = new Rectangle(4, 6); // رسم الأشكال باستخدام دالة متعددة الأشكال drawShape($circle); // رسم دائرة بنصف قطر 5 // المساحة: 78.539816339745 drawShape($rectangle); // رسم مستطيل بعرض 4 وارتفاع 6 // المساحة: 24 // استخدام الطريقة الخاصة بالمستطيل $rectangle->resize(150); // تم تغيير حجم المستطيل إلى: 6 × 9 ?>
2-12
تعريف واجهتين: Drawable وResizable، كل منهما يحدد مجموعة من الطرق التي يجب تنفيذها.
15-28
تعريف فئة Circle تنفذ واجهة Drawable وتطبق جميع طرقها المطلوبة.
31-52
تعريف فئة Rectangle تنفذ واجهتين: Drawable وResizable، وتطبق جميع الطرق المطلوبة من كلا الواجهتين.
55-58
تعريف دالة drawShape تقبل وسيطًا من نوع Drawable، مما يسمح بتمرير أي كائن ينفذ هذه الواجهة (تعدد الأشكال).

توريث الواجهات

يمكن للواجهات أن ترث من واجهات أخرى، مما يسمح بتكوين واجهات أكثر تخصصًا:

<?php
// واجهة أساسية
interface Animal {
    public function eat();
    public function sleep();
}

// واجهة ترث من واجهة أخرى
interface Bird extends Animal {
    public function fly();
    public function sing();
}

// واجهة أخرى تعتمد على وراثة متعددة للواجهات
interface Pet {
    public function play();
    public function getName();
}

// واجهة ترث من واجهتين
interface PetBird extends Bird, Pet {
    public function speak();
}

// فئة تنفذ واجهة معقدة
class Parrot implements PetBird {
    private $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    // تنفيذ طرق واجهة Animal
    public function eat() {
        echo "{$this->name} يأكل البذور
"; } public function sleep() { echo "{$this->name} ينام
"; } // تنفيذ طرق واجهة Bird public function fly() { echo "{$this->name} يطير
"; } public function sing() { echo "{$this->name} يغني
"; } // تنفيذ طرق واجهة Pet public function play() { echo "{$this->name} يلعب
"; } public function getName() { return $this->name; } // تنفيذ طرق واجهة PetBird public function speak() { echo "{$this->name} يقول: مرحبا!
"; } } // إنشاء كائن الببغاء $parrot = new Parrot("كوكو"); // استخدام الطرق المختلفة $parrot->eat(); // كوكو يأكل البذور $parrot->fly(); // كوكو يطير $parrot->play(); // كوكو يلعب $parrot->speak(); // كوكو يقول: مرحبا! // التحقق من نوع الكائن باستخدام instanceof if ($parrot instanceof Bird) { echo "هذا كائن من نوع Bird
"; // سيتم عرضها } if ($parrot instanceof Animal) { echo "هذا كائن من نوع Animal
"; // سيتم عرضها } if ($parrot instanceof PetBird) { echo "هذا كائن من نوع PetBird
"; // سيتم عرضها } ?>
2-22
تعريف واجهات متعددة مع وراثة الواجهات. يمكن للواجهة أن ترث من واجهة أو أكثر.
25-61
تعريف فئة Parrot تنفذ واجهة PetBird، مما يتطلب تنفيذ جميع الطرق من جميع الواجهات المورّثة.
68-82
التحقق من نوع الكائن باستخدام العامل instanceof، حيث يعتبر الكائن مثيلًا لجميع الواجهات التي ينفذها والواجهات التي ترثها هذه الواجهات.

نصائح سريعة

  • جميع الطرق في الواجهة يجب أن تكون عامة (public) ولا يمكن تحديدها كمحمية (protected) أو خاصة (private).
  • الواجهات لا يمكن أن تحتوي على تنفيذات للطرق، فقط التوقيعات.
  • يمكن للفئة تنفيذ عدة واجهات، مما يساعد على التغلب على قيود الوراثة الأحادية في PHP.
  • استخدم الواجهات لضمان أن الفئات المختلفة تتبع نفس العقد وتنفذ نفس السلوك الأساسي.
  • ابدأ في التفكير في الواجهات كنوع عام للكائنات أكثر من كونها تنفيذات محددة.

السمات

السمات (Traits) هي آلية في PHP تسمح بإعادة استخدام مجموعات من الطرق في فئات مختلفة. تحل السمات جزئيًا مشكلة القيود في الوراثة الأحادية، حيث يمكن للفئة استخدام عدة سمات.

تعريف واستخدام السمات

يمكن تعريف السمات باستخدام الكلمة المفتاحية trait واستخدامها في الفئات باستخدام use:

<?php
// تعريف سمة
trait Logger {
    // خصائص السمة
    protected $logFile = 'app.log';
    protected $logCount = 0;
    
    // طرق السمة
    public function log($message) {
        $this->logCount++;
        $timestamp = date('Y-m-d H:i:s');
        echo "[$timestamp] $message (تم تسجيل {$this->logCount} رسائل)
"; // في التطبيق الحقيقي، سيتم كتابة السجل إلى ملف } public function getLogCount() { return $this->logCount; } } // تعريف سمة أخرى trait FileHandler { // طرق السمة public function readFile($filename) { echo "قراءة الملف: $filename
"; // رمز قراءة الملف } public function writeFile($filename, $content) { echo "كتابة إلى الملف: $filename
"; // رمز كتابة الملف } } // فئة تستخدم سمة واحدة class User { use Logger; private $name; private $email; public function __construct($name, $email) { $this->name = $name; $this->email = $email; $this->log("تم إنشاء مستخدم جديد: $name"); } public function login() { $this->log("قام المستخدم {$this->name} بتسجيل الدخول"); } } // فئة تستخدم سمتين class FileLogger { // استخدام عدة سمات use Logger, FileHandler; public function logToFile($filename, $message) { $this->writeFile($filename, $message); $this->log("تم كتابة رسالة إلى الملف: $filename"); } } // استخدام الفئات $user = new User("أحمد", "[email protected]"); $user->login(); echo "عدد رسائل السجل: " . $user->getLogCount() . "
"; // 2 $fileLogger = new FileLogger(); $fileLogger->logToFile("errors.log", "خطأ في النظام"); $fileLogger->readFile("config.txt"); ?>
2-17
تعريف سمة Logger التي توفر وظائف تسجيل الأحداث التي يمكن استخدامها في فئات مختلفة.
20-31
تعريف سمة FileHandler التي توفر وظائف التعامل مع الملفات.
34-48
تعريف فئة User تستخدم سمة Logger من خلال الكلمة المفتاحية use.
51-60
تعريف فئة FileLogger تستخدم كلتا السمتين Logger وFileHandler، مما يوضح كيف يمكن للفئة استخدام وظائف من سمات متعددة.

حل تعارضات السمات

قد تحدث تعارضات عند استخدام عدة سمات تحتوي على طرق بنفس الاسم. توفر PHP عدة طرق لحل هذه التعارضات:

<?php
// تعريف سمتين بهما طرق متعارضة
trait A {
    public function hello() {
        return "مرحبًا من السمة A";
    }
    
    public function world() {
        return "العالم من السمة A";
    }
}

trait B {
    public function hello() {
        return "مرحبًا من السمة B";
    }
    
    public function world() {
        return "العالم من السمة B";
    }
}

// حل التعارض باستخدام insteadof
class Example1 {
    use A, B {
        A::hello insteadof B;  // استخدام hello من السمة A
        B::world insteadof A;  // استخدام world من السمة B
    }
}

// حل التعارض باستخدام as لإعادة تسمية الطرق
class Example2 {
    use A, B {
        A::hello insteadof B;  // استخدام hello من السمة A
        B::hello as helloFromB;  // إعادة تسمية hello من السمة B
        A::world as worldFromA;  // إعادة تسمية world من السمة A
    }
}

// حل التعارض مع تغيير مستوى الوصول
class Example3 {
    use A, B {
        A::hello insteadof B;
        B::hello as private helloFromB;  // تغيير إلى private
        A::world as protected worldFromA;  // تغيير إلى protected
    }
}

// استخدام الفئات
$ex1 = new Example1();
echo $ex1->hello() . "
"; // مرحبًا من السمة A echo $ex1->world() . "
"; // العالم من السمة B $ex2 = new Example2(); echo $ex2->hello() . "
"; // مرحبًا من السمة A echo $ex2->helloFromB() . "
"; // مرحبًا من السمة B echo $ex2->world() . "
"; // العالم من السمة B echo $ex2->worldFromA() . "
"; // العالم من السمة A ?>
2-20
تعريف سمتين A وB تحتويان على طرق بنفس الأسماء، مما يخلق تعارضًا عند استخدامهما معًا.
23-28
حل التعارض باستخدام الكلمة المفتاحية insteadof لتحديد أي تنفيذ للطريقة سيتم استخدامه.
31-38
حل التعارض باستخدام الكلمة المفتاحية as لإعادة تسمية الطرق المتعارضة، مما يسمح باستخدام كلتا النسختين.
41-47
حل التعارض مع تغيير مستوى الوصول للطرق المعاد تسميتها باستخدام private أو protected.

السمات المتداخلة

يمكن للسمات استخدام سمات أخرى، مما يسمح بتركيب وتوليف السلوكيات:

<?php
// تعريف سمات بسيطة
trait Authentication {
    protected function verifyPassword($password, $hash) {
        // تنفيذ وهمي للتحقق من كلمة المرور
        return password_verify($password, $hash);
    }
}

trait Authorization {
    protected function checkPermission($user, $resource) {
        // تنفيذ وهمي للتحقق من الصلاحيات
        echo "التحقق من صلاحيات المستخدم $user للوصول إلى $resource
"; return true; } } // سمة تستخدم سمات أخرى trait Security { // استخدام سمات متداخلة use Authentication, Authorization; public function secureOperation($user, $password, $hash, $resource) { if ($this->verifyPassword($password, $hash)) { if ($this->checkPermission($user, $resource)) { echo "تم تنفيذ العملية الآمنة بنجاح
"; return true; } } echo "فشل العملية الآمنة
"; return false; } } // فئة تستخدم السمة المركبة class SecureSystem { use Security; private $users = [ 'admin' => [ 'hash' => '$2y$10$v4TLQh3HLPhI2iCKXPnYfu5u3WjvO1O8vX.JbZ87hXz3mZqNUWwye', // "admin123" 'resources' => ['system', 'users', 'files'] ] ]; public function performSecureAction($username, $password, $resource) { if (isset($this->users[$username])) { $hash = $this->users[$username]['hash']; return $this->secureOperation($username, $password, $hash, $resource); } echo "المستخدم غير موجود
"; return false; } } // استخدام الفئة $system = new SecureSystem(); $system->performSecureAction('admin', 'admin123', 'users'); // التحقق من صلاحيات المستخدم admin للوصول إلى users // تم تنفيذ العملية الآمنة بنجاح $system->performSecureAction('admin', 'wrong_password', 'files'); // فشل العملية الآمنة $system->performSecureAction('guest', 'guest123', 'files'); // المستخدم غير موجود ?>
2-16
تعريف سمتين بسيطتين: Authentication للتحقق من كلمة المرور وAuthorization للتحقق من الصلاحيات.
19-33
تعريف سمة Security تستخدم السمتين السابقتين وتوفر طريقة secureOperation تجمع وظائفهما.
36-54
تعريف فئة SecureSystem تستخدم سمة Security، وتوفر تنفيذًا واقعيًا يعتمد على بيانات المستخدمين المخزنة.

نصائح سريعة

  • استخدم السمات للوظائف المشتركة التي لا تناسب تمامًا نموذج الوراثة أو الواجهات.
  • يمكن للسمات تعريف خصائص، وطرق، وطرق مجردة، وثوابت.
  • السمات لا يمكن إنشاء كائنات منها مباشرة، فهي مصممة لإعادة استخدام الشفرة داخل الفئات فقط.
  • استخدم insteadof لتحديد أي تنفيذ للطريقة سيتم استخدامه عند تعارض الطرق.
  • استخدم as لإعادة تسمية الطرق المتعارضة أو لتغيير مستوى الوصول إليها.

مساحات الأسماء

مساحات الأسماء (Namespaces) هي طريقة لتنظيم الكود في PHP وتجنب تعارضات الأسماء. تسمح مساحات الأسماء بتجميع الفئات والدوال والثوابت ذات الصلة تحت "مظلة" اسمية واحدة.

تعريف واستخدام مساحات الأسماء

يمكن تعريف مساحة أسماء باستخدام الكلمة المفتاحية namespace:

// ملف User.php
<?php
// تعريف مساحة الأسماء
namespace App\Models;

class User {
    private $id;
    private $name;
    
    public function __construct($id, $name) {
        $this->id = $id;
        $this->name = $name;
    }
    
    public function getName() {
        return $this->name;
    }
}
?>

// ملف Product.php
<?php
namespace App\Models;

class Product {
    private $id;
    private $name;
    private $price;
    
    public function __construct($id, $name, $price) {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
    }
    
    public function getDetails() {
        return "المنتج: {$this->name}, السعر: {$this->price}";
    }
}
?>

// ملف UserController.php
<?php
namespace App\Controllers;

// استخدام فئات من مساحات أسماء أخرى
use App\Models\User;
use App\Models\Product;

class UserController {
    public function showUserProducts($userId, $userName) {
        // إنشاء مستخدم من مساحة أسماء أخرى
        $user = new User($userId, $userName);
        
        // إنشاء منتج من مساحة أسماء أخرى
        $product = new Product(101, "هاتف ذكي", 1999.99);
        
        echo "المستخدم: " . $user->getName() . "
"; echo $product->getDetails() . "
"; } } ?> // ملف index.php <?php // توضيح استخدام الفئات من مساحات الأسماء المختلفة require_once 'User.php'; require_once 'Product.php'; require_once 'UserController.php'; // استخدام الفئة مع اسمها المؤهل بالكامل $controller = new App\Controllers\UserController(); $controller->showUserProducts(1, "أحمد محمد"); // استخدام use لاستيراد الفئة use App\Controllers\UserController; // بعد الاستيراد، يمكن استخدام الاسم مباشرة $controller2 = new UserController(); $controller2->showUserProducts(2, "سارة علي"); ?>
1-16
تعريف فئة User في مساحة الأسماء App\Models.
19-37
تعريف فئة Product في نفس مساحة الأسماء App\Models.
40-59
تعريف فئة UserController في مساحة الأسماء App\Controllers واستيراد واستخدام فئات من مساحة أسماء أخرى.
62-78
استخدام الفئات من مساحات الأسماء المختلفة في ملف التنفيذ الرئيسي، إما باستخدام الاسم المؤهل بالكامل أو باستخدام use.

تعامل أكثر تقدمًا مع مساحات الأسماء

يوفر PHP عدة تقنيات متقدمة للتعامل مع مساحات الأسماء:

<?php
// استيراد فئات متعددة من مساحة أسماء واحدة
use App\Models\User;
use App\Models\Product;
use App\Models\Order;

// يمكن اختصارها باستخدام المجموعة
use App\Models\{User, Product, Order};

// استيراد فئة مع إعطائها اسم مستعار
use App\Services\EmailService as Mailer;

// استخدام الاسم المستعار
$mailer = new Mailer();

// استخدام الفئة من المساحة العامة (بدون مساحة أسماء)
$globalClass = new \GlobalClass();

// تعريف دالة داخل مساحة أسماء
namespace App\Utilities;

function formatCurrency($amount, $currency = 'USD') {
    return number_format($amount, 2) . " $currency";
}

// استخدام ثوابت داخل مساحة أسماء
const APP_VERSION = '1.0.0';

// استيراد دالة من مساحة أسماء
use function App\Utilities\formatCurrency;

// استيراد ثابت من مساحة أسماء
use const App\Utilities\APP_VERSION;

// مساحات أسماء متداخلة
namespace App\Core\Database\Drivers;

class MySQLDriver {
    // تنفيذ...
}

// استيراد مساحة أسماء متداخلة
use App\Core\Database\Drivers\MySQLDriver;

// الإشارة إلى الفئة الحالية
namespace App\Models;

class Cart {
    public function checkout() {
        // استخدام اسم الفئة المؤهل بالكامل
        $service = new \App\Services\PaymentService();
        
        // الإشارة إلى الفئة الحالية
        $self = new self();  // نفس new Cart()
        
        // الإشارة إلى مساحة الأسماء الحالية
        $product = new \App\Models\Product(1, "منتج", 100);
        // أو اختصارًا
        $product2 = new Product(2, "منتج آخر", 200);
    }
}
?>
2-9
طرق مختلفة لاستيراد فئات متعددة من مساحة أسماء واحدة، بما في ذلك استخدام بناء المجموعة المختصر.
11-17
استيراد فئة مع إعطائها اسم مستعار، والوصول إلى فئة في المساحة العامة (بدون مساحة أسماء).
19-26
تعريف دوال وثوابت داخل مساحة أسماء.
43-59
الإشارة إلى الفئات داخل نفس مساحة الأسماء، واستخدام self للإشارة إلى الفئة الحالية.

نصائح سريعة

  • اتبع نمط PSR-4 لتنظيم مساحات الأسماء والمجلدات بشكل متوافق مع معايير الصناعة.
  • استخدم use لتسهيل استخدام الفئات والدوال والثوابت من مساحات الأسماء الأخرى.
  • تجنب تعريف الكثير من الفئات المختلفة في ملف واحد، واجعل كل ملف يحتوي على فئة واحدة تتوافق مع اسم الملف.
  • استخدم أسماء مستعارة عندما يكون هناك تعارض في الأسماء أو لتحسين قابلية قراءة الكود.
  • استخدم \ للإشارة إلى المساحة العامة أو لتحديد المسارات المطلقة عند الحاجة.

قواعد البيانات

تعد قواعد البيانات جزءًا أساسيًا من معظم تطبيقات الويب الحديثة. توفر PHP عدة طرق للتعامل مع قواعد البيانات، مع التركيز بشكل خاص على قواعد بيانات MySQL، الأكثر استخدامًا مع PHP.

MySQLi

MySQLi (MySQL Improved) هي واجهة برمجة تطبيقات PHP المحسنة للتعامل مع قواعد بيانات MySQL. توفر MySQLi دعمًا لكل من نمط البرمجة الإجرائي والكائني، مع ميزات متقدمة مثل الاستعلامات المجهزة والمعاملات.

الاتصال بقاعدة البيانات

يمكن الاتصال بقاعدة بيانات MySQL باستخدام MySQLi بطريقتين مختلفتين:

<?php
// 1. النمط الإجرائي
$servername = "localhost";
$username = "root";
$password = "password";
$dbname = "mydb";

// إنشاء اتصال
$conn = mysqli_connect($servername, $username, $password, $dbname);

// التحقق من الاتصال
if (!$conn) {
    die("فشل الاتصال: " . mysqli_connect_error());
}
echo "تم الاتصال بنجاح
"; // إغلاق الاتصال mysqli_close($conn); // 2. النمط الكائني // إنشاء اتصال جديد $mysqli = new mysqli($servername, $username, $password, $dbname); // التحقق من الاتصال if ($mysqli->connect_errno) { die("فشل الاتصال: " . $mysqli->connect_error); } echo "تم الاتصال بنجاح
"; // إغلاق الاتصال $mysqli->close(); ?>
2-13
استخدام النمط الإجرائي للاتصال بقاعدة البيانات باستخدام دوال mysqli_connect.
19-29
استخدام النمط الكائني للاتصال بقاعدة البيانات باستخدام الفئة mysqli.

تنفيذ الاستعلامات

يمكن تنفيذ استعلامات SQL مختلفة باستخدام MySQLi:

<?php
// الاتصال بقاعدة البيانات
$mysqli = new mysqli("localhost", "root", "password", "mydb");

// التحقق من الاتصال
if ($mysqli->connect_errno) {
    die("فشل الاتصال: " . $mysqli->connect_error);
}

// 1. إنشاء جدول (CREATE)
$sql = "CREATE TABLE IF NOT EXISTS users (
    id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    firstname VARCHAR(30) NOT NULL,
    lastname VARCHAR(30) NOT NULL,
    email VARCHAR(50),
    reg_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)";

if ($mysqli->query($sql) === TRUE) {
    echo "تم إنشاء الجدول بنجاح
"; } else { echo "خطأ في إنشاء الجدول: " . $mysqli->error . "
"; } // 2. إدراج بيانات (INSERT) $sql = "INSERT INTO users (firstname, lastname, email) VALUES ('أحمد', 'محمد', '[email protected]')"; if ($mysqli->query($sql) === TRUE) { echo "تم إدراج البيانات بنجاح
"; echo "آخر معرف تم إدراجه: " . $mysqli->insert_id . "
"; } else { echo "خطأ: " . $sql . "
" . $mysqli->error . "
"; } // 3. إدراج بيانات متعددة $sql = "INSERT INTO users (firstname, lastname, email) VALUES ('سارة', 'أحمد', '[email protected]'), ('محمد', 'علي', '[email protected]'), ('فاطمة', 'حسن', '[email protected]')"; if ($mysqli->query($sql) === TRUE) { echo "تم إدراج البيانات المتعددة بنجاح
"; } else { echo "خطأ: " . $sql . "
" . $mysqli->error . "
"; } // 4. قراءة البيانات (SELECT) $sql = "SELECT id, firstname, lastname, email FROM users"; $result = $mysqli->query($sql); if ($result->num_rows > 0) { // عرض البيانات لكل صف while($row = $result->fetch_assoc()) { echo "المعرف: " . $row["id"] . " - الاسم: " . $row["firstname"] . " " . $row["lastname"] . " - البريد: " . $row["email"] . "
"; } } else { echo "لا توجد نتائج
"; } // 5. تحديث البيانات (UPDATE) $sql = "UPDATE users SET lastname='عبدالله' WHERE firstname='أحمد'"; if ($mysqli->query($sql) === TRUE) { echo "تم تحديث البيانات بنجاح
"; echo "عدد الصفوف المتأثرة: " . $mysqli->affected_rows . "
"; } else { echo "خطأ في التحديث: " . $mysqli->error . "
"; } // 6. حذف بيانات (DELETE) $sql = "DELETE FROM users WHERE id=3"; if ($mysqli->query($sql) === TRUE) { echo "تم حذف البيانات بنجاح
"; } else { echo "خطأ في الحذف: " . $mysqli->error . "
"; } // إغلاق الاتصال $mysqli->close(); ?>
9-21
إنشاء جدول جديد في قاعدة البيانات باستخدام استعلام CREATE TABLE.
23-32
إدراج بيانات في الجدول باستخدام استعلام INSERT. استخدام insert_id للحصول على المعرف الذي تم إنشاؤه.
44-56
قراءة البيانات من الجدول باستخدام استعلام SELECT واستخدام حلقة لعرض النتائج.
58-66
تحديث البيانات في الجدول باستخدام استعلام UPDATE واستخدام affected_rows لمعرفة عدد الصفوف المتأثرة.

التعامل مع نتائج الاستعلام

توفر MySQLi عدة طرق للتعامل مع نتائج الاستعلام:

<?php
// الاتصال بقاعدة البيانات
$mysqli = new mysqli("localhost", "root", "password", "mydb");

// استعلام SELECT
$sql = "SELECT id, firstname, lastname, email FROM users";
$result = $mysqli->query($sql);

// 1. استخدام fetch_assoc() للحصول على المصفوفة الترابطية
echo "

استخدام fetch_assoc

"; $result->data_seek(0); // إعادة مؤشر النتائج إلى البداية while ($row = $result->fetch_assoc()) { echo "المعرف: " . $row["id"] . " - الاسم: " . $row["firstname"] . " " . $row["lastname"] . "
"; } // 2. استخدام fetch_array() للحصول على مصفوفة رقمية أو ترابطية echo "

استخدام fetch_array (MYSQLI_NUM)

"; $result->data_seek(0); while ($row = $result->fetch_array(MYSQLI_NUM)) { echo "المعرف: " . $row[0] . " - الاسم: " . $row[1] . " " . $row[2] . "
"; } // 3. استخدام fetch_object() للحصول على كائن echo "

استخدام fetch_object

"; $result->data_seek(0); while ($obj = $result->fetch_object()) { echo "المعرف: " . $obj->id . " - الاسم: " . $obj->firstname . " " . $obj->lastname . "
"; } // 4. استخدام fetch_all() للحصول على جميع النتائج دفعة واحدة echo "

استخدام fetch_all

"; $result->data_seek(0); $rows = $result->fetch_all(MYSQLI_ASSOC); foreach ($rows as $row) { echo "المعرف: " . $row["id"] . " - الاسم: " . $row["firstname"] . " " . $row["lastname"] . "
"; } // 5. الحصول على معلومات حول النتائج echo "

معلومات حول النتائج

"; echo "عدد الصفوف: " . $result->num_rows . "
"; echo "عدد الحقول: " . $result->field_count . "
"; // عرض معلومات الحقول echo "

معلومات الحقول

"; $fields = $result->fetch_fields(); foreach ($fields as $field) { echo "اسم الحقل: " . $field->name . ", النوع: " . $field->type . ", الحجم: " . $field->length . "
"; } // تحرير الذاكرة $result->free(); // إغلاق الاتصال $mysqli->close(); ?>
8-13
استخدام fetch_assoc() للحصول على صفوف النتيجة كمصفوفات ترابطية.
15-20
استخدام fetch_array() مع MYSQLI_NUM للحصول على صفوف النتيجة كمصفوفات رقمية.
22-27
استخدام fetch_object() للحصول على صفوف النتيجة ككائنات.
29-35
استخدام fetch_all() للحصول على جميع صفوف النتيجة دفعة واحدة.

نصائح سريعة

  • استخدم النمط الكائني للاستفادة من ميزات البرمجة كائنية التوجه وتنظيم الكود بشكل أفضل.
  • تأكد دائمًا من التحقق من الأخطاء بعد الاتصال بقاعدة البيانات وتنفيذ الاستعلامات.
  • استخدم الاستعلامات المجهزة لتفادي حقن SQL (SQL Injection) عند التعامل مع المدخلات المستلمة من المستخدم.
  • قم بإغلاق الاتصال وتحرير النتائج بعد الانتهاء من استخدامها للحفاظ على موارد الخادم.
  • استخدم try/catch لمعالجة الاستثناءات عند استخدام MySQLi في وضع التقارير الصارم.

PDO

PDO (PHP Data Objects) هي واجهة برمجة تطبيقات موحدة للوصول إلى قواعد البيانات في PHP. على عكس MySQLi التي تعمل فقط مع MySQL، يمكن لـ PDO العمل مع أنواع مختلفة من قواعد البيانات من خلال استخدام واجهة برمجة موحدة.

الاتصال بقاعدة البيانات

يتم الاتصال بقاعدة البيانات في PDO باستخدام سلسلة DSN (Data Source Name):

<?php
// معلومات الاتصال
$host = "localhost";
$dbname = "mydb";
$username = "root";
$password = "password";
$charset = "utf8mb4";

try {
    // إنشاء اتصال PDO مع MySQL
    $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
    $options = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,  // تعيين وضع التقارير للاستثناءات
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,  // تعيين وضع الجلب الافتراضي
        PDO::ATTR_EMULATE_PREPARES => false,  // تعطيل محاكاة الاستعلامات المجهزة
    ];
    
    $pdo = new PDO($dsn, $username, $password, $options);
    echo "تم الاتصال بنجاح
"; // يمكن أيضًا الاتصال بأنواع أخرى من قواعد البيانات // SQLite // $pdo = new PDO('sqlite:database.sqlite'); // PostgreSQL // $pdo = new PDO('pgsql:host=localhost;port=5432;dbname=mydb', $username, $password); // SQL Server // $pdo = new PDO('sqlsrv:Server=localhost;Database=mydb', $username, $password); } catch (PDOException $e) { // معالجة الاستثناءات die("فشل الاتصال: " . $e->getMessage()); } ?>
8-18
إنشاء اتصال PDO مع قاعدة بيانات MySQL مع تعيين خيارات هامة مثل وضع التقارير ووضع الجلب الافتراضي.
20-29
أمثلة على الاتصال بأنواع مختلفة من قواعد البيانات باستخدام PDO، مما يوضح تعدد استخدامات PDO.
31-34
استخدام try/catch لمعالجة الاستثناءات التي قد تحدث أثناء الاتصال بقاعدة البيانات.

تنفيذ الاستعلامات

يوفر PDO عدة طرق لتنفيذ استعلامات SQL:

<?php
// افتراض وجود اتصال PDO تم إنشاؤه مسبقًا
try {
    $dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
    $pdo = new PDO($dsn, "root", "password", [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
    
    // 1. إنشاء جدول (CREATE)
    $sql = "CREATE TABLE IF NOT EXISTS products (
        id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        price DECIMAL(10,2) NOT NULL,
        description TEXT,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )";
    
    $pdo->exec($sql);
    echo "تم إنشاء الجدول بنجاح
"; // 2. إدراج بيانات (INSERT) $sql = "INSERT INTO products (name, price, description) VALUES ('هاتف ذكي', 1999.99, 'هاتف ذكي حديث')"; $count = $pdo->exec($sql); $lastId = $pdo->lastInsertId(); echo "تم إدراج $count صف. آخر معرف: $lastId
"; // 3. استخدام query() للاستعلامات البسيطة $stmt = $pdo->query("SELECT * FROM products"); echo "المنتجات:
"; while ($row = $stmt->fetch()) { echo "المعرف: " . $row['id'] . ", الاسم: " . $row['name'] . ", السعر: " . $row['price'] . "
"; } // 4. تحديث البيانات (UPDATE) $sql = "UPDATE products SET price = 1899.99 WHERE name = 'هاتف ذكي'"; $count = $pdo->exec($sql); echo "تم تحديث $count صف
"; // 5. حذف بيانات (DELETE) $sql = "DELETE FROM products WHERE id = 1"; $count = $pdo->exec($sql); echo "تم حذف $count صف
"; } catch (PDOException $e) { die("خطأ: " . $e->getMessage()); } ?>
9-18
استخدام exec() لتنفيذ استعلام CREATE TABLE لإنشاء جدول في قاعدة البيانات.
20-24
استخدام exec() لتنفيذ استعلام INSERT واستخدام lastInsertId() للحصول على معرف آخر صف تم إدراجه.
26-31
استخدام query() لتنفيذ استعلام SELECT وجلب النتائج صفًا بصف باستخدام fetch().
33-41
استخدام exec() لتنفيذ استعلامات UPDATE وDELETE، والحصول على عدد الصفوف المتأثرة.

الاستعلامات المجهزة في PDO

تعد الاستعلامات المجهزة أحد أهم ميزات PDO للحماية من حقن SQL وتحسين الأداء:

<?php
// افتراض وجود اتصال PDO تم إنشاؤه مسبقًا
try {
    $dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
    $pdo = new PDO($dsn, "root", "password", [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
    
    // 1. استعلام مجهز بمعاملات مرقمة
    $stmt = $pdo->prepare("INSERT INTO products (name, price, description) VALUES (?, ?, ?)");
    $stmt->execute(['لابتوب', 3499.99, 'لابتوب قوي للألعاب']);
    echo "تم إدراج منتج جديد بمعرف: " . $pdo->lastInsertId() . "
"; // 2. استعلام مجهز بمعاملات مسماة $stmt = $pdo->prepare("INSERT INTO products (name, price, description) VALUES (:name, :price, :desc)"); $stmt->execute([ ':name' => 'حاسوب مكتبي', ':price' => 4999.99, ':desc' => 'حاسوب مكتبي للاستخدام المهني' ]); echo "تم إدراج منتج جديد بمعرف: " . $pdo->lastInsertId() . "
"; // يمكن أيضًا ربط المعاملات بشكل منفصل $stmt = $pdo->prepare("INSERT INTO products (name, price, description) VALUES (:name, :price, :desc)"); $stmt->bindValue(':name', 'سماعات رأس'); $stmt->bindValue(':price', 299.99); $stmt->bindValue(':desc', 'سماعات رأس لاسلكية'); $stmt->execute(); echo "تم إدراج منتج جديد بمعرف: " . $pdo->lastInsertId() . "
"; // 3. استعلام مع ربط المعاملات بأنواع محددة $stmt = $pdo->prepare("INSERT INTO products (name, price, description) VALUES (:name, :price, :desc)"); $name = 'فأرة لاسلكية'; $price = 99.99; $desc = 'فأرة لاسلكية بتصميم مريح'; $stmt->bindParam(':name', $name, PDO::PARAM_STR); $stmt->bindParam(':price', $price); $stmt->bindParam(':desc', $desc, PDO::PARAM_STR); $stmt->execute(); echo "تم إدراج منتج جديد بمعرف: " . $pdo->lastInsertId() . "
"; // 4. استعلام مجهز مع SELECT $stmt = $pdo->prepare("SELECT * FROM products WHERE price > ?"); $stmt->execute([1000]); echo "المنتجات التي سعرها أكبر من 1000:
"; while ($row = $stmt->fetch()) { echo "المعرف: " . $row['id'] . ", الاسم: " . $row['name'] . ", السعر: " . $row['price'] . "
"; } // 5. استعلام مع LIKE $stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE ?"); $searchTerm = "%لاسلكي%"; // البحث عن كلمة "لاسلكي" في أي مكان $stmt->execute([$searchTerm]); echo "نتائج البحث عن 'لاسلكي':
"; while ($row = $stmt->fetch()) { echo "المعرف: " . $row['id'] . ", الاسم: " . $row['name'] . ", السعر: " . $row['price'] . "
"; } } catch (PDOException $e) { die("خطأ: " . $e->getMessage()); } ?>
10-13
استخدام استعلام مجهز مع معاملات مرقمة (علامات استفهام) للإدراج الآمن.
15-22
استخدام استعلام مجهز مع معاملات مسماة (:name) وتمرير المعاملات كمصفوفة ترابطية.
24-30
ربط المعاملات بشكل منفصل باستخدام bindValue() قبل تنفيذ الاستعلام.
32-42
استخدام bindParam() لربط المعاملات بمتغيرات مع تحديد نوع البيانات باستخدام ثوابت PDO::PARAM_*.

نصائح سريعة

  • استخدم PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION لتفعيل الاستثناءات عند حدوث أخطاء بدلاً من الاعتماد على التحقق اليدوي.
  • استخدم الاستعلامات المجهزة دائمًا عند التعامل مع بيانات المستخدم لمنع حقن SQL.
  • فضّل استخدام المعاملات المسماة بدلاً من المعاملات المرقمة لتحسين قابلية قراءة الكود وصيانته.
  • استخدم PDO::ATTR_EMULATE_PREPARES => false لتعطيل محاكاة الاستعلامات المجهزة والحصول على الأمان والأداء الأفضل.
  • استفد من قابلية تبديل قواعد البيانات في PDO عند تطوير تطبيقات تحتاج إلى العمل مع أنواع مختلفة من قواعد البيانات.

المعاملات

المعاملات (Transactions) تسمح بتنفيذ مجموعة من العمليات على قاعدة البيانات كوحدة واحدة. إذا نجحت جميع العمليات، يتم تأكيد التغييرات (commit). وإذا فشلت أي عملية، يتم التراجع عن جميع التغييرات (rollback).

العمل مع المعاملات في PDO

يمكن استخدام المعاملات في PDO كما يلي:

<?php
// افتراض وجود اتصال PDO تم إنشاؤه مسبقًا
try {
    $dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
    $pdo = new PDO($dsn, "root", "password", [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
    
    // بدء المعاملة
    $pdo->beginTransaction();
    
    // محاولة تنفيذ عدة استعلامات كوحدة واحدة
    // 1. إضافة طلب جديد
    $stmt = $pdo->prepare("INSERT INTO orders (customer_id, order_date, total) VALUES (?, NOW(), ?)");
    $stmt->execute([5, 1500.75]);
    $orderId = $pdo->lastInsertId();
    
    // 2. إضافة تفاصيل الطلب (عدة منتجات)
    $stmt = $pdo->prepare("INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)");
    
    // المنتج الأول
    $stmt->execute([$orderId, 101, 2, 599.99]);
    
    // المنتج الثاني
    $stmt->execute([$orderId, 102, 1, 300.77]);
    
    // 3. تحديث مخزون المنتجات
    $stmt = $pdo->prepare("UPDATE products SET stock = stock - ? WHERE id = ?");
    $stmt->execute([2, 101]);  // خفض مخزون المنتج الأول
    $stmt->execute([1, 102]);  // خفض مخزون المنتج الثاني
    
    // إذا وصلنا إلى هنا، فقد نجحت جميع الاستعلامات، لذلك نؤكد المعاملة
    $pdo->commit();
    echo "تمت معالجة الطلب بنجاح مع معرف: $orderId
"; } catch (PDOException $e) { // إذا حدث أي خطأ، نتراجع عن جميع التغييرات if ($pdo->inTransaction()) { $pdo->rollBack(); } echo "فشلت المعاملة: " . $e->getMessage() . "
"; } ?>
11-12
بدء معاملة جديدة باستخدام طريقة beginTransaction().
14-31
تنفيذ سلسلة من العمليات المرتبطة التي يجب أن تنجح جميعها أو يتم التراجع عنها جميعًا.
33-34
تأكيد المعاملة باستخدام طريقة commit() إذا نجحت جميع العمليات.
36-42
التحقق من وجود المعاملة قيد التنفيذ باستخدام inTransaction() والتراجع عنها باستخدام rollBack() في حالة حدوث أي استثناء.

العمل مع المعاملات في MySQLi

يمكن استخدام المعاملات في MySQLi كما يلي:

<?php
// إنشاء اتصال MySQLi
$mysqli = new mysqli("localhost", "root", "password", "mydb");

// التحقق من الاتصال
if ($mysqli->connect_errno) {
    die("فشل الاتصال: " . $mysqli->connect_error);
}

// تعطيل الالتزام التلقائي
$mysqli->autocommit(false);

try {
    // 1. إضافة مستخدم جديد
    $stmt = $mysqli->prepare("INSERT INTO users (firstname, lastname, email) VALUES (?, ?, ?)");
    $firstname = "خالد";
    $lastname = "محمود";
    $email = "[email protected]";
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    $stmt->execute();
    $userId = $mysqli->insert_id;
    
    // 2. إضافة عنوان للمستخدم
    $stmt = $mysqli->prepare("INSERT INTO addresses (user_id, street, city, country) VALUES (?, ?, ?, ?)");
    $street = "شارع النخيل";
    $city = "الرياض";
    $country = "السعودية";
    $stmt->bind_param("isss", $userId, $street, $city, $country);
    $stmt->execute();
    
    // 3. إضافة تفضيلات المستخدم
    $stmt = $mysqli->prepare("INSERT INTO user_preferences (user_id, theme, notifications) VALUES (?, ?, ?)");
    $theme = "dark";
    $notifications = 1;
    $stmt->bind_param("isi", $userId, $theme, $notifications);
    $stmt->execute();
    
    // تأكيد المعاملة
    $mysqli->commit();
    echo "تم تسجيل المستخدم وبياناته بنجاح
"; } catch (Exception $e) { // التراجع عن المعاملة $mysqli->rollback(); echo "حدث خطأ وتم التراجع عن العملية: " . $e->getMessage() . "
"; } finally { // إعادة تمكين الالتزام التلقائي $mysqli->autocommit(true); // إغلاق الاتصال $mysqli->close(); } ?>
10-11
تعطيل الالتزام التلقائي باستخدام autocommit(false) لبدء المعاملة في MySQLi.
13-36
تنفيذ سلسلة من عمليات الإدراج المترابطة التي تشكل معًا معاملة واحدة.
38-40
تأكيد المعاملة باستخدام commit() بعد نجاح جميع العمليات.
42-52
التراجع عن المعاملة في حالة حدوث أخطاء، وإعادة تمكين الالتزام التلقائي بعد الانتهاء.

نصائح سريعة

  • استخدم المعاملات عندما تكون لديك مجموعة من العمليات المترابطة التي يجب أن تنجح أو تفشل كوحدة واحدة.
  • تأكد من مراعاة مستويات العزل (Isolation Levels) المناسبة لتطبيقك اعتمادًا على متطلبات التزامن.
  • لا تستخدم المعاملات لفترات طويلة، حيث قد يؤدي ذلك إلى مشكلات في الأداء والتزامن.
  • تأكد دائمًا من معالجة الاستثناءات والتراجع عن المعاملات في حالة فشل أي عملية.
  • استخدم وضع التأكيد اليدوي (تعطيل autocommit) فقط أثناء المعاملة، وأعد تمكينه بعد الانتهاء.

الاستعلامات المجهزة

الاستعلامات المجهزة (Prepared Statements) هي طريقة آمنة وفعّالة لتنفيذ استعلامات SQL. تفصل هذه التقنية بين الاستعلام والبيانات، مما يوفر حماية من هجمات حقن SQL ويحسّن الأداء عند تنفيذ استعلامات متشابهة متكررة.

مزايا الاستعلامات المجهزة

توفر الاستعلامات المجهزة عدة مزايا هامة:

  1. الأمان: حماية من هجمات حقن SQL عن طريق فصل بنية الاستعلام عن البيانات.
  2. الأداء: يتم تحليل الاستعلام وإعداده مرة واحدة، ثم يمكن تنفيذه عدة مرات بقيم مختلفة.
  3. سهولة الاستخدام: تبسيط التعامل مع أنواع البيانات المختلفة وتحسين قابلية قراءة الكود.

تدعم كل من PDO وMySQLi الاستعلامات المجهزة بطرق مختلفة قليلاً:

الاستعلامات المجهزة في PDO

تقدم PDO طريقتين لاستخدام الاستعلامات المجهزة:

<?php
// افتراض وجود اتصال PDO تم إنشاؤه مسبقًا
try {
    $dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
    $pdo = new PDO($dsn, "root", "password", [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);
    
    // 1. الاستعلامات المجهزة مع المعاملات المرقمة (علامات الاستفهام)
    echo "

استخدام المعاملات المرقمة

"; $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? AND status = ?"); $stmt->execute([1, 'active']); $user = $stmt->fetch(PDO::FETCH_ASSOC); echo "المستخدم: " . ($user ? $user['firstname'] . ' ' . $user['lastname'] : 'غير موجود') . "
"; // استخدام نفس الاستعلام المجهز مع قيم مختلفة $stmt->execute([2, 'active']); $user = $stmt->fetch(PDO::FETCH_ASSOC); echo "المستخدم: " . ($user ? $user['firstname'] . ' ' . $user['lastname'] : 'غير موجود') . "
"; // 2. الاستعلامات المجهزة مع المعاملات المسماة echo "

استخدام المعاملات المسماة

"; $stmt = $pdo->prepare("SELECT * FROM users WHERE status = :status AND city = :city"); // طريقة 1: تمرير مصفوفة ترابطية إلى execute() $params = [':status' => 'active', ':city' => 'الرياض']; $stmt->execute($params); echo "المستخدمون النشطون في الرياض:
"; while ($user = $stmt->fetch(PDO::FETCH_ASSOC)) { echo "- " . $user['firstname'] . ' ' . $user['lastname'] . "
"; } // طريقة 2: استخدام bindValue() و bindParam() $stmt = $pdo->prepare("INSERT INTO users (firstname, lastname, email, registration_date) VALUES (:fname, :lname, :email, :regdate)"); // bindValue() - يربط قيمة بمعامل $stmt->bindValue(':fname', 'سعيد'); $stmt->bindValue(':lname', 'الأحمد'); $stmt->bindValue(':email', '[email protected]'); // bindParam() - يربط متغيرًا بمعامل (مرجع) $regDate = date('Y-m-d H:i:s'); $stmt->bindParam(':regdate', $regDate); $stmt->execute(); echo "تم إضافة مستخدم جديد بمعرف: " . $pdo->lastInsertId() . "
"; // 3. تحديد أنواع البيانات $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id AND premium = :premium"); $userId = 5; $isPremium = true; $stmt->bindValue(':id', $userId, PDO::PARAM_INT); // تحديد النوع كعدد صحيح $stmt->bindValue(':premium', $isPremium, PDO::PARAM_BOOL); // تحديد النوع كقيمة منطقية $stmt->execute(); $premiumUser = $stmt->fetch(PDO::FETCH_ASSOC); echo "المستخدم المميز: " . ($premiumUser ? $premiumUser['firstname'] . ' ' . $premiumUser['lastname'] : 'غير موجود') . "
"; } catch (PDOException $e) { echo "خطأ: " . $e->getMessage() . "
"; } ?>
9-20
استخدام الاستعلامات المجهزة مع المعاملات المرقمة (?) وإعادة استخدام نفس الاستعلام المجهز مع قيم مختلفة.
22-33
استخدام الاستعلامات المجهزة مع المعاملات المسماة (:name) وتمرير مصفوفة ترابطية إلى طريقة execute().
35-47
استخدام bindValue() لربط قيم ثابتة وbindParam() لربط متغيرات (مراجع) بمعاملات الاستعلام.
49-62
تحديد أنواع البيانات صراحةً باستخدام ثوابت PDO::PARAM_* لتحسين الأمان والأداء.

الاستعلامات المجهزة في MySQLi

تعتمد MySQLi على أسلوب مختلف قليلاً للاستعلامات المجهزة:

<?php
// إنشاء اتصال MySQLi
$mysqli = new mysqli("localhost", "root", "password", "mydb");

// التحقق من الاتصال
if ($mysqli->connect_errno) {
    die("فشل الاتصال: " . $mysqli->connect_error);
}

// 1. استعلام SELECT بسيط
$stmt = $mysqli->prepare("SELECT id, firstname, lastname FROM users WHERE email = ? AND status = ?");

// ربط المعاملات
$email = "[email protected]";
$status = "active";
$stmt->bind_param("ss", $email, $status);  // "ss" يشير إلى أن كلا المعاملين من نوع string

// تنفيذ الاستعلام
$stmt->execute();

// ربط نتائج الاستعلام بمتغيرات
$stmt->bind_result($id, $firstname, $lastname);

// جلب النتائج
if ($stmt->fetch()) {
    echo "المستخدم: $firstname $lastname (معرف: $id)
"; } else { echo "لم يتم العثور على المستخدم
"; } $stmt->close(); // 2. استعلام INSERT $stmt = $mysqli->prepare("INSERT INTO users (firstname, lastname, email, registration_date) VALUES (?, ?, ?, ?)"); // ربط المعاملات $newFirstname = "محمد"; $newLastname = "علي"; $newEmail = "[email protected]"; $regDate = date("Y-m-d H:i:s"); // "ssss" يشير إلى أن جميع المعاملات من نوع string $stmt->bind_param("ssss", $newFirstname, $newLastname, $newEmail, $regDate); // تنفيذ الاستعلام $stmt->execute(); echo "تم إضافة مستخدم جديد بمعرف: " . $mysqli->insert_id . "
"; $stmt->close(); // 3. استخدام get_result() (متوفر في mysqlnd فقط) if (function_exists('mysqli_stmt_get_result')) { echo "

استخدام get_result() (منفذ mysqlnd)

"; $stmt = $mysqli->prepare("SELECT * FROM users WHERE status = ?"); $activeStatus = "active"; $stmt->bind_param("s", $activeStatus); $stmt->execute(); // الحصول على كائن النتيجة $result = $stmt->get_result(); // استخدام fetch_assoc() كما في الاستعلامات العادية while ($row = $result->fetch_assoc()) { echo "المستخدم: " . $row['firstname'] . " " . $row['lastname'] . "
"; } $stmt->close(); } else { echo "وظيفة get_result() غير متوفرة (تتطلب منفذ mysqlnd)
"; // 4. بديل لـ get_result() باستخدام bind_result $stmt = $mysqli->prepare("SELECT id, firstname, lastname FROM users WHERE status = ?"); $activeStatus = "active"; $stmt->bind_param("s", $activeStatus); $stmt->execute(); // ربط النتائج بمتغيرات $stmt->bind_result($id, $firstname, $lastname); // جلب النتائج صفًا بصف while ($stmt->fetch()) { echo "المستخدم: $firstname $lastname (معرف: $id)
"; } $stmt->close(); } // إغلاق الاتصال $mysqli->close(); ?>
9-26
استخدام الاستعلامات المجهزة في MySQLi مع استعلام SELECT، متضمنًا ربط المعاملات باستخدام bind_param() وربط النتائج باستخدام bind_result().
30-47
استخدام الاستعلامات المجهزة مع استعلام INSERT، حيث يتم ربط المعاملات وتنفيذ الاستعلام.
49-66
استخدام get_result() مع منفذ mysqlnd للحصول على كائن نتيجة يمكن معالجته باستخدام طرق الجلب المعتادة.
67-85
بديل لـ get_result() باستخدام bind_result() وfetch() للتعامل مع النتائج عندما لا يكون منفذ mysqlnd متاحًا.

نصائح سريعة

  • استخدم دائمًا الاستعلامات المجهزة عند التعامل مع مدخلات المستخدم للحماية من هجمات حقن SQL.
  • في PDO، فضّل استخدام المعاملات المسماة (:name) على المعاملات المرقمة (?) لتحسين قابلية قراءة وصيانة الكود.
  • في MySQLi، استخدم أحرف نوع البيانات المناسبة في bind_param() (مثل "s" لـ string، "i" لـ integer، "d" لـ double، "b" لـ blob).
  • أعد استخدام الاستعلامات المجهزة للعمليات المتكررة لتحسين الأداء.
  • في PDO، استخدم PDO::ATTR_EMULATE_PREPARES => false لتعطيل محاكاة الاستعلامات المجهزة والحصول على مزايا الأمان والأداء الكاملة.

الأمان

يعد أمان التطبيقات جانبًا حيويًا في تطوير مواقع الويب والتطبيقات باستخدام PHP. يغطي هذا القسم أفضل الممارسات والتقنيات لحماية تطبيقات PHP من التهديدات والثغرات الأمنية الشائعة.

التحقق من المدخلات

التحقق من المدخلات هو خط الدفاع الأول ضد العديد من الهجمات الأمنية. يجب التحقق من جميع المدخلات التي تأتي من المستخدم أو أي مصدر خارجي قبل استخدامها في التطبيق.

تصفية وتنظيف المدخلات

توفر PHP العديد من الدوال المدمجة لتصفية وتنظيف البيانات المدخلة:

<?php
// 1. استخدام filter_var() و Filter Extension
// ------------------------------------------

// التحقق من عنوان البريد الإلكتروني
$email = "[email protected]";
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "عنوان البريد الإلكتروني صالح
"; } else { echo "عنوان البريد الإلكتروني غير صالح
"; } // التحقق من عنوان IP $ip = "192.168.1.1"; if (filter_var($ip, FILTER_VALIDATE_IP)) { echo "عنوان IP صالح
"; } else { echo "عنوان IP غير صالح
"; } // التحقق من URL $url = "https://www.example.com"; if (filter_var($url, FILTER_VALIDATE_URL)) { echo "عنوان URL صالح
"; } else { echo "عنوان URL غير صالح
"; } // التحقق من قيمة عددية صحيحة $int = "42"; if (filter_var($int, FILTER_VALIDATE_INT) !== false) { echo "القيمة هي عدد صحيح
"; } else { echo "القيمة ليست عددًا صحيحًا
"; } // التحقق من قيمة عددية صحيحة ضمن نطاق $age = 25; $options = array( 'options' => array( 'min_range' => 18, 'max_range' => 65 ) ); if (filter_var($age, FILTER_VALIDATE_INT, $options) !== false) { echo "العمر ضمن النطاق المسموح (18-65)
"; } else { echo "العمر خارج النطاق المسموح
"; } // 2. تنظيف المدخلات // ---------------- // إزالة العلامات HTML من النص $cleanComment = filter_var($userComment, FILTER_SANITIZE_STRING); // Deprecated in PHP 8.1+ // بديل حديث في PHP 8.1+ $cleanComment = htmlspecialchars($userComment, ENT_QUOTES | ENT_HTML5, 'UTF-8'); echo "التعليق بعد التنظيف: $cleanComment
"; // تنظيف عنوان البريد الإلكتروني $dirtyEmail = "user(at)example.com"; $cleanEmail = filter_var($dirtyEmail, FILTER_SANITIZE_EMAIL); echo "البريد الإلكتروني بعد التنظيف: $cleanEmail
"; // تنظيف URL $dirtyUrl = "https://example.com/script?param=value with space"; $cleanUrl = filter_var($dirtyUrl, FILTER_SANITIZE_URL); echo "الرابط بعد التنظيف: $cleanUrl
"; // 3. استخدام تعبيرات منتظمة (Regular Expressions) للتحقق المخصص // ------------------------------------------------------------ function validateUsername($username) { // يجب أن يحتوي اسم المستخدم على أحرف وأرقام فقط، وطوله بين 3 و 20 حرفًا return preg_match('/^[a-zA-Z0-9]{3,20}$/', $username); } $username = "user123"; if (validateUsername($username)) { echo "اسم المستخدم صالح
"; } else { echo "اسم المستخدم غير صالح
"; } // 4. التحقق من نوع البيانات // ------------------------ function isValidDate($date, $format = 'Y-m-d') { $d = DateTime::createFromFormat($format, $date); return $d && $d->format($format) === $date; } $date = "2023-12-31"; if (isValidDate($date)) { echo "التاريخ صالح
"; } else { echo "التاريخ غير صالح
"; } // 5. التحقق من القيم المسموح بها (Whitelist) // ---------------------------------------- function validateStatus($status) { $allowedStatuses = ['pending', 'active', 'suspended', 'deleted']; return in_array($status, $allowedStatuses, true); } $status = "active"; if (validateStatus($status)) { echo "الحالة صالحة
"; } else { echo "الحالة غير صالحة
"; } ?>
4-28
استخدام دالة filter_var() مع فلاتر التحقق المختلفة للتأكد من أن البيانات تطابق التنسيق المتوقع.
29-41
استخدام فلتر التحقق من العدد الصحيح مع خيارات إضافية لتحديد النطاق المسموح به.
44-61
استخدام فلاتر التنظيف لإزالة الأحرف غير المرغوب فيها من البيانات المدخلة.
64-92
أمثلة على طرق التحقق المخصصة باستخدام التعبيرات المنتظمة، والتحقق من نوع البيانات، والتحقق من القيم باستخدام قوائم مسموح بها.

التحقق من نماذج HTML

فيما يلي مثال على كيفية التحقق من بيانات نموذج HTML في PHP:

<?php
// تعريف متغيرات الخطأ
$nameErr = $emailErr = $passwordErr = $websiteErr = "";
$name = $email = $password = $website = "";

// التحقق عند إرسال النموذج
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // التحقق من الاسم
    if (empty($_POST["name"])) {
        $nameErr = "الاسم مطلوب";
    } else {
        $name = test_input($_POST["name"]);
        // التأكد من أن الاسم يحتوي على أحرف ومسافات فقط
        if (!preg_match("/^[a-zA-Z\s]*$/", $name)) {
            $nameErr = "يُسمح فقط بالأحرف والمسافات";
        }
    }
    
    // التحقق من البريد الإلكتروني
    if (empty($_POST["email"])) {
        $emailErr = "البريد الإلكتروني مطلوب";
    } else {
        $email = test_input($_POST["email"]);
        // التحقق من صحة تنسيق البريد الإلكتروني
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $emailErr = "تنسيق البريد الإلكتروني غير صالح";
        }
    }
    
    // التحقق من كلمة المرور
    if (empty($_POST["password"])) {
        $passwordErr = "كلمة المرور مطلوبة";
    } else {
        $password = test_input($_POST["password"]);
        // التحقق من قوة كلمة المرور (على الأقل 8 أحرف، حرف كبير، رقم)
        if (!preg_match("/^(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,}$/", $password)) {
            $passwordErr = "يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل وحرف كبير ورقم واحد على الأقل";
        }
    }
    
    // التحقق من الموقع الإلكتروني (اختياري)
    if (!empty($_POST["website"])) {
        $website = test_input($_POST["website"]);
        // التحقق من صحة تنسيق URL
        if (!filter_var($website, FILTER_VALIDATE_URL)) {
            $websiteErr = "تنسيق URL غير صالح";
        }
    }
}

// دالة لتنظيف البيانات المدخلة
function test_input($data) {
    $data = trim($data);  // إزالة المسافات الزائدة
    $data = stripslashes($data);  // إزالة الشرطات المائلة العكسية
    $data = htmlspecialchars($data);  // تحويل الأحرف الخاصة HTML إلى كيانات
    return $data;
}
?>

<!-- نموذج HTML مع عرض رسائل الخطأ -->
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
    <div class="form-group">
        <label for="name">الاسم:</label>
        <input type="text" name="name" id="name" value="<?php echo $name;?>">
        <span class="error"><?php echo $nameErr;?></span>
    </div>
    
    <div class="form-group">
        <label for="email">البريد الإلكتروني:</label>
        <input type="text" name="email" id="email" value="<?php echo $email;?>">
        <span class="error"><?php echo $emailErr;?></span>
    </div>
    
    <div class="form-group">
        <label for="password">كلمة المرور:</label>
        <input type="password" name="password" id="password">
        <span class="error"><?php echo $passwordErr;?></span>
    </div>
    
    <div class="form-group">
        <label for="website">الموقع الإلكتروني (اختياري):</label>
        <input type="text" name="website" id="website" value="<?php echo $website;?>">
        <span class="error"><?php echo $websiteErr;?></span>
    </div>
    
    <button type="submit">إرسال</button>
</form>
2-5
تعريف متغيرات لتخزين البيانات ورسائل الخطأ.
8-47
التحقق من البيانات المرسلة عند تقديم النموذج، مع فحص كل حقل وتنفيذ التحقق المناسب.
50-56
دالة test_input() لتنظيف البيانات المدخلة قبل معالجتها.
59-84
نموذج HTML مع عرض رسائل الخطأ وإعادة ملء الحقول بالقيم السابقة في حالة وجود خطأ.

نصائح سريعة

  • اتبع مبدأ "لا تثق أبدًا بمدخلات المستخدم" وتحقق من جميع البيانات الواردة.
  • استخدم التحقق من المدخلات على جانب العميل للتحسين من تجربة المستخدم، ولكن لا تعتمد عليه للأمان.
  • نفذ دائمًا التحقق من المدخلات على جانب الخادم بغض النظر عن التحقق على جانب العميل.
  • استخدم قوائم مسموح بها (Whitelists) بدلاً من قوائم محظورة (Blacklists) حيثما أمكن.
  • تحقق من نوع البيانات وطولها والنطاق والتنسيق والقيم المتوقعة.
  • قم بتنظيف البيانات قبل تخزينها أو عرضها لمنع هجمات XSS وحقن SQL.

منع حقن SQL

حقن SQL هو أحد أكثر أنواع الهجمات شيوعًا على تطبيقات الويب، حيث يحاول المهاجم إدخال أوامر SQL مخصصة إلى استعلامات قاعدة البيانات الخاصة بالتطبيق.

مخاطر حقن SQL

يمكن أن يؤدي حقن SQL إلى:

  • الوصول غير المصرح به: الحصول على بيانات حساسة لم يكن من المفترض أن يصل إليها المهاجم.
  • تعديل البيانات: تغيير أو حذف بيانات في قاعدة البيانات.
  • تجاوز المصادقة: تسجيل الدخول دون كلمة مرور صالحة.
  • تنفيذ الأوامر: في بعض الحالات، تنفيذ أوامر نظام التشغيل على الخادم.

استعلام غير آمن مقابل استعلام آمن

لنقارن بين استعلام غير آمن واستعلام آمن:

<?php
// استعلام غير آمن - عرضة لحقن SQL
// ------------------------------

$username = $_POST['username'];
$password = $_POST['password'];

// خطر: بناء استعلام SQL مباشرة من مدخلات المستخدم
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);

// إذا أدخل المستخدم كـ username: admin' --
// فإن الاستعلام سيصبح:
// SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'أي شيء'
// وهذا يتجاوز التحقق من كلمة المرور

// استعلام آمن باستخدام الاستعلامات المجهزة (MySQLi)
// ---------------------------------------

$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();

// استعلام آمن باستخدام الاستعلامات المجهزة (PDO)
// -------------------------------------

$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
    ':username' => $username,
    ':password' => $password
]);
$result = $stmt->fetch();

// ملاحظة: في الواقع، لا ينبغي تخزين كلمات المرور بنص واضح
// سنرى الطريقة الصحيحة في قسم تأمين كلمات المرور
?>
4-15
استعلام غير آمن يقوم ببناء جملة SQL مباشرة من مدخلات المستخدم، مما يجعله عرضة لهجمات حقن SQL.
17-22
استعلام آمن باستخدام الاستعلامات المجهزة في MySQLi، حيث يتم فصل بنية الاستعلام عن البيانات.
24-31
استعلام آمن باستخدام الاستعلامات المجهزة في PDO، مع استخدام المعاملات المسماة.

أفضل الممارسات لمنع حقن SQL

  1. استخدم الاستعلامات المجهزة:

    هذه هي الطريقة الأكثر فعالية لمنع حقن SQL، حيث تفصل بين بنية الاستعلام والبيانات. تدعم كل من PDO وMySQLi هذه الميزة.

  2. استخدم وظائف الهروب (Escaping):

    إذا لم تتمكن من استخدام الاستعلامات المجهزة، فاستخدم الدوال المناسبة لتهرب الأحرف الخاصة في المدخلات (mysqli_real_escape_string أو PDO::quote).

  3. تحقق من نوع البيانات:

    تحويل المدخلات إلى النوع المناسب باستخدام دوال مثل intval() للأعداد الصحيحة و floatval() للأعداد العشرية.

  4. استخدم قوائم مسموح بها للمعرّفات:

    عند استخدام مدخلات المستخدم كأسماء أعمدة أو جداول، تحقق من أنها موجودة في قائمة معرّفات مسموح بها.

  5. استخدم أقل امتيازات ممكنة:

    قم بإعداد حساب قاعدة البيانات بأقل الصلاحيات اللازمة للتطبيق، للحد من الضرر في حال نجاح الهجوم.

<?php
// مثال على تنفيذ استعلام آمن مع تحقق إضافي
// -----------------------------------------

// 1. استخدام الاستعلامات المجهزة مع التحقق من نوع البيانات
function getUserById($pdo, $userId) {
    // التحقق من أن المعرف هو رقم صحيح
    $userId = filter_var($userId, FILTER_VALIDATE_INT);
    if ($userId === false) {
        return null; // أو رمي استثناء
    }
    
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
    $stmt->execute([':id' => $userId]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}

// 2. استخدام قوائم مسموح بها للمعرّفات الديناميكية
function getSortedUsers($pdo, $sortColumn, $sortOrder) {
    // قائمة بالأعمدة المسموح بها للفرز
    $allowedColumns = ['username', 'email', 'created_at', 'status'];
    // قائمة باتجاهات الفرز المسموح بها
    $allowedOrders = ['ASC', 'DESC'];
    
    // التحقق من أن العمود واتجاه الفرز مسموح بهما
    if (!in_array($sortColumn, $allowedColumns, true)) {
        $sortColumn = 'created_at'; // قيمة افتراضية آمنة
    }
    
    if (!in_array(strtoupper($sortOrder), $allowedOrders, true)) {
        $sortOrder = 'ASC'; // قيمة افتراضية آمنة
    }
    
    // استخدام الاستعلام المجهز مع إدراج اسم العمود مباشرة (آمن لأننا تحققنا منه)
    $query = "SELECT * FROM users ORDER BY $sortColumn $sortOrder";
    $stmt = $pdo->prepare($query);
    $stmt->execute();
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// 3. معالجة البحث بشكل آمن
function searchUsers($pdo, $searchTerm) {
    // تنظيف وإعداد مصطلح البحث
    $searchTerm = '%' . trim($searchTerm) . '%';
    
    $stmt = $pdo->prepare("
        SELECT * FROM users 
        WHERE username LIKE :search 
        OR email LIKE :search 
        OR firstname LIKE :search 
        OR lastname LIKE :search
    ");
    
    $stmt->execute([':search' => $searchTerm]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
?>
4-15
دالة آمنة للحصول على مستخدم بواسطة المعرف، مع التحقق من نوع البيانات قبل استخدامها في الاستعلام.
18-38
دالة للحصول على قائمة مستخدمين مفروزة، مع استخدام قوائم مسموح بها للأعمدة واتجاهات الفرز.
41-57
دالة للبحث عن مستخدمين بشكل آمن، باستخدام الاستعلام المجهز مع عامل LIKE.

نصائح سريعة

  • استخدم دائمًا الاستعلامات المجهزة مع المعاملات المربوطة للتعامل مع مدخلات المستخدم في استعلامات SQL.
  • لا تبني استعلامات SQL بتركيب سلاسل النصوص مباشرة من المدخلات.
  • استخدم PDO بدلاً من mysqli حيثما أمكن، فهو يوفر تجريدًا أفضل ودعمًا لقواعد بيانات متعددة.
  • تحقق من صحة ونوع البيانات قبل استخدامها في استعلامات SQL.
  • استخدم أقل امتيازات ممكنة في حساب قاعدة البيانات المستخدم في التطبيق.
  • استخدم ORM (مثل Doctrine أو Eloquent) حيثما أمكن، فهي توفر طبقة إضافية من الحماية.

منع هجمات XSS

هجمات Cross-Site Scripting (XSS) هي نوع من الهجمات حيث يتم حقن أكواد JavaScript ضارة في صفحات الويب التي يعرضها المستخدمون الآخرون. يمكن أن تؤدي هذه الهجمات إلى سرقة جلسات المستخدمين، وسرقة الكوكيز، وتنفيذ أكواد ضارة في المتصفح.

أنواع هجمات XSS

هناك ثلاثة أنواع رئيسية من هجمات XSS:

  1. الهجمات المخزنة (Stored XSS):

    يتم تخزين البرمجيات النصية الضارة في قاعدة البيانات وتُعرض لكل مستخدم يزور الصفحة المتأثرة (مثل التعليقات في المدونات).

  2. الهجمات المنعكسة (Reflected XSS):

    يتم إرسال البرمجيات النصية الضارة في طلب HTTP ثم تنعكس على المستخدم دون تخزينها (مثل نتائج البحث).

  3. هجمات DOM XSS:

    تحدث عندما تقوم البرمجيات النصية JavaScript بتعديل DOM الخاص بالصفحة بشكل غير آمن باستخدام بيانات من مصدر غير موثوق.

ترميز المخرجات لمنع XSS

الطريقة الرئيسية لمنع هجمات XSS هي ترميز جميع المخرجات غير الموثوقة قبل عرضها للمستخدم:

<?php
// ترميز المخرجات باستخدام htmlspecialchars()
// -------------------------------------------

// غير آمن - عرضة لهجمات XSS
$userName = $_GET['name'];
echo "مرحبًا، " . $userName;  // خطر إذا أدخل المستخدم: <script>alert('XSS')</script>

// آمن - ترميز المخرجات
$userName = $_GET['name'];
echo "مرحبًا، " . htmlspecialchars($userName, ENT_QUOTES, 'UTF-8');

// استخدام htmlspecialchars() بشكل شامل
function h($string) {
    return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}

// استخدام الدالة المساعدة
echo "مرحبًا، " . h($_GET['name']);

// ترميز المخرجات في سياقات HTML المختلفة
// ---------------------------------------

// 1. سياق النص
echo "<div>" . h($userComment) . "</div>";

// 2. سياق سمات HTML
echo '<a title="' . h($userTitle) . '">الرابط</a>';

// 3. سياق JavaScript
// لا تضع بيانات المستخدم مباشرة في كود JavaScript
// استخدم JSON لنقل البيانات وترميزها بشكل صحيح
$userDataJson = json_encode(['name' => $userName]);
echo '<script>const userData = ' . $userDataJson . ';</script>';

// 4. سياق URL
// استخدم urlencode() لترميز البيانات في الروابط
echo '<a href="profile.php?id=' . urlencode($userId) . '">الملف الشخصي</a>';
?>
4-6
مثال على كود غير آمن معرض لهجمات XSS، حيث يتم عرض بيانات المستخدم مباشرة دون ترميز.
8-17
استخدام htmlspecialchars() لترميز المخرجات وإنشاء دالة مساعدة لتبسيط العملية.
20-31
ترميز المخرجات في سياقات HTML المختلفة: النص، سمات HTML، وسياق JavaScript.

استراتيجيات إضافية لمنع XSS

  1. استخدام Content Security Policy (CSP):

    إضافة رأس HTTP للتحكم في المصادر المسموح بها لتحميل المحتوى وتنفيذ البرمجيات النصية.

  2. استخدام HttpOnly للكوكيز:

    تعيين العلامة HttpOnly على الكوكيز الحساسة لمنع الوصول إليها من خلال JavaScript.

  3. التحقق من المدخلات:

    تنظيف وتحقق من المدخلات قبل تخزينها أو عرضها.

  4. استخدام مكتبات مضمنة للمعالجة:

    استخدام مكتبات مثل HTML Purifier لتنظيف HTML المدخل من المستخدم.

<?php
// 1. تعيين رأس Content Security Policy
// ------------------------------------
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com;");

// 2. تعيين كوكيز آمنة
// ------------------
// تعيين كوكيز مع علامة HttpOnly
setcookie('session_id', $sessionId, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,    // يتطلب HTTPS
    'httponly' => true,  // يمنع الوصول من JavaScript
    'samesite' => 'Lax' // مقاومة لهجمات CSRF
]);

// 3. استخدام HTML Purifier لتنظيف HTML من المستخدم
// ---------------------------------------------
// تأكد من تثبيت المكتبة باستخدام Composer:
// composer require ezyang/htmlpurifier

require_once 'vendor/autoload.php';

$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br,span[class]');
$purifier = new HTMLPurifier($config);

// تنظيف HTML من المستخدم
$userHtml = $_POST['comment'];
$cleanHtml = $purifier->purify($userHtml);

// يمكن الآن تخزين $cleanHtml في قاعدة البيانات أو عرضه بأمان

// 4. تنفيذ XSS Auditor مخصص للنصوص المدخلة
// --------------------------------------
function detectXSS($input) {
    // نمط بسيط للكشف عن محاولات XSS الشائعة
    $patterns = [
        '/<script.*?>/i',
        '/on\w+=".*?"/i',
        '/javascript:/i'
    ];
    
    foreach ($patterns as $pattern) {
        if (preg_match($pattern, $input)) {
            return true; // تم الكشف عن محاولة XSS محتملة
        }
    }
    
    return false;
}

// استخدام الدالة للتحقق من المدخلات
$userInput = $_POST['message'];
if (detectXSS($userInput)) {
    die("تم الكشف عن محتوى غير آمن!");
} else {
    // معالجة المدخلات الآمنة
    // دائمًا استخدم ترميز المخرجات عند العرض
    echo h($userInput);
}
?>
2-4
تعيين رأس Content Security Policy للتحكم في مصادر المحتوى وتعزيز الأمان.
7-14
تعيين كوكيز آمنة باستخدام خيارات HttpOnly وSecure وSameSite لتعزيز الأمان.
17-27
استخدام مكتبة HTML Purifier لتنظيف HTML المدخل من المستخدم بشكل آمن.
30-49
تنفيذ دالة بسيطة للكشف عن محاولات XSS في المدخلات وتنفيذ إجراءات مناسبة.

نصائح سريعة

  • قم دائمًا بترميز المخرجات باستخدام htmlspecialchars() أو ما يعادلها عند عرض بيانات غير موثوقة.
  • استخدم ترميزًا مناسبًا للسياق: HTML، وسمات HTML، وJavaScript، وCSS، وURL.
  • تجنب استخدام دوال مثل eval() وdocument.write() وinnerHTML مع بيانات من مصادر غير موثوقة.
  • استخدم Content Security Policy كطبقة أمان إضافية للحد من تأثير الثغرات الأمنية.
  • استخدم أطر عمل ومكتبات حديثة توفر حماية ضد XSS بشكل افتراضي.
  • استخدم علامة HttpOnly للكوكيز الحساسة، خاصة كوكيز الجلسة، لمنع سرقتها عبر XSS.

منع هجمات CSRF

هجمات Cross-Site Request Forgery (CSRF) هي نوع من الهجمات حيث يتم خداع المستخدم المصادق عليه لتنفيذ إجراءات غير مرغوب فيها على موقع ويب آخر دون علمه، باستغلال حالة المصادقة الخاصة به.

كيف تعمل هجمات CSRF

تحدث هجمات CSRF عندما يقوم موقع ضار بإرسال طلبات إلى موقع آمن يكون المستخدم مسجل دخوله فيه بالفعل. تعتمد على حقيقة أن المتصفح يرسل تلقائيًا الكوكيز المرتبطة بالموقع المستهدف مع الطلب.

مثال على هجوم CSRF:
  1. المستخدم مسجل دخوله إلى موقع بنكه (bank.example.com).
  2. يزور المستخدم موقعًا ضارًا (malicious.example.com).
  3. يحتوي الموقع الضار على صورة مخفية مع طلب: <img src="https://bank.example.com/transfer?to=attacker&amount=1000" style="display:none">
  4. يقوم المتصفح بإرسال الطلب تلقائيًا مع كوكيز جلسة المستخدم.
  5. يتم تنفيذ التحويل المصرفي دون علم المستخدم أو موافقته.

استخدام رموز CSRF للحماية

الطريقة الأكثر فعالية لمنع هجمات CSRF هي استخدام رموز CSRF. هذه رموز فريدة وعشوائية يتم توليدها لكل جلسة مستخدم وتضمينها في جميع النماذج والطلبات:

<?php
// تنفيذ حماية CSRF بسيطة في PHP
// ----------------------------

session_start();

// توليد رمز CSRF إذا لم يكن موجودًا
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// التحقق من صحة الرمز عند استلام طلب POST
function validateCsrfToken() {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token'])) {
            die('فشل التحقق من رمز CSRF: الرمز مفقود');
        }
        
        if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
            die('فشل التحقق من رمز CSRF: الرمز غير صالح');
        }
        
        // الرمز صالح، يمكن متابعة معالجة الطلب
        return true;
    }
    
    return false;
}

// التحقق من رمز CSRF عند استلام طلب POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    validateCsrfToken();
    
    // معالجة الطلب
    echo "تم معالجة الطلب بنجاح";
}
?>

<!-- إضافة حقل مخفي للرمز في النموذج -->
<form method="post" action="process.php">
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
    
    <div>
        <label for="username">اسم المستخدم:</label>
        <input type="text" id="username" name="username">
    </div>
    
    <div>
        <label for="email">البريد الإلكتروني:</label>
        <input type="email" id="email" name="email">
    </div>
    
    <button type="submit">إرسال</button>
</form>

<!-- مع طلبات AJAX، أضف الرمز في الرأس أو في البيانات -->
<script>
    // مثال على طلب AJAX مع رمز CSRF
    fetch('api/user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-Token': '<?php echo $_SESSION['csrf_token']; ?>'
        },
        body: JSON.stringify({
            name: 'John Doe',
            email: '[email protected]'
        })
    })
    .then(response => response.json())
    .then(data => console.log(data));
</script>
4-9
بدء الجلسة وتوليد رمز CSRF فريد وعشوائي إذا لم يكن موجودًا.
12-26
دالة للتحقق من صحة رمز CSRF عند استلام طلب POST، باستخدام hash_equals() للمقارنة الآمنة.
36-49
إضافة حقل مخفي يحتوي على رمز CSRF في النموذج.
51-66
مثال على كيفية تضمين رمز CSRF في طلبات AJAX عبر رأس مخصص.

استراتيجيات إضافية لمنع CSRF

  1. استخدام SameSite للكوكيز:

    تعيين خاصية SameSite للكوكيز (Strict أو Lax) لمنع إرسالها في طلبات متعددة المواقع.

  2. التحقق من مصدر الطلب:

    التحقق من رأس Referer و/أو Origin للتأكد من أن الطلب قادم من موقعك.

  3. استخدام الطلبات المزدوجة:

    طلب تأكيد إضافي للعمليات الحساسة، مثل نافذة تأكيد أو إدخال كلمة المرور مرة أخرى.

<?php
// 1. تعيين كوكيز SameSite
// ----------------------
setcookie('session_id', $sessionId, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax'  // 'Strict' لمزيد من الأمان، لكن قد يؤثر على تجربة المستخدم
]);

// 2. التحقق من رأس Referer
// -----------------------
function checkReferer() {
    if (!isset($_SERVER['HTTP_REFERER'])) {
        die('رأس Referer مفقود');
    }
    
    $referer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
    $allowedHosts = ['example.com', 'www.example.com'];
    
    if (!in_array($referer, $allowedHosts)) {
        die('رأس Referer غير صالح');
    }
    
    return true;
}

// استخدام التحقق من Referer مع التحقق من رمز CSRF
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    validateCsrfToken();
    checkReferer();
    
    // معالجة الطلب
}

// 3. تنفيذ نظام أكثر تعقيدًا لحماية CSRF
// -------------------------------------
class CsrfProtection {
    private $tokenName = 'csrf_token';
    private $tokenLength = 32;
    
    // توليد رمز CSRF جديد
    public function generateToken() {
        if (empty($_SESSION[$this->tokenName])) {
            $_SESSION[$this->tokenName] = bin2hex(random_bytes($this->tokenLength));
        }
        return $_SESSION[$this->tokenName];
    }
    
    // الحصول على الرمز الحالي
    public function getToken() {
        return isset($_SESSION[$this->tokenName]) ? $_SESSION[$this->tokenName] : $this->generateToken();
    }
    
    // إنشاء حقل نموذج HTML
    public function tokenField() {
        return '';
    }
    
    // التحقق من صحة الرمز
    public function validateToken($token) {
        if (!isset($_SESSION[$this->tokenName])) {
            return false;
        }
        
        if (!hash_equals($_SESSION[$this->tokenName], $token)) {
            return false;
        }
        
        return true;
    }
    
    // تحقق من طلب POST للتأكد من وجود الرمز الصحيح
    public function validateRequest() {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            if (!isset($_POST[$this->tokenName])) {
                throw new Exception('فشل التحقق من CSRF: الرمز مفقود');
            }
            
            if (!$this->validateToken($_POST[$this->tokenName])) {
                throw new Exception('فشل التحقق من CSRF: الرمز غير صالح');
            }
        }
        
        return true;
    }
}

// استخدام فئة حماية CSRF
$csrf = new CsrfProtection();

// في الصفحة التي تحتوي على النموذج
echo '
'; echo $csrf->tokenField(); // حقول النموذج الأخرى echo '
'; // في معالج النموذج try { $csrf->validateRequest(); // معالجة البيانات } catch (Exception $e) { die($e->getMessage()); } ?>
2-10
تعيين كوكيز مع خاصية SameSite لمنع إرسالها في طلبات متعددة المواقع.
13-30
دالة للتحقق من رأس Referer للتأكد من أن الطلب قادم من المواقع المسموح بها.
33-84
تنفيذ فئة متكاملة لحماية CSRF تتضمن توليد الرموز والتحقق منها وإنشاء حقول النموذج.
86-100
استخدام فئة حماية CSRF في الصفحات والنماذج.

نصائح سريعة

  • استخدم دائمًا رموز CSRF في جميع النماذج والطلبات التي تغير الحالة (POST, PUT, DELETE).
  • استخدم خاصية SameSite للكوكيز للحد من هجمات CSRF.
  • تأكد من مقارنة الرموز باستخدام دالة hash_equals() لمنع هجمات التوقيت.
  • استخدم دالة random_bytes() أو openssl_random_pseudo_bytes() لتوليد رموز عشوائية آمنة.
  • ضع في اعتبارك استخدام مكتبة أو إطار عمل يوفر حماية CSRF مضمنة.
  • تجنب استخدام طلبات GET لتغيير الحالة أو تنفيذ إجراءات.

تأمين كلمات المرور

تأمين كلمات المرور هو أحد أهم جوانب أمان التطبيقات. يجب عدم تخزين كلمات المرور بنص واضح وبدلاً من ذلك، يجب استخدام تقنيات التجزئة (hashing) مع إضافة ملح (salt) لضمان أمانها.

استخدام وظائف التجزئة الآمنة

توفر PHP دوال مضمنة لتجزئة كلمات المرور والتحقق منها بأمان:

<?php
// 1. استخدام password_hash() و password_verify()
// --------------------------------------------

// تخزين كلمة المرور - عند التسجيل أو تغيير كلمة المرور
function storePassword($password) {
    // تجزئة كلمة المرور باستخدام خوارزمية التجزئة الافتراضية (حاليًا bcrypt)
    // سيتم إنشاء ملح عشوائي تلقائيًا وتخزينه مع التجزئة
    $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
    
    // تخزين التجزئة في قاعدة البيانات
    // saveToDatabase($userId, $hashedPassword);
    
    return $hashedPassword;
}

// التحقق من كلمة المرور - عند تسجيل الدخول
function verifyPassword($password, $hashedPassword) {
    // password_verify() تتحقق من كلمة المرور مقابل التجزئة
    if (password_verify($password, $hashedPassword)) {
        // كلمة المرور صحيحة
        return true;
    } else {
        // كلمة المرور غير صحيحة
        return false;
    }
}

// 2. استخدام خوارزميات تجزئة مختلفة
// -------------------------------
// PASSWORD_DEFAULT: يستخدم أفضل خوارزمية متاحة حاليًا (bcrypt حاليًا)
$hash1 = password_hash($password, PASSWORD_DEFAULT);

// PASSWORD_BCRYPT: يستخدم خوارزمية bcrypt مع عامل تكلفة (10-12 قيمة جيدة للإنتاج)
$hash2 = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);

// PASSWORD_ARGON2I/PASSWORD_ARGON2ID: يستخدم خوارزمية Argon2 (متاحة في PHP 7.2+)
if (defined('PASSWORD_ARGON2ID')) {
    $hash3 = password_hash($password, PASSWORD_ARGON2ID, [
        'memory_cost' => 1024,   // الذاكرة المستخدمة بالكيلوبايت
        'time_cost' => 2,        // عدد مرات التكرار
        'threads' => 2           // عدد الخيوط المتوازية
    ]);
}

// 3. التحقق مما إذا كانت كلمة المرور بحاجة إلى إعادة التجزئة
// --------------------------------------------
function checkPasswordNeedsRehash($hashedPassword) {
    // التحقق مما إذا كانت التجزئة الحالية تستخدم الإعدادات المطلوبة أو الخوارزمية الحالية
    if (password_needs_rehash($hashedPassword, PASSWORD_DEFAULT, ['cost' => 12])) {
        // كلمة المرور بحاجة إلى إعادة التجزئة باستخدام الإعدادات الجديدة
        return true;
    }
    
    return false;
}

// 4. مثال كامل لنظام تسجيل الدخول والتسجيل
// -----------------------------------
class UserAuthManager {
    private $db; // افتراض وجود اتصال بقاعدة البيانات
    
    public function __construct($db) {
        $this->db = $db;
    }
    
    // تسجيل مستخدم جديد
    public function registerUser($username, $email, $password) {
        // التحقق من البيانات المدخلة
        if (empty($username) || empty($email) || empty($password)) {
            throw new Exception("جميع الحقول مطلوبة");
        }
        
        // التحقق من قوة كلمة المرور
        if (!$this->isStrongPassword($password)) {
            throw new Exception("كلمة المرور غير قوية بما فيه الكفاية");
        }
        
        // التحقق من فريدية اسم المستخدم والبريد الإلكتروني
        if ($this->isUsernameTaken($username)) {
            throw new Exception("اسم المستخدم مستخدم بالفعل");
        }
        
        if ($this->isEmailTaken($email)) {
            throw new Exception("البريد الإلكتروني مستخدم بالفعل");
        }
        
        // تجزئة كلمة المرور
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
        
        // إنشاء مستخدم جديد في قاعدة البيانات
        $stmt = $this->db->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
        $stmt->execute([$username, $email, $hashedPassword]);
        
        return $this->db->lastInsertId();
    }
    
    // تسجيل الدخول
    public function loginUser($username, $password) {
        // جلب المستخدم من قاعدة البيانات
        $stmt = $this->db->prepare("SELECT id, password FROM users WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$user) {
            return false; // المستخدم غير موجود
        }
        
        // التحقق من كلمة المرور
        if (password_verify($password, $user['password'])) {
            // التحقق مما إذا كانت كلمة المرور بحاجة إلى إعادة التجزئة
            if (password_needs_rehash($user['password'], PASSWORD_DEFAULT, ['cost' => 12])) {
                // تحديث التجزئة في قاعدة البيانات
                $newHash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
                $stmt = $this->db->prepare("UPDATE users SET password = ? WHERE id = ?");
                $stmt->execute([$newHash, $user['id']]);
            }
            
            return $user['id']; // تسجيل الدخول ناجح
        }
        
        return false; // كلمة المرور غير صحيحة
    }
    
    // إعادة تعيين كلمة المرور
    public function resetPassword($userId, $newPassword) {
        // التحقق من قوة كلمة المرور
        if (!$this->isStrongPassword($newPassword)) {
            throw new Exception("كلمة المرور الجديدة غير قوية بما فيه الكفاية");
        }
        
        // تجزئة كلمة المرور الجديدة
        $hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT, ['cost' => 12]);
        
        // تحديث كلمة المرور في قاعدة البيانات
        $stmt = $this->db->prepare("UPDATE users SET password = ? WHERE id = ?");
        $stmt->execute([$hashedPassword, $userId]);
        
        return $stmt->rowCount() > 0;
    }
    
    // التحقق من قوة كلمة المرور
    private function isStrongPassword($password) {
        // الحد الأدنى للطول هو 8 أحرف
        if (strlen($password) < 8) {
            return false;
        }
        
        // يجب أن تحتوي على حرف كبير واحد على الأقل
        if (!preg_match('/[A-Z]/', $password)) {
            return false;
        }
        
        // يجب أن تحتوي على حرف صغير واحد على الأقل
        if (!preg_match('/[a-z]/', $password)) {
            return false;
        }
        
        // يجب أن تحتوي على رقم واحد على الأقل
        if (!preg_match('/[0-9]/', $password)) {
            return false;
        }
        
        // يجب أن تحتوي على حرف خاص واحد على الأقل
        if (!preg_match('/[^A-Za-z0-9]/', $password)) {
            return false;
        }
        
        return true;
    }
    
    // التحقق من وجود اسم المستخدم
    private function isUsernameTaken($username) {
        $stmt = $this->db->prepare("SELECT id FROM users WHERE username = ?");
        $stmt->execute([$username]);
        return $stmt->fetch() !== false;
    }
    
    // التحقق من وجود البريد الإلكتروني
    private function isEmailTaken($email) {
        $stmt = $this->db->prepare("SELECT id FROM users WHERE email = ?");
        $stmt->execute([$email]);
        return $stmt->fetch() !== false;
    }
}

// 5. معالجة سيناريوهات أمان كلمة المرور المتقدمة
// ---------------------------------------

// إنشاء توكن لإعادة تعيين كلمة المرور
function generatePasswordResetToken($userId) {
    $token = bin2hex(random_bytes(32));
    $expires = time() + 3600; // صالح لمدة ساعة واحدة
    
    // تخزين التوكن في قاعدة البيانات
    // storeResetToken($userId, $token, $expires);
    
    return $token;
}

// التحقق من صحة توكن إعادة تعيين كلمة المرور
function validatePasswordResetToken($userId, $token) {
    // استرجاع التوكن من قاعدة البيانات
    // $storedToken = getResetToken($userId);
    
    // للتوضيح فقط
    $storedToken = [
        'token' => '123456789abcdef',
        'expires' => time() + 100
    ];
    
    // التحقق من انتهاء صلاحية التوكن
    if ($storedToken['expires'] < time()) {
        return false; // التوكن منتهي الصلاحية
    }
    
    // مقارنة التوكن
    return hash_equals($storedToken['token'], $token);
}

// سياسة تغيير كلمة المرور
function enforcePasswordPolicy($userId, $newPassword) {
    // التحقق من تاريخ استخدام كلمات المرور السابقة
    // $previousPasswords = getPreviousPasswords($userId);
    
    // للتوضيح فقط
    $previousPasswords = [
        '$2y$12$abc123',
        '$2y$12$def456'
    ];
    
    // التحقق من استخدام كلمة المرور سابقًا
    foreach ($previousPasswords as $previousHash) {
        if (password_verify($newPassword, $previousHash)) {
            throw new Exception("لا يمكن إعادة استخدام كلمة مرور سابقة");
        }
    }
    
    // تأكد من مرور الحد الأدنى من الوقت منذ آخر تغيير (على سبيل المثال، لا يمكن تغيير كلمة المرور أكثر من مرة في اليوم)
    // ...
    
    return true;
}
?>
4-15
استخدام password_hash() لتجزئة كلمات المرور بأمان، حيث يتم إنشاء ملح عشوائي تلقائيًا وتخزينه مع التجزئة.
18-27
استخدام password_verify() للتحقق من صحة كلمة المرور مقابل التجزئة المخزنة.
47-55
استخدام password_needs_rehash() للتحقق مما إذا كانت كلمة المرور بحاجة إلى إعادة التجزئة باستخدام خوارزمية أو إعدادات جديدة.
70-169
تنفيذ فئة UserAuthManager متكاملة للتعامل مع تسجيل المستخدمين وتسجيل الدخول وإعادة تعيين كلمات المرور بشكل آمن.

أفضل الممارسات الإضافية لتأمين كلمات المرور

  1. تعقيد كلمة المرور:

    فرض سياسة قوية لكلمات المرور تتطلب طولًا كافيًا وتعقيدًا (أحرف كبيرة وصغيرة، أرقام، رموز).

  2. المصادقة متعددة العوامل (MFA):

    توفير خيار المصادقة بعاملين أو أكثر لزيادة الأمان (مثل رمز البريد الإلكتروني، رمز SMS، أو تطبيق المصادقة).

  3. تأخير الاستجابة:

    إضافة تأخير في الاستجابة بعد محاولات تسجيل دخول فاشلة للحد من هجمات القوة الغاشمة.

  4. سجلات المحاولات:

    تسجيل محاولات تسجيل الدخول الفاشلة وإعلام المستخدم عند حدوث نشاط مشبوه.

<?php
// 1. تنفيذ المصادقة متعددة العوامل (MFA)
// -----------------------------------
class MFAManager {
    private $db;
    
    public function __construct($db) {
        $this->db = $db;
    }
    
    // توليد رمز التحقق
    public function generateVerificationCode($userId) {
        // إنشاء رمز من 6 أرقام
        $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        
        // تخزين الرمز مع وقت انتهاء الصلاحية (10 دقائق)
        $expires = time() + 600;
        
        // تخزين الرمز في قاعدة البيانات
        $stmt = $this->db->prepare("INSERT INTO mfa_codes (user_id, code, expires_at) VALUES (?, ?, ?)");
        $stmt->execute([$userId, $code, date('Y-m-d H:i:s', $expires)]);
        
        return $code;
    }
    
    // إرسال رمز التحقق عبر البريد الإلكتروني
    public function sendVerificationCodeByEmail($email, $code) {
        // تنفيذ إرسال البريد الإلكتروني
        $subject = "رمز التحقق الخاص بك";
        $message = "رمز التحقق الخاص بك هو: $code\nينتهي خلال 10 دقائق.";
        
        // mail($email, $subject, $message);
        
        return true;
    }
    
    // التحقق من صحة الرمز
    public function verifyCode($userId, $code) {
        $stmt = $this->db->prepare("
            SELECT id FROM mfa_codes 
            WHERE user_id = ? AND code = ? AND expires_at > ?
        ");
        $stmt->execute([$userId, $code, date('Y-m-d H:i:s')]);
        
        $result = $stmt->fetch();
        
        if ($result) {
            // حذف الرمز بعد استخدامه
            $stmt = $this->db->prepare("DELETE FROM mfa_codes WHERE id = ?");
            $stmt->execute([$result['id']]);
            
            return true;
        }
        
        return false;
    }
}

// 2. تنفيذ نظام تأخير المحاولات وقفل الحساب
// ---------------------------------------
class LoginAttemptManager {
    private $db;
    private $maxAttempts = 5;        // الحد الأقصى للمحاولات الفاشلة
    private $lockoutDuration = 1800;  // مدة القفل بالثواني (30 دقيقة)
    
    public function __construct($db) {
        $this->db = $db;
    }
    
    // تسجيل محاولة تسجيل دخول فاشلة
    public function recordFailedAttempt($username, $ipAddress) {
        $stmt = $this->db->prepare("
            INSERT INTO login_attempts (username, ip_address, attempt_time) 
            VALUES (?, ?, NOW())
        ");
        $stmt->execute([$username, $ipAddress]);
        
        // التحقق مما إذا كان يجب قفل الحساب
        return $this->shouldLockAccount($username);
    }
    
    // التحقق مما إذا وصل المستخدم إلى الحد الأقصى للمحاولات
    public function shouldLockAccount($username) {
        $stmt = $this->db->prepare("
            SELECT COUNT(*) as attempts FROM login_attempts 
            WHERE username = ? AND attempt_time > ?
        ");
        
        $lockoutTime = date('Y-m-d H:i:s', time() - $this->lockoutDuration);
        $stmt->execute([$username, $lockoutTime]);
        
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        
        return $result['attempts'] >= $this->maxAttempts;
    }
    
    // التحقق مما إذا كان الحساب مقفلاً
    public function isAccountLocked($username) {
        return $this->shouldLockAccount($username);
    }
    
    // مسح محاولات تسجيل الدخول الفاشلة بعد تسجيل دخول ناجح
    public function clearAttempts($username) {
        $stmt = $this->db->prepare("DELETE FROM login_attempts WHERE username = ?");
        $stmt->execute([$username]);
    }
    
    // الحصول على وقت القفل المتبقي بالثواني
    public function getRemainingLockoutTime($username) {
        $stmt = $this->db->prepare("
            SELECT MAX(attempt_time) as last_attempt FROM login_attempts 
            WHERE username = ?
        ");
        $stmt->execute([$username]);
        
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$result['last_attempt']) {
            return 0;
        }
        
        $lastAttemptTime = strtotime($result['last_attempt']);
        $unlockTime = $lastAttemptTime + $this->lockoutDuration;
        $remaining = $unlockTime - time();
        
        return max(0, $remaining);
    }
}
?>
2-51
تنفيذ نظام المصادقة متعددة العوامل (MFA) باستخدام رموز تحقق مؤقتة يتم إرسالها عبر البريد الإلكتروني.
54-113
تنفيذ نظام لتتبع محاولات تسجيل الدخول الفاشلة وقفل الحساب بعد تجاوز الحد المسموح للمحاولات.

نصائح سريعة

  • استخدم دائمًا password_hash() و password_verify() للتعامل مع كلمات المرور.
  • لا تقم أبدًا بتخزين كلمات المرور بنص واضح أو استخدام خوارزميات تجزئة ضعيفة مثل MD5 أو SHA1.
  • استخدم password_needs_rehash() للتحقق مما إذا كان يجب تحديث تجزئة كلمة المرور باستخدام خوارزمية أقوى.
  • فرض سياسات قوية لكلمات المرور، مع طول كافٍ وتعقيد مناسب.
  • نفذ المصادقة متعددة العوامل (MFA) للحسابات الحساسة أو المهمة.
  • قم بتعطيل الحسابات مؤقتًا بعد عدة محاولات تسجيل دخول فاشلة لمنع هجمات القوة الغاشمة.
  • أعلم المستخدمين عن تسجيلات الدخول من أجهزة أو مواقع جديدة.

الختام

يتضمن هذا الدليل مقدمة شاملة لتطوير تطبيقات PHP الآمنة والفعالة. من أساسيات اللغة إلى التقنيات المتقدمة في التعامل مع قواعد البيانات وتنفيذ ممارسات الأمان، يمكنك الآن بناء تطبيقات ويب قوية وموثوقة.