Y.
Yahya.DEV
Y.
Yahya Al-sulami
Full-Stack Developer & UI/UX Architect
PHP 8.x · الدليل الشامل + مشروع تطبيقي · Yahya.DEV

تعلّم لغة PHP
من الصفر… حتى تبني مشروعك الخاص

هذا ليس مجرد شرح لتعليمات اللغة. ستبني عقلية المطوّر الخلفي أولاً، ثم تتعلم كل أساس بعمق، ثم تطبّق كل شيء في مشروع حقيقي مستقل ستبنيه معي خطوة بخطوة — ومصمم خصيصاً ليكون جاهزاً لربطه بقاعدة بيانات في كورس SQL القادم.

PHP 8.x الحديثة 🧠عقلية Backend حقيقية 🛡️أمان من أول سطر 🏗️مشروع تطبيقي كامل 🪲مطبات ودِيباغنج حقيقي
24قسم متدرّج
5تطبيقات عملية
1مشروع مستقل كامل
مجاناًبلا تكلفة
00–02البداية 03–07الأساسيات 08–13المتوسط 14–19المتقدم 20–23المشروع 🏁التحدي النهائي
00

كيف يعمل الويب فعلياً؟ — العقلية الخلفية

The Request/Response Mental Model
قبل أي سطر كود، يجب أن تفهم السؤال الأهم: عندما يفتح شخص موقعك، ماذا يحدث بالضبط؟ هذا القسم هو الأساس الذي سيُبنى عليه كل ما بعده — تخطّاه أغلب من يتعلمون PHP، ولهذا يبقون "يكتبون كود يعمل" بدون أن يفهموا *لماذا* يعمل.

دورة الطلب والاستجابة

Client ↔ Server
المتصفح (Client) لا "يفتح" ملف PHP من جهازك. هو يرسل طلباً (Request) عبر الإنترنت إلى خادم (Server)، والخادم هو من يقوم بتشغيل كود PHP وإرسال استجابة (Response) — وهذه الاستجابة في النهاية مجرد HTML/CSS/JS عادي. PHP "تختفي" تماماً بحلول الوقت الذي يراها المتصفح — هذا هو معنى أنها لغة Server-Side.
🖥️

1. المتصفح يطلب

يكتب المستخدم العنوان، فيرسل المتصفح طلب HTTP إلى الخادم: "أعطني هذه الصفحة"

⚙️

2. الخادم يُشغّل PHP

يجد ملف .php، يُنفّذ الكود من أعلى لأسفل، قد يقرأ من قاعدة بيانات

📄

3. يُولَّد HTML

PHP تنتج نصاً نهائياً — مجرد HTML خام، لا أثر لكود PHP فيه

🌐

4. المتصفح يستقبل

يستقبل المتصفح هذا الـ HTML ويرسمه على الشاشة كصفحة ويب

لماذا "Server-Side" بالضبط؟

جافاسكريبت تعمل داخل متصفح المستخدم (Client-Side) — أي شخص يستطيع رؤية كودها بالضغط على "Inspect". أما PHP فتعمل على خادمك أنت فقط — المستخدم لا يرى سطراً واحداً من كودها أبداً، حتى لو فتح أدوات المطوّر. هذا الفرق هو سبب وجود PHP من الأساس: أي شيء حسّاس (كلمات مرور، مفاتيح API، منطق قاعدة البيانات) يجب أن يكون في كود يعمل على الخادم لا على المتصفح.
// ملف: hello.php — هذا ما يكتبه المطوّر
<?php
$name = "يحيى";
echo "<h1>أهلاً، " . $name . "</h1>";
?>
// هذا ما يستقبله متصفح المستخدم فعلياً — لا أثر لـ PHP <h1>أهلاً، يحيى</h1>

أين تعيش بياناتك؟ خريطة الذاكرة في كل طلب

