كيف يعمل الويب فعلياً؟ — العقلية الخلفية
The Request/Response Mental Modelدورة الطلب والاستجابة
Client ↔ Server1. المتصفح يطلب
يكتب المستخدم العنوان، فيرسل المتصفح طلب HTTP إلى الخادم: "أعطني هذه الصفحة"
2. الخادم يُشغّل PHP
يجد ملف .php، يُنفّذ الكود من أعلى لأسفل، قد يقرأ من قاعدة بيانات
3. يُولَّد HTML
PHP تنتج نصاً نهائياً — مجرد HTML خام، لا أثر لكود PHP فيه
4. المتصفح يستقبل
يستقبل المتصفح هذا الـ HTML ويرسمه على الشاشة كصفحة ويب
لماذا "Server-Side" بالضبط؟
// ملف: hello.php — هذا ما يكتبه المطوّر
<?php
$name = "يحيى";
echo "<h1>أهلاً، " . $name . "</h1>";
?>
أين تعيش بياناتك؟ خريطة الذاكرة في كل طلب
| مكان التخزين | يعيش لمدة | متى تستخدمه |
|---|---|---|
متغير عادي $x | طلب واحد فقط، ثم يُمحى | حسابات مؤقتة داخل نفس الصفحة |
الجلسة $_SESSION | طوال زيارة المستخدم (بملف على الخادم) | "هل المستخدم مسجّل دخول؟" |
الكوكي $_COOKIE | أيام/شهور (مخزّن في متصفح المستخدم) | تذكّر تفضيلات، "تذكّرني" |
| ملف/قاعدة بيانات | دائم حتى تحذفه أنت | أي بيانات حقيقية يجب أن تبقى للأبد |
ما هي PHP ولماذا تتعلمها؟
What is PHP & Why?لماذا تستحق PHP وقتك في 2026؟
2. منتشرة في فرص العمل الحقيقية — تشغّل نسبة ضخمة من مواقع الويب حول العالم، وLaravel وحدها سوق عمل كامل.
3. مصمَّمة للويب من الأساس — التعامل مع HTTP وFormsوالـ Sessions مدمج في اللغة نفسها، لا تحتاج مكتبات خارجية لتبدأ.
أين تتموضع PHP في عالم تطوير الويب؟
// PHP تستطيع أيضاً أن "تكتب" HTML مباشرة (Server-Rendered)
<!DOCTYPE html>
<html>
<body>
<h1><?php echo "مرحباً بالعالم"; ?></h1>
</body>
</html>
البيئة والتثبيت
Local Environment Setupالتثبيت السريع — السيرفر المدمج في PHP
PHP Built-in Serverphp.net أو عبر مدير الحزم في نظامك، ثم:# تأكد من التثبيت
php -v
# شغّل سيرفر تطوير في مجلد مشروعك
php -S localhost:8000
# الآن افتح المتصفح على
# http://localhost:8000
أول ملف PHP وقاعدة التسمية
.php. الكود نفسه يبدأ بـ <?php وينتهي اختيارياً بـ ?> — لكن في الملفات التي تحتوي PHP فقط (دون HTML بعدها)، يُفضَّل عدم كتابة ?> النهائية لتجنّب مشاكل المسافات الخفية.// index.php
<?php
declare(strict_types=1); // أول سطر دائماً — صرامة الأنواع
echo "أهلاً من PHP " . PHP_VERSION;
// لاحظ: لا يوجد ?> في النهاية — هذا مقصود ومُتعمَّد
أداة لا غنى عنها: Composer
PHP Package Managergetcomposer.org حتى يكون جاهزاً.بنية الجملة الأساسية
Basic Syntaxالفاصلة المنقوطة والتعليقات
// كل جملة (statement) تنتهي بفاصلة منقوطة
$x = 5;
// تعليق سطر واحد
# تعليق سطر واحد — أقل استخداماً
/* تعليق
متعدد الأسطر */
المسافات والحساسية لحالة الأحرف
$Name ≠ $name)، أما أسماء الدوال والكلاسات فغير حساسة — مع ذلك التزم بنفس الحالة دائماً لقابلية القراءة.$name = "Yahya";
$Name = "Different!"; // متغير مختلف تماماً
echo $name; // Yahya
echo $Name; // Different!
المتغيرات وأنواع البيانات
Variables & Data Typesأنواع البيانات الأساسية الثمانية
// string — نص
$name = "Yahya Al-sulami";
// int — رقم صحيح
$age = 25;
$big = 1_000_000; // فواصل تنظيمية، PHP 7.4+
// float — رقم عشري
$price = 99.99;
// bool — صح أو غلط
$login = true;
// null — لا قيمة على الإطلاق
$data = null;
// array — مصفوفة (قسم كامل قادم)
$tags = ["php", "laravel"];
// object — كائن (قسم OOP قادم)
$dt = new DateTime();
// resource — مقبض ملف/اتصال مفتوح
$fp = fopen("f.txt", "r");
فحص وتحويل الأنواع
$val = "42";
gettype($val); // "string"
is_string($val); // true
is_numeric($val); // true
var_dump($val); // string(2) "42"
// Casting — تحويل صريح للنوع
$i = (int) $val; // 42
$f = (float) $val; // 42.0
$s = (string)123; // "123"
الثوابت Constants
const API_VERSION = "v2.5"; // الطريقة الحديثة المفضّلة
define("SITE_NAME", "Yahya.DEV"); // طريقة قديمة، ديناميكية
echo API_VERSION; // v2.5
echo PHP_VERSION; // ثابت مدمج، مثل 8.4.1
نطاق المتغيرات Scope
$site = "yahya.dev"; // global
function show(string $url): void {
echo $url; // تُمرَّر كمعطى، لا كـ global
}
show($site);
declare(strict_types=1); في أول سطر بعد <?php في كل ملف. هذا يمنع PHP من "تحويل" الأنواع بصمت (مثل قبول النص "5" في مكان يطلب int)، ويفجّر الأخطاء فوراً بدل أن تختفي وتظهر لاحقاً كبَّق غامض.العوامل والمقارنة
Operators & Comparisonحسابية + اختصارات
$a = 15; $b = 4;
echo $a + $b; // 19
echo $a % $b; // 3 باقي القسمة
echo $a ** $b; // 50625 أُس
$x = 10;
$x += 5; // 15
$x++; // post-increment
المقارنة == مقابل ===
// == قيمة فقط — مصدر خطر
0 == "foo" // false في PHP 8 (تم تصحيحها!)
"1" == "01" // true — كلاهما يُفسَّر كرقم
// === قيمة + نوع — الأصحّ دائماً
5 === "5" // false — int مقابل string
5 === 5 // true
// عامل الصاروخ Spaceship
1 <=> 2 // -1 (أصغر)
2 <=> 2 // 0 (متساوٍ)
العوامل المنطقية
true && false; // false — AND
true || false; // true — OR
!true; // false — NOT
عوامل خاصة بـ PHP الحديثة
// Null Coalescing — قيمة افتراضية إن كانت null/غير موجودة
$name = $_GET['name'] ?? 'زائر';
// Nullsafe — استدعاء أمِن بدون فحص يدوي
$city = $user?->getAddress()?->city;
// لو $user أو getAddress() كانت null → النتيجة null بدل Fatal Error
الشروط ومُعبِّر match
Conditionals & matchif / elseif / else
$age = 20;
if ($age < 13) {
echo "طفل";
} elseif ($age < 18) {
echo "مراهق";
} else {
echo "بالغ"; // النتيجة هنا
}
match — المُعبِّر الحديث (PHP 8+)
match يستخدم مقارنة صارمة (===) دائماً، ولا يحتاج break;، ويُرجع قيمة مباشرة (Expression لا Statement).$status = 404;
$msg = match(true) {
$status >= 500 => "خطأ خادم",
$status >= 400 => "خطأ طلب",
$status >= 200 => "نجاح",
default => "غير معروف",
};
echo $msg; // "خطأ طلب"
العامل الثلاثي والاختصار
$age = 17;
$can = ($age >= 18) ? "نعم" : "لا";
// الاختصار — مفيد مع قيم قد تكون فاضية
$user = $input ?: "ضيف";
switch القديم — متى لا تزال مفيدة
switch فقط حين تحتاج fallthrough مقصوداً (تنفيذ أكثر من حالة بنفس الكود) — وإلا فـ match أوضح وأأمن.switch ($day) {
case 'Sat':
case 'Fri':
echo "عطلة";
break;
default:
echo "يوم دوام";
}
الحلقات التكرارية
Loopsfor — عندما تعرف عدد التكرارات
for ($i = 1; $i <= 5; $i++) {
echo "التكرار رقم $i" . PHP_EOL;
}
while و do-while
$n = 0;
while ($n < 3) {
echo $n;
$n++;
}
// do-while: يُنفَّذ مرة واحدة أولاً، ثم يفحص الشرط
do {
echo "مرة واحدة على الأقل";
} while (false);
foreach — الأكثر استخداماً مع المصفوفات
$skills = ["PHP", "Laravel", "MySQL"];
foreach ($skills as $skill) {
echo $skill . PHP_EOL;
}
// مع المفتاح أيضاً (Associative)
$user = ["name" => "Yahya", "role" => "Dev"];
foreach ($user as $key => $value) {
echo "$key: $value" . PHP_EOL;
}
break و continue
foreach ([1,2,3,4,5] as $n) {
if ($n === 3) continue; // تخطّى 3
if ($n === 5) break; // أوقف عند 5
echo $n; // يطبع 1 2 4
}
while(true) بدون شرط خروج واضح (break) تُجمّد السيرفر فعلياً وتستهلك الموارد. تأكد دائماً أن هناك مساراً منطقياً للخروج.تطبيق عملي — حاسبة طرفية + لعبة تخمين الرقم
مستوى: أساسيالآن وقد عرفت المتغيرات والشروط والحلقات، حان وقت التطبيق الحقيقي — بدون أن أعطيك الحل جاهزاً. ابنِ سكريبت PHP واحد (شغّله من Terminal بـ php calc.php) يقوم بـ:
- يُعرّف رقمين ثابتين وعملية (جمع/طرح/ضرب/قسمة) كمتغيرات، ويطبع نتيجة العملية بشكل منسَّق.
- يستخدم
matchلاختيار العملية بدلاً من سلسلةifطويلة. - التحدي الإضافي: أضف لعبة "خمّن الرقم" — يولّد البرنامج رقماً عشوائياً بين 1 و100 بدالة
rand()، ويستخدم حلقةwhileمع قائمة تخمينات مكتوبة مسبقاً في مصفوفة، ويطبع "أكبر/أصغر/صحيح!" لكل تخمين حتى يصل للرقم الصحيح أو تنتهي المحاولات.
الدوال Functions
Functions & Modern Signaturesدالة بسيطة مع أنواع صريحة
function greet(string $name): string {
return "أهلاً، $name!";
}
echo greet("يحيى"); // أهلاً، يحيى!
قيم افتراضية ومعطيات اختيارية
function power(int $base, int $exp = 2): int {
return $base ** $exp;
}
echo power(5); // 25 — exp اختياري
echo power(5, 3); // 125
Named Arguments (PHP 8+)
function createUser(string $name, bool $isAdmin = false, ?string $bio = null) { ... }
createUser(name: "يحيى", isAdmin: true);
// لا حاجة لتمرير $bio — وضوح كامل دون الترتيب
Arrow Functions و Closures
array_map القادمة).// Closure تقليدي
$double = function(int $n): int { return $n * 2; };
// Arrow Function (PHP 7.4+) — أقصر، ويرى المتغيرات المحيطة تلقائياً
$tax = 0.15;
$withTax = fn(float $price): float => $price * (1 + $tax);
echo $withTax(100); // 115
تمرير بالمرجع، Variadic، و Union Types
// تمرير بالمرجع & — يعدّل المتغير الأصلي مباشرة (استخدمه بحذر)
function addBonus(int &$salary): void { $salary += 500; }
$s = 3000; addBonus($s); // $s أصبحت 3500
// Variadic — عدد غير محدود من المعطيات
function sum(int ...$numbers): int {
return array_sum($numbers);
}
echo sum(1, 2, 3, 4); // 10
// Union Types (PHP 8+) — أكثر من نوع مقبول
function formatId(int|string $id): string {
return "#" . $id;
}
المصفوفات Arrays
Arrays & Built-in Functionsمصفوفة مرقَّمة (Indexed)
$stack = ["PHP", "Laravel", "MySQL"];
echo $stack[0]; // "PHP"
$stack[] = "Node.js"; // إضافة في النهاية
echo count($stack); // 4
مصفوفة ترابطية (Associative)
$user = [
"name" => "Yahya",
"role" => "Full-Stack Dev",
"exp" => 3,
];
echo $user["name"]; // "Yahya"
$user["exp"]++; // تعديل قيمة مباشرة
المصفوفات متعددة الأبعاد — أساس كل "صف بيانات"
$tasks = [
["id" => 1, "title" => "تعلّم PHP", "done" => true],
["id" => 2, "title" => "بناء المشروع", "done" => false],
];
foreach ($tasks as $task) {
echo $task["title"] . ($task["done"] ? " ✓" : " …");
}
دوال التحويل: map / filter / reduce
$nums = [1,2,3,4,5];
// array_map — حوِّل كل عنصر
$squared = array_map(fn($n) => $n ** 2, $nums);
// array_filter — احتفظ بما يطابق شرطاً
$even = array_filter($nums, fn($n) => $n % 2 === 0);
// array_reduce — اختزل لقيمة واحدة
$total = array_reduce($nums, fn($carry, $n) => $carry + $n, 0);
فرز وبحث
$nums = [5,2,8,1];
sort($nums); // [1,2,5,8] — يعدّل المصفوفة مباشرة
rsort($nums); // عكسي
in_array(8, $nums); // true
array_search(8, $nums); // يرجع المفتاح/الموضع
النصوص Strings
String Functions & Formattingالدمج والتنسيق
$name = "يحيى"; $role = "مطوّر";
// String Interpolation — الأسهل والأكثر استخداماً
echo "الاسم: $name، الدور: $role";
// Concatenation بالنقطة
echo "الاسم: " . $name;
// sprintf — لتنسيق دقيق (أرقام، محاذاة)
echo sprintf("السعر: %.2f$", 49.5); // 49.50$
قياس وتقطيع
$s = " Yahya.DEV ";
strlen($s); // عدد البايتات
mb_strlen($s); // عدد الأحرف — مهم جداً للعربي/الإيموجي
trim($s); // "Yahya.DEV" — إزالة المسافات الجانبية
substr($s, 2, 5); // قطعة من النص
explode(".", "Yahya.DEV"); // ["Yahya","DEV"]
implode(", ", ["a","b"]); // "a, b"
بحث وتبديل
str_contains("Hello World", "World"); // true (8+)
str_starts_with("Hello", "He"); // true (8+)
str_replace("World", "PHP", "Hello World"); // Hello PHP
strtolower("YAHYA"); strtoupper("yahya");
⚠️ النص الآمن قبل الطباعة
htmlspecialchars() قبل طباعته في صفحة HTML.$comment = $_POST['comment'] ?? '';
echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
تطبيق عملي — محلّل نصوص
مستوى: متوسطوقت دمج الدوال والمصفوفات والنصوص في برنامج واحد. ابنِ سكريبت analyzer.php يستقبل جملة طويلة مكتوبة كمتغير، ثم:
- يحسب عدد الكلمات (استخدم
explodeعلى الفراغ ثمcount). - يكتشف ويطبع أطول كلمة في الجملة (لفّ على المصفوفة، قارن بـ
mb_strlen). - يبني مصفوفة ترابطية تُحصي كل كلمة وعدد تكرارها في الجملة.
- التحدي الإضافي: اجعل الدالة الرئيسية تستقبل الجملة كمعطى (Parameter) لا متغيراً عاماً، وأرجِع النتيجة كمصفوفة واحدة منظَّمة بدل طباعتها مباشرة داخل الدالة — مبدأ سيخدمك كثيراً في المشروع التطبيقي القادم.
الفورمز والـ Superglobals
Forms & $_GET / $_POST / $_SERVER$_GET — بيانات من رابط الصفحة
page.php?id=5&sort=asc. مناسبة للفلترة والتصفّح، غير مناسبة لكلمات المرور (تظهر في الرابط نفسه).// الرابط: page.php?id=5
$id = $_GET['id'] ?? null;
echo "المعرف: $id";
$_POST — بيانات نموذج HTML
<form method="POST" action="save.php">
<input name="email" type="email">
<button type="submit">إرسال</button>
</form>
// save.php
$email = $_POST['email'] ?? '';
التحقّق من الطريقة والمعطيات معاً
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['email']) || empty(trim($_POST['email']))) {
die("الإيميل مطلوب");
}
// كل شيء سليم، استمر بالمعالجة
}
$_SERVER — معلومات عن الطلب نفسه
$_SERVER['REQUEST_METHOD']; // GET / POST
$_SERVER['HTTP_HOST']; // domain.com
$_SERVER['REQUEST_URI']; // /page.php?id=5
$_SERVER['REMOTE_ADDR']; // IP الزائر
$_GET أو $_POST هي غير موثوقة بالكامل حتى تتحقق منها أنت. لا تفترض أبداً وجودها أو صحة نوعها — استخدم ?? وisset() دائماً.الجلسات والكوكيز
Sessions & Cookiesبدء جلسة وتخزين بيانات
// أول سطر تماماً في كل صفحة تستخدم الجلسة
session_start();
$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'yahya';
// في صفحة أخرى، بعد session_start() أيضاً
echo $_SESSION['username'] ?? 'زائر';
تسجيل الخروج وإنهاء الجلسة
session_start();
$_SESSION = []; // إفراغ كل البيانات
session_destroy(); // حذف الجلسة من الخادم
header("Location: /login.php");
exit; // مهم جداً بعد header(Location)
الكوكيز — تخزين على جهاز المستخدم
setcookie("theme", "dark", time() + (86400 * 30)); // 30 يوم
echo $_COOKIE["theme"] ?? "light";
Flash Messages — رسالة تظهر مرة واحدة
// بعد حفظ ناجح
$_SESSION['flash'] = 'تم الحفظ بنجاح ✓';
header("Location: /list.php"); exit;
// في list.php — اعرض ثم احذف فوراً
if (!empty($_SESSION['flash'])) {
echo htmlspecialchars($_SESSION['flash']);
unset($_SESSION['flash']);
}
أساسيات الأمان
XSS, CSRF & Input ValidationXSS — حقن سكريبت عبر المواقع
Cross-Site Scripting<script> يُنفَّذ في متصفح ضحية آخر يزور الصفحة.// المستخدم كتب في خانة "الاسم": <script>alert('hacked')</script>
// ❌ خطير — يُنفَّذ السكريبت فعلياً عند أي زائر
echo "أهلاً " . $_GET['name'];
// ✅ آمن — يتحوّل السكريبت لنص عادي غير قابل للتنفيذ
echo "أهلاً " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
CSRF — اختطاف الفورم
Cross-Site Request Forgery// عند توليد الفورم
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
// <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
// عند استقبال الفورم
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
die('طلب غير موثوق');
}
hash_equals() لا === عند مقارنة الـ Tokens — تقاوم هجمات Timing Attack.تشفير كلمات المرور — لا تخزّنها أبداً كنص صريح
// عند التسجيل
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
// خزّن $hash في قاعدة البيانات — لاحقاً مع SQL
// عند تسجيل الدخول
if (password_verify($inputPassword, $hash)) {
echo "دخول صحيح";
}
التحقق من المدخلات Validation
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'بريد إلكتروني غير صحيح';
}
if (mb_strlen($_POST['title'] ?? '') < 3) {
$errors[] = 'العنوان قصير جداً';
}
مطبات برمجية — الفورمز والأمان
Common Pitfalls & Debugging?? أو isset() دائماً قبل القراءة المباشرة.header() فقط يضيف تعليمة للمتصفح — لا تُوقف تنفيذ سكريبت PHP. أخطر مظهر لهذا: كود حساس يُنفَّذ بعد محاولة "حماية" غير مكتملة.session_start() وheader() يحتاجان إرسال رؤوس HTTP قبل أي إخراج نصّي — حتى مسافة فاضية قبل <?php تُحسب كإخراج.hash_equals مصمَّمة لمقاومة هذا تحديداً.تطبيق عملي — نظام تسجيل دخول آمن (بدون قاعدة بيانات بعد)
مستوى: متوسط-متقدمهذا التطبيق هو أول "تمرين أمان حقيقي" في الدليل. ابنِ نظاماً من ملفين login.php وdashboard.php:
- خزّن "مستخدماً واحداً" مؤقتاً كمصفوفة ثابتة في الكود، باسم مستخدم وكلمة مرور مشفّرة بـ
password_hash. - فورم
login.phpيحتوي حقل CSRF Token مخفي، ويتحقق منه عند الإرسال بـhash_equals. - عند نجاح تسجيل الدخول: خزّن حالة الدخول في
$_SESSIONوأعد التوجيه لـdashboard.php(لا تنسexit;). dashboard.phpيجب أن يطرد أي زائر غير مسجَّل دخوله فوراً لصفحة الدخول.- التحدي الإضافي: أضف "Rate Limiting" بسيط — إن فشل تسجيل الدخول 3 مرات متتالية، اعرض رسالة تأخير قبل السماح بمحاولة جديدة (خزّن عدّاد المحاولات في الجلسة).
OOP — الأساسيات
Object-Oriented Programming Basicsأول Class — وConstructor Property Promotion (PHP 8+)
class Task {
public function __construct(
public readonly int $id,
public string $title,
public bool $done = false,
) {}
public function markDone(): void {
$this->done = true;
}
}
$t = new Task(1, "تعلّم OOP");
$t->markDone();
echo $t->title; // "تعلّم OOP"
Visibility — public / private / protected
class BankAccount {
private float $balance = 0; // لا يُلمس من الخارج
public function deposit(float $amount): void {
if ($amount <= 0) throw new InvalidArgumentException("قيمة غير صالحة");
$this->balance += $amount;
}
public function getBalance(): float { return $this->balance; }
}
static — ينتمي للكلاس لا للكائن
class Counter {
private static int $total = 0;
public function __construct() { static::$total++; }
public static function getTotal(): int { return static::$total; }
}
new Counter(); new Counter();
echo Counter::getTotal(); // 2
OOP — المستوى المتقدم
Inheritance, Interfaces, Traits & Enumsالوراثة Inheritance
abstract class Repository {
abstract public function all(): array;
public function count(): int { return count($this->all()); }
}
class TaskRepository extends Repository {
public function all(): array { return ["مهمة 1", "مهمة 2"]; }
}
$repo = new TaskRepository();
echo $repo->count(); // 2 — موروثة جاهزة
Interfaces — التعاقد الذي يجعل مشروعك "قابلاً للاستبدال"
interface TaskRepositoryInterface {
public function all(): array;
public function create(array $data): void;
}
// اليوم: تخزين في ملف JSON
class JsonTaskRepository implements TaskRepositoryInterface { /* ... */ }
// لاحقاً في كورس SQL: تخزين في قاعدة بيانات — نفس التعاقد بالضبط
// class PdoTaskRepository implements TaskRepositoryInterface { /* ... */ }
Traits — مشاركة سلوك دون وراثة
trait HasTimestamps {
public ?string $createdAt = null;
public function touch(): void {
$this->createdAt = date("Y-m-d H:i");
}
}
class Task { use HasTimestamps; }
Enums — بدائل القيم الثابتة (PHP 8.1+)
"pending" منتشرة في الكود (وعرضة للأخطاء الإملائية)، الـ Enum يعطيك قيماً محدودة وآمنة.enum TaskStatus: string {
case Pending = 'pending';
case Done = 'done';
public function label(): string {
return match($this) {
self::Pending => "قيد الانتظار",
self::Done => "مكتملة",
};
}
}
$status = TaskStatus::Pending;
echo $status->label(); // "قيد الانتظار"
readonly Properties (PHP 8.1+)
class User {
public function __construct(
public readonly int $id,
) {}
}
$u = new User(1);
$u->id = 2; // ❌ Error: Cannot modify readonly property
معالجة الأخطاء
Exceptions & Error Handlingtry / catch / finally
try {
$result = 10 / $divisor;
if ($divisor === 0) {
throw new DivisionByZeroError("القسمة على صفر");
}
} catch (DivisionByZeroError $e) {
echo "خطأ: " . $e->getMessage();
} finally {
echo "ينفَّذ دائماً، نجح أو فشل";
}
Custom Exceptions — أخطاء بمعنى من مشروعك
class TaskNotFoundException extends Exception {}
function findTask(int $id, array $tasks): array {
foreach ($tasks as $t) {
if ($t['id'] === $id) return $t;
}
throw new TaskNotFoundException("المهمة #$id غير موجودة");
}
التقاط أكثر من نوع، والتسلسل الهرمي للأخطاء
try {
// كود قد يفجّر أكثر من نوع خطأ
} catch (TaskNotFoundException|InvalidArgumentException $e) {
error_log($e->getMessage()); // سجِّل الخطأ للمطوّر
echo "حدث خطأ، حاول مرة أخرى"; // رسالة عامة آمنة للمستخدم
}
الملفات و Namespaces
File Handling, Namespaces & Autoloadingrequire المتكررة.القراءة والكتابة في الملفات
// كتابة (تنشئ الملف إن لم يكن موجوداً)
file_put_contents("data/log.txt", "سجل جديد\n", FILE_APPEND);
// قراءة كاملة
$content = file_get_contents("data/log.txt");
// قراءة وكتابة JSON — ستستخدمها مباشرة في المشروع
$tasks = json_decode(file_get_contents("data/tasks.json"), true);
file_put_contents("data/tasks.json", json_encode($tasks, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
Namespaces — تجنّب تعارض أسماء الكلاسات
// ملف: src/Models/Task.php
namespace App\Models;
class Task { /* ... */ }
// ملف آخر يستخدمه
use App\Models\Task;
$t = new Task(1, "عنوان");
Autoloading بـ Composer — وداعاً لـ require اليدوي
require لكل ملف كلاس تستخدمه، تخبر Composer بقاعدة واحدة (PSR-4) فيقوم بتحميل أي كلاس تحتاجه تلقائياً عند أول استخدام.// composer.json
{
"autoload": {
"psr-4": { "App\\": "src/" }
}
}
// بعد تشغيل: composer dump-autoload
// في أي ملف دخول، سطر واحد فقط:
require "vendor/autoload.php";
use App\Models\Task; // يُحمَّل تلقائياً، بلا require إضافي
مطبات برمجية — OOP والأخطاء
Common Pitfalls & Debuggingnull لأنها لم تجد نتيجة، وكودك افترض أنها أرجعت كائناً دائماً. الـ Nullsafe Operator ?-> يحلّ هذا بأمان.readonly — منع أي تعديل لاحق عمداً. إن احتجت قيمة قابلة للتغيير، لا تجعلها readonly من الأساس.PHP الحديثة بعمق
Modern PHP 8.x Deep DiveFirst-Class Callable Syntax (8.1+)
// قديماً
$fn = 'strtoupper';
// حديثاً — أوضح ويفهمه المحرر (IDE) بشكل كامل
$fn = strtoupper(...);
echo $fn("yahya"); // YAHYA
Attributes — وصف معدني للكود (8.0+)
#[Route('/tasks', method: 'GET')]
function listTasks(): array { return []; }
Typed Class Constants (8.3+)
class Config {
public const int MAX_UPLOAD_MB = 10;
}
json_validate() (8.3+)
if (json_validate($rawInput)) {
$data = json_decode($rawInput, true);
}
never Return Type (8.1+)
function abortRequest(string $msg): never {
http_response_code(403);
die($msg);
}
Intersection Types (8.1+)
function process(Countable&Iterator $collection): void {}
Clean Code و PSR Standards
Clean Code & PSR-12أسماء واضحة، لا اختصارات غامضة
// ❌ غامض
function calc($d, $p) { return $d * $p; }
// ✅ واضح بذاته — لا يحتاج تعليقاً ليُفهم
function calculateDiscountedPrice(float $price, float $discountPercent): float {
return $price * (1 - $discountPercent / 100);
}
دالة واحدة = مسؤولية واحدة
declare(strict_types=1) في كل ملف
<?php
declare(strict_types=1);
namespace App\Models;
// ... باقي الملف
قواعد PSR-12 الأساسية للتنسيق
class TaskController
{
public function store(array $data): void
{
// 4 مسافات للمسافة البادئة، لا Tab
// قوس { للكلاسات والدوال في سطر جديد
// قوس { لجمل if/foreach في نفس السطر
}
}
مشروع التخرّج — مقدّمة: نظام يحيى لإدارة المهام
The Trajectory Project — Overviewالمبدأ المعماري المحوري: Repository Pattern
خريطة الملفات التي سنبنيها معاً
المشروع: البنية والنماذج
Models & Repository Layersrc/Models/Task.php
Model<?php
declare(strict_types=1);
namespace App\Models;
final class Task
{
public function __construct(
public readonly int $id,
public string $title,
public string $description,
public bool $done = false,
public readonly string $createdAt = '',
) {
}
// تحويل من مصفوفة (كما تأتي من JSON) إلى كائن Task
public static function fromArray(array $data): self
{
return new self(
id: (int) $data['id'],
title: (string) $data['title'],
description: (string) ($data['description'] ?? ''),
done: (bool) ($data['done'] ?? false),
createdAt: (string) ($data['created_at'] ?? ''),
);
}
// تحويل عكسي — جاهز للحفظ في JSON (أو لاحقاً في صف قاعدة بيانات)
public function toArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'done' => $this->done,
'created_at' => $this->createdAt,
];
}
}
src/Repositories/TaskRepositoryInterface.php
Interface<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Models\Task;
interface TaskRepositoryInterface
{
/** @return Task[] */
public function all(): array;
public function find(int $id): ?Task;
public function create(string $title, string $description): Task;
public function markDone(int $id): void;
public function delete(int $id): void;
}
src/Repositories/JsonTaskRepository.php
Implementation<?php
declare(strict_types=1);
namespace App\Repositories;
use App\Models\Task;
final class JsonTaskRepository implements TaskRepositoryInterface
{
public function __construct(private readonly string $path)
{
if (!file_exists($this->path)) {
file_put_contents($this->path, '[]');
}
}
public function all(): array
{
$rows = json_decode(file_get_contents($this->path), true) ?: [];
return array_map(Task::fromArray(...), $rows);
}
public function find(int $id): ?Task
{
foreach ($this->all() as $task) {
if ($task->id === $id) {
return $task;
}
}
return null;
}
public function create(string $title, string $description): Task
{
$tasks = $this->all();
$nextId = count($tasks) > 0
? max(array_map(fn(Task $t) => $t->id, $tasks)) + 1
: 1;
$task = new Task(
id: $nextId,
title: $title,
description: $description,
createdAt: date('Y-m-d H:i'),
);
$tasks[] = $task;
$this->save($tasks);
return $task;
}
public function markDone(int $id): void
{
$tasks = $this->all();
foreach ($tasks as $task) {
if ($task->id === $id) {
$task->done = true;
}
}
$this->save($tasks);
}
public function delete(int $id): void
{
$tasks = array_filter($this->all(), fn(Task $t) => $t->id !== $id);
$this->save($tasks);
}
private function save(array $tasks): void
{
$rows = array_map(fn(Task $t) => $t->toArray(), array_values($tasks));
file_put_contents($this->path, json_encode($rows, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}
}
المشروع: منطق العمل وملف التحميل
Business Logic, Validation & Bootstrapsrc/Support/Validator.php
<?php
declare(strict_types=1);
namespace App\Support;
final class Validator
{
private array $errors = [];
public function required(string $field, ?string $value, int $min = 1): self
{
if (mb_strlen(trim($value ?? '')) < $min) {
$this->errors[$field] = "حقل $field مطلوب (لا يقل عن $min حروف)";
}
return $this;
}
public function passes(): bool { return empty($this->errors); }
public function errors(): array { return $this->errors; }
}
bootstrap.php — نقطة التحميل الموحَّدة
index.php، create.php، actions.php) تبدأ بسطر واحد: require __DIR__ . '/bootstrap.php';<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use App\Repositories\JsonTaskRepository;
session_start();
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
// نقطة التبديل المستقبلية: غيّر هذا السطر فقط لاحقاً مع SQL
$repository = new JsonTaskRepository(__DIR__ . '/data/tasks.json');
function csrf_field(): string {
return '<input type="hidden" name="csrf" value="'
. htmlspecialchars($_SESSION['csrf'], ENT_QUOTES) . '">';
}
function csrf_check(): bool {
return hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '');
}
PdoTaskRepository implements TaskRepositoryInterface، وتغيّر سطراً واحداً هنا — كل باقي المشروع يبقى كما هو دون أي تعديل.المشروع: الواجهة والربط الآمن
Views, Forms & Secure Outputindex.php — عرض القائمة
<?php
require __DIR__ . '/bootstrap.php';
$tasks = $repository->all();
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<body>
<h1>مهامي</h1>
<?php if (!empty($_SESSION['flash'])): ?>
<p class="flash"><?= htmlspecialchars($_SESSION['flash']) ?></p>
<?php unset($_SESSION['flash']); endif; ?>
<ul>
<?php foreach ($tasks as $task): ?>
<li>
<strong><?= htmlspecialchars($task->title) ?></strong>
<?= $task->done ? '✓ مكتملة' : '… قيد الانتظار' ?>
<form method="POST" action="actions.php" style="display:inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $task->id ?>">
<button name="action" value="done">إكمال</button>
<button name="action" value="delete">حذف</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<a href="create.php">+ مهمة جديدة</a>
</body>
</html>
create.php — إضافة مهمة مع تحقق كامل
<?php
require __DIR__ . '/bootstrap.php';
use App\Support\Validator;
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_check()) {
die('طلب غير موثوق');
}
$title = trim($_POST['title'] ?? '');
$desc = trim($_POST['description'] ?? '');
$validator = (new Validator())->required('title', $title, min: 3);
if ($validator->passes()) {
$repository->create($title, $desc);
$_SESSION['flash'] = 'تمت إضافة المهمة ✓';
header('Location: index.php');
exit;
}
$errors = $validator->errors();
}
?>
<form method="POST">
<?= csrf_field() ?>
<input name="title" placeholder="عنوان المهمة">
<textarea name="description" placeholder="وصف اختياري"></textarea>
<button type="submit">حفظ</button>
</form>
actions.php — إكمال أو حذف، عبر match
<?php
require __DIR__ . '/bootstrap.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !csrf_check()) {
die('طلب غير صالح');
}
$id = (int) ($_POST['id'] ?? 0);
$action = $_POST['action'] ?? '';
match ($action) {
'done' => $repository->markDone($id),
'delete' => $repository->delete($id),
default => null,
};
$_SESSION['flash'] = 'تم تحديث القائمة';
header('Location: index.php');
exit;
التحدي النهائي — اختبار استقلالك الكامل
بدون أي حل جاهزهذا التمرين لن يُعطيك أي كود جاهز — فقط المواصفة. الهدف هو قياس صادق: هل تستطيع التفكير ببنية حل كامل بنفسك بالأدوات التي امتلكتها؟ عُد لمشروعك ووسّعه بالميزات التالية:
- تصنيفات (Categories): أضف خاصية
categoryلكل مهمة (مثل: عمل، شخصي، تعلّم) — فكّر: هل تستخدم نصاً حراً أم Enum؟ ولماذا؟ - مستوى أولوية (Priority): أضف ثلاث درجات أولوية، واعرض المهام مرتَّبة تلقائياً من الأعلى أولوية للأقل.
- بحث وفلترة: أضف فورم بسيط بـ
GETيفلتر القائمة حسب التصنيف أو كلمة في العنوان — بدون إعادة تحميل كامل المنطق، فقط فلترة على نتيجةall()الموجودة. - تعديل مهمة (Edit): صفحة
edit.phpكاملة — يجب أن تضيف دالةupdate()في الـ Interface وفي الـ Repository، مع كل تحقق وحماية CSRF التي طبّقتها في create.php. - اختبار الفهم الحقيقي: بعد إنجاز كل ما سبق، اسأل نفسك: لو غيّرت
JsonTaskRepositoryبالكامل، كم سطراً ستحتاج لتعديله في باقي ملفات المشروع؟ إن كان الجواب "صفر أو سطر واحد"، فقد نجحت في الهدف الحقيقي من هذا الدليل.
مرجع سريع PHP
Quick Reference Cheatsheet| الفئة | الدالة | الاستخدام | مثال |
|---|---|---|---|
| 📅 وقت | date("Y-m-d") | التاريخ الحالي | 2026-06-19 |
| 📅 وقت | strtotime('+1 week') | نص لـ timestamp | timestamp بعد أسبوع |
| 🔢 رياضيات | round($n, 2) | تقريب | round(3.456,2) = 3.46 |
| 🔢 رياضيات | rand(1, 100) | عشوائي | رقم بين 1 و100 |
| 🔢 رياضيات | intdiv(7, 2) | قسمة صحيحة | 3 |
| ✅ تحقق | isset($var) | موجود وليس null؟ | isset($_GET['id']) |
| ✅ تحقق | empty($var) | فاضي؟ 0/""/[]/null | !empty($name) |
| ✅ تحقق | filter_var($e, FILTER_VALIDATE_EMAIL) | إيميل صحيح؟ | true/false |
| 🧱 مصفوفات | array_map(fn, $arr) | تحويل كل عنصر | ['a','b'] → ['A','B'] |
| 🧱 مصفوفات | array_filter($arr, fn) | تصفية بشرط | الأعداد الزوجية فقط |
| 🧱 مصفوفات | array_reduce($arr, fn, $init) | اختزال لقيمة واحدة | جمع كل العناصر |
| 🔤 نصوص | str_contains($h, $n) | يحتوي؟ (8+) | true/false |
| 🔤 نصوص | mb_strlen($s) | عدد الأحرف (عربي/إيموجي) | آمن للعربية |
| 🗄️ JSON | json_encode($arr, JSON_UNESCAPED_UNICODE) | مصفوفة لـ JSON | {"name":"يحيى"} |
| 🗄️ JSON | json_decode($json, true) | JSON لمصفوفة | ['name'=>'يحيى'] |
| 🔒 أمان | htmlspecialchars($s, ENT_QUOTES) | حماية XSS | دائماً عند الطباعة |
| 🔒 أمان | password_hash($p, PASSWORD_DEFAULT) | تشفير كلمة مرور | bcrypt آمن |
| 🔒 أمان | password_verify($p, $hash) | مقارنة كلمة مرور | true/false |
| 🔒 أمان | hash_equals($a, $b) | مقارنة آمنة من Timing Attack | مقارنة CSRF tokens |
| 🔒 أمان | random_bytes(32) | bytes عشوائي آمن | للـ tokens والـ CSRF |
| 🏗️ OOP | $obj instanceof Class | هل الكائن من هذا النوع؟ | true/false |
| 🏗️ OOP | Class::method(...) | First-Class Callable | تمرير دالة كقيمة |
| 🔀 أخرى | header("Location: /page") + exit; | إعادة توجيه | لا تنسَ exit أبداً |
| 🔀 أخرى | var_dump($x) | debug — نوع وقيمة | string(5) "hello" |