توثيق PHP الشامل
مرحبًا بك في توثيق PHP الشامل باللغة العربية. هذا الدليل يقدم شرحًا تفصيليًا للغة PHP بدءًا من أساسياتها وحتى المفاهيم المتقدمة مثل البرمجة كائنية التوجه وأمان التطبيقات. يتضمن التوثيق أمثلة عملية وشرح مفصل سطرًا بسطر لتسهيل فهم المفاهيم والتقنيات المختلفة.
أساسيات PHP
PHP هي لغة برمجة نصية مفتوحة المصدر مصممة خصيصًا لتطوير تطبيقات الويب. تم إطلاقها في عام 1994 على يد راسموس ليردورف، وأصبحت واحدة من أكثر لغات البرمجة استخدامًا في تطوير الويب. دعنا نبدأ بالتعرف على أساسيات اللغة.
بناء الجملة الأساسي
ملفات PHP يمكن أن تحتوي على HTML وCSS وJavaScript بالإضافة إلى كود PHP. كود PHP يتم تنفيذه على الخادم، وينتج HTML يتم إرساله إلى المتصفح.
وسوم PHP
تبدأ وتنتهي كتابة كود PHP بوسوم خاصة:
<?php
// كود PHP هنا
?>
صيغة الملف
ملفات PHP يمكن أن تحتوي على كود HTML وPHP معًا:
<!DOCTYPE html>
<html>
<head>
<title>صفحة PHP</title>
</head>
<body>
<h1>مرحباً بالعالم!</h1>
<?php
echo "هذا نص من PHP!";
?>
</body>
</html>
echo
يستخدم لإخراج النص أو القيم إلى HTML. هنا سيتم عرض "هذا نص من PHP!" في المتصفح.
التعليقات
التعليقات في PHP تساعد على توثيق الكود وشرحه:
<?php
// هذا تعليق سطري واحد
# هذا أيضًا تعليق سطري واحد
/*
هذا تعليق
متعدد الأسطر
*/
echo "مرحباً بالعالم!"; // يمكن وضع تعليق بعد الكود
?>
نصائح سريعة
- في 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 . " متر";
?>
قواعد تسمية المتغيرات
هناك عدة قواعد يجب اتباعها عند تسمية المتغيرات في PHP:
- يجب أن يبدأ اسم المتغير بالرمز $ متبوعًا بحرف أو شرطة سفلية (_).
- يمكن أن يحتوي اسم المتغير على أحرف وأرقام وشرطات سفلية.
- لا يمكن أن يبدأ اسم المتغير برقم.
- أسماء المتغيرات في PHP حساسة لحالة الأحرف (case-sensitive).
<?php
// صحيح
$name = "أحمد";
$_name = "محمد";
$name1 = "علي";
$firstName = "خالد";
// خطأ
$1name = "عمر"; // لا يمكن أن يبدأ اسم المتغير برقم
$name-first = "سعيد"; // لا يمكن استخدام الشرطة (-)
// متغيرات مختلفة بسبب حساسية حالة الأحرف
$Name = "حسن";
$name = "حسين";
echo $Name; // سيطبع "حسن"
echo $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
?>
تحويل الأنواع
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
?>
نصائح سريعة
- استخدم الوظيفة
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 بواحد ثم استخدامه
?>
عوامل الإسناد
تستخدم لإسناد قيم للمتغيرات:
<?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; // "مرحباً بالعالم!"
?>
عوامل المقارنة
تستخدم لمقارنة قيمتين وإرجاع قيمة منطقية (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 (مع تحويل النوع)
?>
عوامل منطقية
تستخدم لإجراء عمليات منطقية وتجميع شروط:
<?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;
?>
نصائح سريعة
- انتبه للفرق بين == و === عند المقارنة. استخدم === عندما تريد التأكد من تطابق النوع والقيمة.
- العوامل && و || لها أولوية تقييم قصيرة - لن يتم تقييم الجزء الثاني إذا كانت النتيجة معروفة من الجزء الأول.
- هناك فرق في الأولوية بين && و 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 . "
";
?>
الفرق بين 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;
?>
الثوابت السحرية
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;
}
?>
نصائح سريعة
- أسماء الثوابت عادةً تكون بأحرف كبيرة مع استخدام الشرطة السفلية للفصل (مثل 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;
?>
عبارة 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 "منتصف يوم العمل! استمر في العمل الجيد.";
}
}
?>
استخدام الأقواس المتعرجة ومشكلات محتملة
عند استخدام عبارة 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 "مساء سعيد!";
?>
نصائح سريعة
- دائمًا استخدم الأقواس المتعرجة {} للتعبيرات الشرطية حتى لو كان هناك بيان واحد فقط، لتجنب الأخطاء المنطقية في المستقبل.
- استخدم العامل الثلاثي (؟:) لعبارات 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 "انطلق!";
?>
حلقة 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 "لن يتم تنفيذ هذا الكود أبدًا.";
}
?>
حلقة 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;
*/
?>
حلقة 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 . " ";
}
?>
التحكم في الحلقة: 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 "لم نجد المطلوب.";
}
?>
نصائح سريعة
- استخدم حلقة 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 "قيمة غير معروفة!";
}
?>
أهمية عبارة 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
?>
استخدام التعبيرات في 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
?>
نصائح سريعة
- تأكد دائمًا من وضع 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
?>
تعريف وتسمية الدوال
هناك بعض القواعد والممارسات التي يجب اتباعها عند تعريف الدوال:
<?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";
}
*/
?>
تسميات شائعة للدوال
اتفاقيات التسمية الشائعة للدوال في 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.
?>
تمرير الوسائط بالقيمة وبالمرجع
افتراضيًا، يتم تمرير الوسائط بالقيمة في 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
?>
عدد متغير من الوسائط
يمكنك تعريف دوال تقبل عددًا متغيرًا من الوسائط باستخدام ... (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
?>
نصائح سريعة
- استخدم الوسائط الاختيارية للقيم التي غالبًا ما تكون لها قيمة معينة.
- استخدم التمرير بالمرجع فقط عندما تحتاج إلى تعديل المتغيرات الأصلية.
- كن حذرًا عند استخدام عدد متغير من الوسائط، وتأكد من التحقق من قيم الوسائط قبل استخدامها.
- في 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
?>
قيم الإرجاع المتعددة
في 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)
?>
إعلان نوع الإرجاع (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;
}
?>
نصائح سريعة
- استخدم 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 . "
"; // خطأ: المتغير غير معرف
?>
استخدام الكلمة المفتاحية 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
?>
استخدام مصفوفة $GLOBALS
طريقة أخرى للوصول إلى المتغيرات العامة هي استخدام مصفوفة $GLOBALS المدمجة:
<?php
$counter = 0;
function increment() {
// استخدام مصفوفة $GLOBALS للوصول إلى المتغير العام
$GLOBALS['counter']++;
echo "القيمة داخل الدالة: " . $GLOBALS['counter'] . "
";
}
echo "القيمة قبل استدعاء الدالة: " . $counter . "
";
increment();
increment();
echo "القيمة بعد استدعاء الدالة: " . $counter . "
";
// النتيجة:
// القيمة قبل استدعاء الدالة: 0
// القيمة داخل الدالة: 1
// القيمة داخل الدالة: 2
// القيمة بعد استدعاء الدالة: 2
?>
المتغيرات الثابتة (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
?>
نصائح سريعة
- تجنب استخدام المتغيرات العامة حيثما أمكن لتقليل التداخل بين أجزاء البرنامج.
- استخدم وسائط الدوال لتمرير البيانات إلى داخل الدالة والقيم المرجعة لإخراج البيانات.
- استخدم المتغيرات الثابتة (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
?>
الإغلاقات (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
?>
استخدامات الدوال المجهولة
الدوال المجهولة مفيدة في العديد من الحالات، وخاصة مع دوال معالجة المصفوفات:
<?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'] . " سنة
";
}
?>
الدوال السهمية (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) . "
";
?>
نصائح سريعة
- استخدم الدوال المجهولة عندما تحتاج إلى دالة لاستخدام مرة واحدة أو لفترة قصيرة.
- استخدم 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 . "
";
}
?>
إنشاء المصفوفات الترابطية
المصفوفات الترابطية تستخدم سلاسل نصية كمفاتيح بدلاً من الأرقام:
<?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) . "
";
?>
مصفوفات مختلطة
يمكن أن تحتوي المصفوفات في PHP على مزيج من المفاتيح العددية والنصية:
<?php
// مصفوفة تجمع بين المفاتيح العددية والنصية
$mixed = [
"name" => "محمد",
42 => "الإجابة",
"colors" => ["أحمر", "أخضر"],
99
];
// ملاحظة: بعد 99، الفهرس التالي سيكون 100
$mixed[] = "عنصر جديد"; // سيكون المفتاح 100
// طباعة المصفوفة
print_r($mixed);
/*
النتيجة:
Array
(
[name] => محمد
[42] => الإجابة
[colors] => Array
(
[0] => أحمر
[1] => أخضر
)
[43] => 99
[100] => عنصر جديد
)
*/
?>
نصائح سريعة
- استخدم الصيغة المختصرة [] بدلاً من 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] => فراولة )
?>
دمج وتقسيم المصفوفات
يمكن دمج وتقسيم المصفوفات بعدة طرق مختلفة:
<?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 ) )
?>
تحويل المصفوفات
يمكن إجراء تحويلات مختلفة على المصفوفات:
<?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 )
?>
نصائح سريعة
- استخدم 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
?>
دوال الفرز المخصصة
يمكن استخدام دوال مخصصة للفرز عندما نحتاج إلى معايير فرز معقدة:
<?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
?>
نصائح سريعة
- استخدم 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"]) . "
";
}
?>
معالجة المصفوفات متعددة الأبعاد
يمكن معالجة المصفوفات متعددة الأبعاد باستخدام مجموعة متنوعة من الأساليب:
<?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 سنة
?>
نصائح سريعة
- استخدم 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(); // سارة محمد قامت بتسجيل الدخول
?>
دوال السحر (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 . "
"; // هاتف ذكي (نسخة)
?>
نصائح سريعة
- استخدم أسماء الفئات بصيغة المفرد (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
?>
الخصائص والطرق الثابتة (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
?>
نصائح سريعة
- استخدم مستوى الوصول 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
?>
الفئات النهائية والطرق النهائية
يمكن تحديد الفئات والطرق كنهائية (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(); // هذه فئة نهائية لا يمكن توريثها
?>
نصائح سريعة
- تذكر أن 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
?>
توريث الواجهات
يمكن للواجهات أن ترث من واجهات أخرى، مما يسمح بتكوين واجهات أكثر تخصصًا:
<?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
"; // سيتم عرضها
}
?>
نصائح سريعة
- جميع الطرق في الواجهة يجب أن تكون عامة (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");
?>
حل تعارضات السمات
قد تحدث تعارضات عند استخدام عدة سمات تحتوي على طرق بنفس الاسم. توفر 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
?>
السمات المتداخلة
يمكن للسمات استخدام سمات أخرى، مما يسمح بتركيب وتوليف السلوكيات:
<?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');
// المستخدم غير موجود
?>
نصائح سريعة
- استخدم السمات للوظائف المشتركة التي لا تناسب تمامًا نموذج الوراثة أو الواجهات.
- يمكن للسمات تعريف خصائص، وطرق، وطرق مجردة، وثوابت.
- السمات لا يمكن إنشاء كائنات منها مباشرة، فهي مصممة لإعادة استخدام الشفرة داخل الفئات فقط.
- استخدم 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, "سارة علي");
?>
تعامل أكثر تقدمًا مع مساحات الأسماء
يوفر 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);
}
}
?>
نصائح سريعة
- اتبع نمط 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();
?>
تنفيذ الاستعلامات
يمكن تنفيذ استعلامات 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();
?>
التعامل مع نتائج الاستعلام
توفر 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();
?>
نصائح سريعة
- استخدم النمط الكائني للاستفادة من ميزات البرمجة كائنية التوجه وتنظيم الكود بشكل أفضل.
- تأكد دائمًا من التحقق من الأخطاء بعد الاتصال بقاعدة البيانات وتنفيذ الاستعلامات.
- استخدم الاستعلامات المجهزة لتفادي حقن 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());
}
?>
تنفيذ الاستعلامات
يوفر 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());
}
?>
الاستعلامات المجهزة في 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());
}
?>
نصائح سريعة
- استخدم 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() . "
";
}
?>
العمل مع المعاملات في 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();
}
?>
نصائح سريعة
- استخدم المعاملات عندما تكون لديك مجموعة من العمليات المترابطة التي يجب أن تنجح أو تفشل كوحدة واحدة.
- تأكد من مراعاة مستويات العزل (Isolation Levels) المناسبة لتطبيقك اعتمادًا على متطلبات التزامن.
- لا تستخدم المعاملات لفترات طويلة، حيث قد يؤدي ذلك إلى مشكلات في الأداء والتزامن.
- تأكد دائمًا من معالجة الاستثناءات والتراجع عن المعاملات في حالة فشل أي عملية.
- استخدم وضع التأكيد اليدوي (تعطيل autocommit) فقط أثناء المعاملة، وأعد تمكينه بعد الانتهاء.
الاستعلامات المجهزة
الاستعلامات المجهزة (Prepared Statements) هي طريقة آمنة وفعّالة لتنفيذ استعلامات SQL. تفصل هذه التقنية بين الاستعلام والبيانات، مما يوفر حماية من هجمات حقن SQL ويحسّن الأداء عند تنفيذ استعلامات متشابهة متكررة.
مزايا الاستعلامات المجهزة
توفر الاستعلامات المجهزة عدة مزايا هامة:
- الأمان: حماية من هجمات حقن SQL عن طريق فصل بنية الاستعلام عن البيانات.
- الأداء: يتم تحليل الاستعلام وإعداده مرة واحدة، ثم يمكن تنفيذه عدة مرات بقيم مختلفة.
- سهولة الاستخدام: تبسيط التعامل مع أنواع البيانات المختلفة وتحسين قابلية قراءة الكود.
تدعم كل من 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() . "
";
}
?>
الاستعلامات المجهزة في 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();
?>
نصائح سريعة
- استخدم دائمًا الاستعلامات المجهزة عند التعامل مع مدخلات المستخدم للحماية من هجمات حقن 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 "الحالة غير صالحة
";
}
?>
التحقق من نماذج 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>
نصائح سريعة
- اتبع مبدأ "لا تثق أبدًا بمدخلات المستخدم" وتحقق من جميع البيانات الواردة.
- استخدم التحقق من المدخلات على جانب العميل للتحسين من تجربة المستخدم، ولكن لا تعتمد عليه للأمان.
- نفذ دائمًا التحقق من المدخلات على جانب الخادم بغض النظر عن التحقق على جانب العميل.
- استخدم قوائم مسموح بها (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();
// ملاحظة: في الواقع، لا ينبغي تخزين كلمات المرور بنص واضح
// سنرى الطريقة الصحيحة في قسم تأمين كلمات المرور
?>
أفضل الممارسات لمنع حقن SQL
-
استخدم الاستعلامات المجهزة:
هذه هي الطريقة الأكثر فعالية لمنع حقن SQL، حيث تفصل بين بنية الاستعلام والبيانات. تدعم كل من PDO وMySQLi هذه الميزة.
-
استخدم وظائف الهروب (Escaping):
إذا لم تتمكن من استخدام الاستعلامات المجهزة، فاستخدم الدوال المناسبة لتهرب الأحرف الخاصة في المدخلات (mysqli_real_escape_string أو PDO::quote).
-
تحقق من نوع البيانات:
تحويل المدخلات إلى النوع المناسب باستخدام دوال مثل intval() للأعداد الصحيحة و floatval() للأعداد العشرية.
-
استخدم قوائم مسموح بها للمعرّفات:
عند استخدام مدخلات المستخدم كأسماء أعمدة أو جداول، تحقق من أنها موجودة في قائمة معرّفات مسموح بها.
-
استخدم أقل امتيازات ممكنة:
قم بإعداد حساب قاعدة البيانات بأقل الصلاحيات اللازمة للتطبيق، للحد من الضرر في حال نجاح الهجوم.
<?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);
}
?>
نصائح سريعة
- استخدم دائمًا الاستعلامات المجهزة مع المعاملات المربوطة للتعامل مع مدخلات المستخدم في استعلامات SQL.
- لا تبني استعلامات SQL بتركيب سلاسل النصوص مباشرة من المدخلات.
- استخدم PDO بدلاً من mysqli حيثما أمكن، فهو يوفر تجريدًا أفضل ودعمًا لقواعد بيانات متعددة.
- تحقق من صحة ونوع البيانات قبل استخدامها في استعلامات SQL.
- استخدم أقل امتيازات ممكنة في حساب قاعدة البيانات المستخدم في التطبيق.
- استخدم ORM (مثل Doctrine أو Eloquent) حيثما أمكن، فهي توفر طبقة إضافية من الحماية.
منع هجمات XSS
هجمات Cross-Site Scripting (XSS) هي نوع من الهجمات حيث يتم حقن أكواد JavaScript ضارة في صفحات الويب التي يعرضها المستخدمون الآخرون. يمكن أن تؤدي هذه الهجمات إلى سرقة جلسات المستخدمين، وسرقة الكوكيز، وتنفيذ أكواد ضارة في المتصفح.
أنواع هجمات XSS
هناك ثلاثة أنواع رئيسية من هجمات XSS:
-
الهجمات المخزنة (Stored XSS):
يتم تخزين البرمجيات النصية الضارة في قاعدة البيانات وتُعرض لكل مستخدم يزور الصفحة المتأثرة (مثل التعليقات في المدونات).
-
الهجمات المنعكسة (Reflected XSS):
يتم إرسال البرمجيات النصية الضارة في طلب HTTP ثم تنعكس على المستخدم دون تخزينها (مثل نتائج البحث).
-
هجمات 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>';
?>
استراتيجيات إضافية لمنع XSS
-
استخدام Content Security Policy (CSP):
إضافة رأس HTTP للتحكم في المصادر المسموح بها لتحميل المحتوى وتنفيذ البرمجيات النصية.
-
استخدام HttpOnly للكوكيز:
تعيين العلامة HttpOnly على الكوكيز الحساسة لمنع الوصول إليها من خلال JavaScript.
-
التحقق من المدخلات:
تنظيف وتحقق من المدخلات قبل تخزينها أو عرضها.
-
استخدام مكتبات مضمنة للمعالجة:
استخدام مكتبات مثل 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);
}
?>
نصائح سريعة
- قم دائمًا بترميز المخرجات باستخدام 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:
- المستخدم مسجل دخوله إلى موقع بنكه (bank.example.com).
- يزور المستخدم موقعًا ضارًا (malicious.example.com).
- يحتوي الموقع الضار على صورة مخفية مع طلب:
<img src="https://bank.example.com/transfer?to=attacker&amount=1000" style="display:none">
- يقوم المتصفح بإرسال الطلب تلقائيًا مع كوكيز جلسة المستخدم.
- يتم تنفيذ التحويل المصرفي دون علم المستخدم أو موافقته.
استخدام رموز 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>
استراتيجيات إضافية لمنع CSRF
-
استخدام SameSite للكوكيز:
تعيين خاصية SameSite للكوكيز (Strict أو Lax) لمنع إرسالها في طلبات متعددة المواقع.
-
التحقق من مصدر الطلب:
التحقق من رأس Referer و/أو Origin للتأكد من أن الطلب قادم من موقعك.
-
استخدام الطلبات المزدوجة:
طلب تأكيد إضافي للعمليات الحساسة، مثل نافذة تأكيد أو إدخال كلمة المرور مرة أخرى.
<?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 '';
// في معالج النموذج
try {
$csrf->validateRequest();
// معالجة البيانات
} catch (Exception $e) {
die($e->getMessage());
}
?>
نصائح سريعة
- استخدم دائمًا رموز 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;
}
?>
أفضل الممارسات الإضافية لتأمين كلمات المرور
-
تعقيد كلمة المرور:
فرض سياسة قوية لكلمات المرور تتطلب طولًا كافيًا وتعقيدًا (أحرف كبيرة وصغيرة، أرقام، رموز).
-
المصادقة متعددة العوامل (MFA):
توفير خيار المصادقة بعاملين أو أكثر لزيادة الأمان (مثل رمز البريد الإلكتروني، رمز SMS، أو تطبيق المصادقة).
-
تأخير الاستجابة:
إضافة تأخير في الاستجابة بعد محاولات تسجيل دخول فاشلة للحد من هجمات القوة الغاشمة.
-
سجلات المحاولات:
تسجيل محاولات تسجيل الدخول الفاشلة وإعلام المستخدم عند حدوث نشاط مشبوه.
<?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);
}
}
?>
نصائح سريعة
- استخدم دائمًا password_hash() و password_verify() للتعامل مع كلمات المرور.
- لا تقم أبدًا بتخزين كلمات المرور بنص واضح أو استخدام خوارزميات تجزئة ضعيفة مثل MD5 أو SHA1.
- استخدم password_needs_rehash() للتحقق مما إذا كان يجب تحديث تجزئة كلمة المرور باستخدام خوارزمية أقوى.
- فرض سياسات قوية لكلمات المرور، مع طول كافٍ وتعقيد مناسب.
- نفذ المصادقة متعددة العوامل (MFA) للحسابات الحساسة أو المهمة.
- قم بتعطيل الحسابات مؤقتًا بعد عدة محاولات تسجيل دخول فاشلة لمنع هجمات القوة الغاشمة.
- أعلم المستخدمين عن تسجيلات الدخول من أجهزة أو مواقع جديدة.
الختام
يتضمن هذا الدليل مقدمة شاملة لتطوير تطبيقات PHP الآمنة والفعالة. من أساسيات اللغة إلى التقنيات المتقدمة في التعامل مع قواعد البيانات وتنفيذ ممارسات الأمان، يمكنك الآن بناء تطبيقات ويب قوية وموثوقة.