نقطة جوهرية يخطئ فيها كل مبتدئ: كل طلب HTTP جديد هو صفحة بيضاء تماماً بالنسبة لـ PHP. المتغيرات التي أنشأتها في طلب سابق تُمحى تماماً بعد إرسال الاستجابة — السيرفر لا "يتذكر" شيئاً بين طلب وآخر، إلا إذا خزّنته بنفسك في مكان دائم.
مكان التخزينيعيش لمدةمتى تستخدمه
متغير عادي $xطلب واحد فقط، ثم يُمحىحسابات مؤقتة داخل نفس الصفحة
الجلسة $_SESSIONطوال زيارة المستخدم (بملف على الخادم)"هل المستخدم مسجّل دخول؟"
الكوكي $_COOKIEأيام/شهور (مخزّن في متصفح المستخدم)تذكّر تفضيلات، "تذكّرني"
ملف/قاعدة بياناتدائم حتى تحذفه أنتأي بيانات حقيقية يجب أن تبقى للأبد
🧭
الخلاصة التي ستحتاجها طوال الكورس: كل ملف PHP يُنفَّذ من جديد مع كل طلب، من أول سطر. إن أردت أن تتذكّر شيئاً بين الطلبات، عليك تخزينه بصراحة (جلسة، كوكي، ملف، أو قاعدة بيانات). هذا المبدأ بالذات هو ما سيجعل فصل "الجلسات" ولاحقاً "قواعد البيانات" منطقياً بدلاً من أن يبدو سحراً.
01

ما هي PHP ولماذا تتعلمها؟

What is PHP & Why?
PHP هي لغة برمجة مفتوحة المصدر مصمَّمة خصيصاً لبناء الويب من جهة الخادم (Server-Side). رغم أنها وُلدت في 1994، فإن PHP الحديثة (8.x) لغة مختلفة جذرياً — سريعة، صارمة في الأنواع إن أردت، وتدعم OOP بمستوى عالمي. وهي العمود الفقري لجزء كبير من الإنترنت، بما فيه ووردبريس ولارافيل — وهو ما أستخدمه أنا يومياً في مشاريعي الحقيقية.

لماذا تستحق PHP وقتك في 2026؟

1. سهلة البداية، عميقة عند الاحتراف — منحنى تعلّم لطيف لا يخيفك من الأسبوع الأول، لكنه يفتح أمامك OOP وAttributes وFibers عندما تكون جاهزاً.

2. منتشرة في فرص العمل الحقيقية — تشغّل نسبة ضخمة من مواقع الويب حول العالم، وLaravel وحدها سوق عمل كامل.

3. مصمَّمة للويب من الأساس — التعامل مع HTTP وFormsوالـ Sessions مدمج في اللغة نفسها، لا تحتاج مكتبات خارجية لتبدأ.

أين تتموضع PHP في عالم تطوير الويب؟

موقع الويب الكامل = Frontend (ما يراه المستخدم: HTML/CSS/JS) + Backend (المنطق الخفي: قواعد البيانات، تسجيل الدخول، الأمان). PHP تعمل في الطبقة الثانية. ستتعلم في هذا الدليل أن تكتب Backend نظيفاً ومنظّماً — ثم تربطه لاحقاً بأي Frontend تريد، حتى لو كان React.
// PHP تستطيع أيضاً أن "تكتب" HTML مباشرة (Server-Rendered)
<!DOCTYPE html>
<html>
<body>
  <h1><?php echo "مرحباً بالعالم"; ?></h1>
</body>
</html>
💡
وعد هذا الدليل: لن تتعلم فقط "كيف تكتب الكود"، بل "كيف يفكّر مطوّر Backend محترف". كل قسم يبني على ما قبله، وستنتهي ببناء مشروعك المستقل الأول بيديك.
02

البيئة والتثبيت

Local Environment Setup
لتشغيل PHP محلياً تحتاج بيئة خادم محاكاة على جهازك. أبسط طريقة في 2026: تثبيت PHP CLI مباشرة دون أي برنامج إضافي ضخم.

التثبيت السريع — السيرفر المدمج في PHP

PHP Built-in Server
منذ PHP 5.4، تأتي اللغة بسيرفر تطوير مدمج — لا تحتاج Apache أو XAMPP لتبدأ. ثبّت PHP من php.net أو عبر مدير الحزم في نظامك، ثم:
# تأكد من التثبيت
php -v

# شغّل سيرفر تطوير في مجلد مشروعك
php -S localhost:8000

# الآن افتح المتصفح على
# http://localhost:8000
📌
هذا السيرفر المدمج للتطوير المحلي فقط — لا تستخدمه في الإنتاج (Production) أبداً. للنشر الحقيقي تحتاج Apache/Nginx + PHP-FPM، أو خدمة استضافة جاهزة.

أول ملف PHP وقاعدة التسمية

أي ملف يحتوي كود PHP يجب أن ينتهي بامتداد .php. الكود نفسه يبدأ بـ <?php وينتهي اختيارياً بـ ?> — لكن في الملفات التي تحتوي PHP فقط (دون HTML بعدها)، يُفضَّل عدم كتابة ?> النهائية لتجنّب مشاكل المسافات الخفية.
// index.php
<?php

declare(strict_types=1); // أول سطر دائماً — صرامة الأنواع

echo "أهلاً من PHP " . PHP_VERSION;
// لاحظ: لا يوجد ?> في النهاية — هذا مقصود ومُتعمَّد

أداة لا غنى عنها: Composer

PHP Package Manager
Composer هو مدير الحزم الرسمي لـ PHP — ستحتاجه لاحقاً لتثبيت مكتبات، ولتفعيل Autoloading الذي سنغطّيه في قسم الملفات. ثبّته الآن من getcomposer.org حتى يكون جاهزاً.
🔍 ابحث عن:  "php built-in server vs apache" · "composer install windows/mac/linux" · "VS Code PHP extensions"
03

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

Basic Syntax
قواعد صغيرة، لكن تجاهلها يسبّب أغلب أخطاء الأسبوع الأول. اقرأها بتركيز — ستوفّر عليك ساعات تصحيح أخطاء لاحقاً.

الفاصلة المنقوطة والتعليقات

// كل جملة (statement) تنتهي بفاصلة منقوطة
$x = 5;

// تعليق سطر واحد
# تعليق سطر واحد — أقل استخداماً
/* تعليق
   متعدد الأسطر */

المسافات والحساسية لحالة الأحرف

المسافات والـ Tabs لا تهم PHP (على عكس Python). لكن: أسماء المتغيرات حساسة لحالة الأحرف ($Name$name)، أما أسماء الدوال والكلاسات فغير حساسة — مع ذلك التزم بنفس الحالة دائماً لقابلية القراءة.
$name = "Yahya";
$Name = "Different!"; // متغير مختلف تماماً

echo $name;  // Yahya
echo $Name;  // Different!
04

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

Variables & Data Types
كل متغير في PHP يبدأ بعلامة $. PHP لغة ديناميكية الأنواع بطبيعتها — لكن منذ PHP 7، يمكنك (ويجب عليك) تحديد الأنواع بصراحة لكتابة كود أكثر أماناً وقابلية للفهم.

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

// 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

الثابت قيمته لا تتغير أبداً بعد تعريفه — يُكتب تقليدياً بـ CAPITALS.
const API_VERSION = "v2.5"; // الطريقة الحديثة المفضّلة
define("SITE_NAME", "Yahya.DEV"); // طريقة قديمة، ديناميكية

echo API_VERSION; // v2.5
echo PHP_VERSION;  // ثابت مدمج، مثل 8.4.1

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

المتغير العام (global) غير مرئي تلقائياً داخل الدوال — هذا مقصود لحماية الكود من التداخل العشوائي.
$site = "yahya.dev"; // global

function show(string $url): void {
  echo $url; // تُمرَّر كمعطى، لا كـ global
}
show($site);
⚠️
عادة يجب أن تتبنّاها من اليوم الأول: ضع declare(strict_types=1); في أول سطر بعد <?php في كل ملف. هذا يمنع PHP من "تحويل" الأنواع بصمت (مثل قبول النص "5" في مكان يطلب int)، ويفجّر الأخطاء فوراً بدل أن تختفي وتظهر لاحقاً كبَّق غامض.
05

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

Operators & Comparison
أهم فرق ستحتاجه طوال حياتك في PHP: == (قيمة فقط) مقابل === (قيمة + نوع) — مصدر نسبة كبيرة من الأخطاء المنطقية الصامتة. القاعدة: استخدم === دائماً، إلا إن كان لديك سبب واضح وموثَّق لاستخدام ==.

حسابية + اختصارات

$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
06

الشروط ومُعبِّر match

Conditionals & match
جمل التحكم هي ما يعطي برنامجك "قدرة على اتخاذ القرار". في PHP الحديثة، match أصبح غالباً الأنسب من switch القديم — أكثر أماناً وأقل أخطاءً.

if / 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 "يوم دوام";
}
07

الحلقات التكرارية

Loops
الحلقات تنفّذ كتلة كود عدة مرات. اختيار النوع الصحيح يجعل كودك أوضح للقارئ — وأنت القارئ بعد 6 شهور من الآن.

for — عندما تعرف عدد التكرارات

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 مع قائمة تخمينات مكتوبة مسبقاً في مصفوفة، ويطبع "أكبر/أصغر/صحيح!" لكل تخمين حتى يصل للرقم الصحيح أو تنتهي المحاولات.
🔍 ابحث عن:  "php rand function" · "php match vs switch" · "php string interpolation $variable in string"
08

الدوال Functions

Functions & Modern Signatures
الدالة هي كتلة كود قابلة لإعادة الاستخدام. في PHP الحديثة، توقيع الدالة الجيد يحدّد نوع كل معطى ونوع القيمة المُرجعة — هذا ليس "زخرفة"، بل يمنع فئة كاملة من الأخطاء قبل أن تحدث.

دالة بسيطة مع أنواع صريحة

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;
}
09

المصفوفات Arrays

Arrays & Built-in Functions
المصفوفة في PHP هي أهم بنية بيانات في اللغة كلها — تُستخدم كقائمة، أو كقاموس (Key=>Value)، أو حتى كمصفوفة متعددة الأبعاد لتمثيل بيانات معقّدة كصفوف من قاعدة بيانات.

مصفوفة مرقَّمة (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"]++;     // تعديل قيمة مباشرة

المصفوفات متعددة الأبعاد — أساس كل "صف بيانات"

هذا الشكل بالضبط هو ما ستراه لاحقاً قادماً من قاعدة البيانات (كل عنصر = صف/سجل) — تعلّمه الآن جيداً يجعل كورس SQL القادم بديهياً.
$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); // يرجع المفتاح/الموضع
10

النصوص Strings

String Functions & Formatting
PHP غنية جداً بدوال النصوص الجاهزة — معرفة الدالة الصحيحة توفّر عليك إعادة اختراع العجلة في كل مرة.

الدمج والتنسيق

$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) لا متغيراً عاماً، وأرجِع النتيجة كمصفوفة واحدة منظَّمة بدل طباعتها مباشرة داخل الدالة — مبدأ سيخدمك كثيراً في المشروع التطبيقي القادم.
🔍 ابحث عن:  "php explode function" · "php array key exists vs isset" · "php function return array"
11

الفورمز والـ Superglobals

Forms & $_GET / $_POST / $_SERVER
هنا يبدأ التفاعل الحقيقي بين المستخدم وخادمك. الـ Superglobals هي مصفوفات جاهزة تملؤها PHP تلقائياً بمعلومات الطلب الحالي — متاحة في أي مكان دون استيراد.

$_GET — بيانات من رابط الصفحة

تأتي من Query String في الرابط: 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() دائماً.
12

الجلسات والكوكيز

Sessions & Cookies
تذكّر القسم 00: كل طلب "صفحة بيضاء". الجلسات هي الطريقة الرسمية لـ PHP لتجاوز هذه القاعدة — تتيح لك "تذكّر" المستخدم بين صفحة وأخرى.

بدء جلسة وتخزين بيانات

يخزّن PHP بيانات الجلسة في ملف على الخادم، ويرسل للمتصفح فقط معرّفاً عشوائياً (Session ID) عبر كوكي — هذا يجعلها أأمن بطبيعتها من الكوكيز العادية.
// أول سطر تماماً في كل صفحة تستخدم الجلسة
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']);
}
13

أساسيات الأمان

XSS, CSRF & Input Validation
الأمان في هذا الدليل ليس قسماً منفصلاً تقرأه وتنسى — إنه عادة. لكن هنا نضع الأساس النظري الواضح الذي سنطبّقه حرفياً في كل سطر لاحق، وفي المشروع التطبيقي بأكمله.

XSS — حقن سكريبت عبر المواقع

Cross-Site Scripting
يحدث عندما تطبع بيانات المستخدم في HTML دون تنقية، فيستطيع شخص خبيث إدخال <script> يُنفَّذ في متصفح ضحية آخر يزور الصفحة.
// المستخدم كتب في خانة "الاسم": <script>alert('hacked')</script>

// ❌ خطير — يُنفَّذ السكريبت فعلياً عند أي زائر
echo "أهلاً " . $_GET['name'];

// ✅ آمن — يتحوّل السكريبت لنص عادي غير قابل للتنفيذ
echo "أهلاً " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');
💡
قاعدة الذهب: Escape عند الإخراج (Output) لا عند الإدخال. خزّن البيانات في قاعدة البيانات كما هي، ونقِّها فقط لحظة طباعتها في HTML.

CSRF — اختطاف الفورم

Cross-Site Request Forgery
موقع خبيث يجعل متصفح ضحية مسجّل دخوله بالفعل في موقعك يرسل طلباً (مثل حذف حساب) دون علم الضحية. الحل: CSRF Token — قيمة عشوائية سرية تُولَّد لكل جلسة وتُرسَل مع كل فورم، ويتحقق الخادم منها.
// عند توليد الفورم
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
هذه أخطاء حقيقية ستقابلك حتماً. الهدف ليس حفظها، بل تدريب عينك على التعرّف على رسالة الخطأ وربطها بسببها فوراً.
⚠️ الوصول لمفتاح غير موجود في $_POST
$name = $_POST['name']; // المستخدم لم يملأ الحقل، أو الفورم لم يُرسَل أصلاً
Warning: Undefined array key "name"
$name = $_POST['name'] ?? '';
السبب: PHP لا تفترض وجود أي مفتاح في مصفوفة قادمة من المستخدم. استخدم ?? أو isset() دائماً قبل القراءة المباشرة.
⚠️ نسيان exit; بعد header("Location")
header("Location: /login.php"); echo "سيتم نقلك..."; // ينفّذ ويُطبع فعلياً!
لا خطأ ظاهر — لكن الكود يستمر بالتنفيذ فعلياً بعد إعادة التوجيه
header("Location: /login.php"); exit; // أوقف التنفيذ فوراً وبصرامة
السبب: header() فقط يضيف تعليمة للمتصفح — لا تُوقف تنفيذ سكريبت PHP. أخطر مظهر لهذا: كود حساس يُنفَّذ بعد محاولة "حماية" غير مكتملة.
⚠️ "Headers already sent" عند استخدام session_start أو header
<!-- مسافة أو سطر فاضي قبل <?php --> <?php session_start();
Warning: session_start(): Cannot send session cookies - headers already sent
<?php // لا أي حرف أو مسافة قبل <?php في أول الملف session_start();
السبب: session_start() وheader() يحتاجان إرسال رؤوس HTTP قبل أي إخراج نصّي — حتى مسافة فاضية قبل <?php تُحسب كإخراج.
⚠️ مقارنة CSRF token بـ == بدل hash_equals
if ($_POST['csrf'] == $_SESSION['csrf']) { ... }
لا خطأ ظاهر — ثغرة أمنية صامتة (Timing Attack)
if (hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) { ... }
السبب: مقارنة النصوص العادية تتوقف عند أول اختلاف، ما يكشف معلومات عن الوقت تساعد مهاجماً على تخمين الـ Token حرفاً بحرف. hash_equals مصمَّمة لمقاومة هذا تحديداً.
🎯

تطبيق عملي — نظام تسجيل دخول آمن (بدون قاعدة بيانات بعد)

مستوى: متوسط-متقدم

هذا التطبيق هو أول "تمرين أمان حقيقي" في الدليل. ابنِ نظاماً من ملفين login.php وdashboard.php:

  • خزّن "مستخدماً واحداً" مؤقتاً كمصفوفة ثابتة في الكود، باسم مستخدم وكلمة مرور مشفّرة بـ password_hash.
  • فورم login.php يحتوي حقل CSRF Token مخفي، ويتحقق منه عند الإرسال بـ hash_equals.
  • عند نجاح تسجيل الدخول: خزّن حالة الدخول في $_SESSION وأعد التوجيه لـ dashboard.php (لا تنس exit;).
  • dashboard.php يجب أن يطرد أي زائر غير مسجَّل دخوله فوراً لصفحة الدخول.
  • التحدي الإضافي: أضف "Rate Limiting" بسيط — إن فشل تسجيل الدخول 3 مرات متتالية، اعرض رسالة تأخير قبل السماح بمحاولة جديدة (خزّن عدّاد المحاولات في الجلسة).
🔍 ابحث عن:  "php password_hash password_verify" · "php csrf token tutorial" · "php redirect after login session"
14

OOP — الأساسيات

Object-Oriented Programming Basics
حتى الآن كتبت كل شيء كدوال ومتغيرات منفصلة. OOP يتيح لك تجميع البيانات (Properties) والسلوك (Methods) معاً داخل وحدة واحدة منطقية — وهذا هو الأساس الذي سيُبنى عليه مشروعك التطبيقي بالكامل.

أول Class — وConstructor Property Promotion (PHP 8+)

الطريقة الحديثة لتعريف خاصيات الكلاس مباشرة داخل الـ Constructor — توفّر عشرات الأسطر المكرَّرة.
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

تتحكم بمن يستطيع الوصول للخاصية أو الدالة من خارج الكلاس — أساس مبدأ Encapsulation (التغليف).
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
15

OOP — المستوى المتقدم

Inheritance, Interfaces, Traits & Enums
هنا تصبح OOP أداة هندسية حقيقية. كل أداة من هذه ستُستخدم بالضبط في بنية مشروعك التطبيقي — خصوصاً Interfaces، التي ستجعل ربط المشروع بقاعدة بيانات لاحقاً عملية تغيير سطر واحد لا إعادة كتابة.

الوراثة 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 يحدّد ما يجب أن تفعله الفئات، دون أن يهتم بـ كيف. هذا هو السر الذي سيجعل التبديل من تخزين JSON إلى قاعدة بيانات حقيقية في كورس SQL لا يكسر أي كود آخر في مشروعك.
interface TaskRepositoryInterface {
  public function all(): array;
  public function create(array $data): void;
}

// اليوم: تخزين في ملف JSON
class JsonTaskRepository implements TaskRepositoryInterface { /* ... */ }

// لاحقاً في كورس SQL: تخزين في قاعدة بيانات — نفس التعاقد بالضبط
// class PdoTaskRepository implements TaskRepositoryInterface { /* ... */ }

Traits — مشاركة سلوك دون وراثة

PHP لا تسمح بوراثة أكثر من كلاس واحد — Trait يحل هذا: "نسخ ولصق" منطقي لسلوك مشترك بين كلاسات غير مرتبطة.
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+)

خاصية تُكتب مرة واحدة فقط (داخل الـ Constructor عادة)، ثم تصبح ثابتة للأبد — تمنع تعديلاً عرضياً لاحقاً.
class User {
  public function __construct(
    public readonly int $id,
  ) {}
}
$u = new User(1);
$u->id = 2; // ❌ Error: Cannot modify readonly property
16

معالجة الأخطاء

Exceptions & Error Handling
الكود الاحترافي لا "يتمنى" أن لا تحدث أخطاء — هو يتوقّعها ويتعامل معها بأناقة، بدل أن ينهار الموقع بالكامل أمام المستخدم.

try / 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 "حدث خطأ، حاول مرة أخرى";    // رسالة عامة آمنة للمستخدم
}
⚠️
لا تعرض تفاصيل الخطأ التقنية للمستخدم في بيئة الإنتاج (Production). رسالة مثل "Fatal error in /var/www/db.php line 42" تكشف معلومات قد يستغلها مهاجم. اعرض رسالة عامة، وسجِّل التفاصيل في ملف Log خاص بك فقط.
17

الملفات و Namespaces

File Handling, Namespaces & Autoloading
مشروعك سيكبر إلى أكثر من ملف واحد سريعاً. هذا القسم هو ما يجعل تنظيم الملفات منطقياً بدل فوضى من require المتكررة.

القراءة والكتابة في الملفات

// كتابة (تنشئ الملف إن لم يكن موجوداً)
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 & Debugging
⚠️ استدعاء method على null
$user = findUser(99); // غير موجود، ترجع null echo $user->name;
Fatal error: Call to a member function on null
echo $user?->name ?? 'غير موجود';
السبب: دالة البحث أرجعت null لأنها لم تجد نتيجة، وكودك افترض أنها أرجعت كائناً دائماً. الـ Nullsafe Operator ?-> يحلّ هذا بأمان.
⚠️ نسيان implements عند استخدام Interface
class JsonTaskRepository { public function all(): array { ... } } function save(TaskRepositoryInterface $r) {} save(new JsonTaskRepository()); // لا implements!
TypeError: Argument #1 must implement TaskRepositoryInterface
class JsonTaskRepository implements TaskRepositoryInterface { ... }
السبب: PHP تتحقق من التعاقد الصريح (implements)، لا من تشابه أسماء الدوال فقط. وجود الدوال نفسها لا يكفي.
⚠️ تعديل خاصية readonly بعد التهيئة
$task = new Task(1, "عنوان"); $task->id = 2;
Error: Cannot modify readonly property Task::$id
// أنشئ كائناً جديداً بدلاً من التعديل $task = new Task(2, "عنوان");
السبب: هذا هو الغرض الكامل من readonly — منع أي تعديل لاحق عمداً. إن احتجت قيمة قابلة للتغيير، لا تجعلها readonly من الأساس.
⚠️ try/catch بدون نوع خطأ محدد يُخفي مشاكل حقيقية
try { riskyOperation(); } catch (\Throwable $e) { // فارغ تماماً — صمت كامل }
لا خطأ ظاهر — لكن البرنامج "يبدو" يعمل وهو معطوب فعلياً
} catch (\Throwable $e) { error_log($e->getMessage()); // تعامل مع الخطأ بمنطق واضح، لا تتجاهله }
السبب: Catch فارغ يحوّل أخطاء حقيقية إلى سلوك صامت وغير متوقَّع — أصعب فئة أخطاء للتتبّع لاحقاً. سجِّل كل خطأ تمسكه على الأقل.
18

PHP الحديثة بعمق

Modern PHP 8.x Deep Dive
ملخّص مركَّز لأهم إضافات PHP 8.x التي رفعت اللغة لمستوى آخر تماماً — معظمها استخدمته بالفعل ضمنياً في الأقسام السابقة، وهنا نجمعها معاً بوعي.

First-Class Callable Syntax (8.1+)

// قديماً
$fn = 'strtoupper';
// حديثاً — أوضح ويفهمه المحرر (IDE) بشكل كامل
$fn = strtoupper(...);
echo $fn("yahya"); // YAHYA

Attributes — وصف معدني للكود (8.0+)

بدائل حديثة لتعليقات DocBlock القديمة، تُقرأ برمجياً عبر Reflection — أساس عمل أي Framework حديث مثل Laravel.
#[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+)

تحقّق من صحة JSON دون فك تشفيره بالكامل — أخفّ وأسرع عندما تريد فقط التحقق.
if (json_validate($rawInput)) {
  $data = json_decode($rawInput, true);
}

never Return Type (8.1+)

يخبر المحرر والقارئ أن الدالة لا ترجع أبداً — تنهي التنفيذ دوماً (Throw أو Exit).
function abortRequest(string $msg): never {
  http_response_code(403);
  die($msg);
}

Intersection Types (8.1+)

المعطى يجب أن يحقّق كل الـ Interfaces المذكورة معاً — لا أحدها فقط.
function process(Countable&Iterator $collection): void {}
📌
لست بحاجة لحفظ كل هذا الآن. الهدف أن تتعرّف عليها عندما تراها في كود حقيقي أو في توثيق Laravel — ستعرف بالضبط ماذا تفعل ولماذا.
19

Clean Code و PSR Standards

Clean Code & PSR-12
الفرق بين كود "يعمل" وكود "محترف" هو الانضباط في هذه التفاصيل — وهي ما يُفحَص فعلياً في أي مراجعة كود (Code Review) حقيقية.

أسماء واضحة، لا اختصارات غامضة

// ❌ غامض
function calc($d, $p) { return $d * $p; }

// ✅ واضح بذاته — لا يحتاج تعليقاً ليُفهم
function calculateDiscountedPrice(float $price, float $discountPercent): float {
  return $price * (1 - $discountPercent / 100);
}

دالة واحدة = مسؤولية واحدة

إن وجدت دالتك تتحقق من المدخلات، وتحفظ في ملف، وترسل بريداً، وتطبع HTML — قسّمها. كل دالة يجب أن تُوصَف بجملة واحدة بسيطة دون "و".

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 في نفس السطر
    }
}
💡
أداة عملية: ثبّت PHP CS Fixer أو PHP_CodeSniffer ليُصحّح تنسيق كودك تلقائياً وفق PSR-12 — لا تحتاج لحفظ كل القواعد يدوياً، فقط اعرف وجودها.
20

مشروع التخرّج — مقدّمة: نظام يحيى لإدارة المهام

The Trajectory Project — Overview
🏗️

لماذا هذا المشروع بالذات؟

مشروع بسيط في وظيفته (إدارة قائمة مهام)، لكنه مبني بانضباط هندسي حقيقي — حتى يبقى مفتوحاً تماماً ليستقبل قاعدة بيانات فعلية في كورس SQL القادم، دون أن تعيد كتابة سطر واحد من منطق عملك.

طوال الأقسام السابقة، كل أداة تعلّمتها كانت تُمهِّد لهذه اللحظة: الفورمز، الجلسات، الأمان، OOP، الـ Interfaces. الآن سنجمعها في تطبيق PHP حقيقي واحد متكامل — بدون أي Framework، لتفهم ما الذي يفعله Framework مثل Laravel قبل أن تعتمد عليه.

المبدأ المعماري المحوري: Repository Pattern

بدل أن تكتب كود "قراءة وكتابة الملف" مباشرة داخل كل صفحة، نضعه في كلاس واحد مسؤول عن البيانات فقط، يتحدّث مع باقي المشروع عبر Interface ثابت. النتيجة: لاحقاً، تستبدل "كيف تُخزَّن البيانات" (من ملف JSON إلى قاعدة بيانات حقيقية) دون أن تلمس صفحات العرض أو منطق التحقق إطلاقاً.

خريطة الملفات التي سنبنيها معاً

📁task-manager/ ├── 📁src/ │ ├── 📁Models/ │ │ └── Task.php ← القسم 21 │ ├── 📁Repositories/ │ │ ├── TaskRepositoryInterface.php ← القسم 21 │ │ └── JsonTaskRepository.php ← القسم 21 │ └── 📁Support/ │ └── Validator.php ← القسم 22 ├── 📁data/ │ └── tasks.json ← يُنشأ تلقائياً ├── bootstrap.php ← القسم 22 — يجمع كل شيء ├── index.php ← القسم 23 — عرض القائمة ├── create.php ← القسم 23 — إضافة مهمة └── actions.php ← القسم 23 — تنفيذ (إكمال/حذف)
📌
مواصفات المشروع: إضافة مهمة بعنوان ووصف، عرض القائمة، تعليم مهمة كمكتملة، حذف مهمة — مع تحقق كامل من المدخلات، حماية CSRF، رسائل Flash، وEscape كل مخرج. بسيط في الوظيفة، صحيح بالكامل في البنية.
21

المشروع: البنية والنماذج

Models & Repository Layer
نبدأ من الداخل للخارج: أولاً نموذج البيانات (Model)، ثم طبقة الوصول للبيانات (Repository) خلف Interface ثابت.

src/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));
    }
}
22

المشروع: منطق العمل وملف التحميل

Business Logic, Validation & Bootstrap
قبل صفحات العرض، نحتاج ملفاً واحداً "يُحمِّل" كل شيء (Autoload + الإعدادات + الجلسة)، وكلاس تحقق بسيط يفصل منطق "هل المدخلات صحيحة؟" عن صفحات العرض.

src/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'] ?? '');
}
💡
لاحظ السطر المعلَّق "نقطة التبديل المستقبلية": هذا تماماً ما تحدثنا عنه في القسم 20. عندما تتعلم SQL، ستكتب PdoTaskRepository implements TaskRepositoryInterface، وتغيّر سطراً واحداً هنا — كل باقي المشروع يبقى كما هو دون أي تعديل.
23

المشروع: الواجهة والربط الآمن

Views, Forms & Secure Output
الخطوة الأخيرة: صفحات تستخدم كل ما بنيناه. لاحظ كيف كل سطر هنا يطبّق قاعدة أمان تعلّمتها في الأقسام السابقة — لا شيء عشوائي.

index.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 usort array of objects" · "php enum with interface" · "php filter array by query string get"

مرجع سريع PHP

Quick Reference Cheatsheet
جدول مرجعي مكثَّف لأكثر الدوال استخداماً في هذا الدليل وفي أي مشروع PHP حقيقي. استخدم البحث السريع (/) لإيجاد دالة بعينها فوراً.
الفئةالدالةالاستخداممثال
📅 وقتdate("Y-m-d")التاريخ الحالي2026-06-19
📅 وقتstrtotime('+1 week')نص لـ timestamptimestamp بعد أسبوع
🔢 رياضيات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)عدد الأحرف (عربي/إيموجي)آمن للعربية
🗄️ JSONjson_encode($arr, JSON_UNESCAPED_UNICODE)مصفوفة لـ JSON{"name":"يحيى"}
🗄️ JSONjson_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
🏗️ OOPClass::method(...)First-Class Callableتمرير دالة كقيمة
🔀 أخرىheader("Location: /page") + exit;إعادة توجيهلا تنسَ exit أبداً
🔀 أخرىvar_dump($x)debug — نوع وقيمةstring(5) "hello"
💡
خارطة المراجعة الموصى بها: العقلية الخلفية ← الأساسيات ← الدوال والمصفوفات ← الأمان ← OOP ← مشروعك التطبيقي. اقرأ، أغلق الشاشة، أعد البناء من الذاكرة — هذا هو التعلّم الحقيقي.
📌
الخطوتان التاليتان: (1) كورس SQL وقواعد البيانات القادم — ستأخذ نفس مشروعك وتستبدل JsonTaskRepository بـ PdoTaskRepository. (2) إطار Laravel — يبسّط كل ما تعلمته هنا (Routing، Eloquent، Blade، Auth) ضمن بنية جاهزة. أستخدمه يومياً في مشاريعي الحقيقية.
Yahya.DEV  ·  Full-Stack Developer
<?php /* Yahya.DEV · PHP 8.x */ ?>