تخطَّ إلى المحتوى

البرمجة الكائنية في PHP: الكلاسات والكائنات والوراثة

الدرس 11 من 12 · PHP

حتى الآن كتبت كودك على شكل متغيرات ودوال منفصلة. هذا يكفي للصفحات الصغيرة، لكن المواقع الحقيقية فيها منتجات ومستخدمون وطلبات، ولكل منها بياناته وعملياته. البرمجة الكائنية (Object-Oriented Programming، واختصارها OOP) تنظّم هذا كله، وهي الأسلوب الذي بُني عليه Laravel وكل أطر PHP الحديثة. فهمها شرط للانتقال إلى الكورس التالي.

الكلاس (Class) مخطط يصف شيئاً: ما بياناته، وماذا يستطيع أن يفعل. والكائن (Object) نسخة حقيقية مصنوعة من هذا المخطط. تخيّل مخطط بيت عند المهندس: المخطط واحد، وتُبنى منه بيوت كثيرة، ولكل بيت لونه وساكنوه.

<?php
class Product {
public string $name;
public float $price;
public function __construct(string $name, float $price) {
$this->name = $name;
$this->price = $price;
}
public function priceWithTax(): float {
return $this->price * 1.14;
}
}
$pen = new Product("قلم", 10);
$bag = new Product("حقيبة", 200);
echo $pen->name . "\n";
echo $bag->priceWithTax() . "\n";
الناتج
قلم
228
  • $name و $price هما خصائص (Properties) الكائن، أي بياناته.
  • __construct هو المُنشئ (Constructor): دالة خاصة تعمل تلقائياً عند إنشاء كل كائن بالكلمة new. قبل اسمها شرطتان سفليتان.
  • $this تعني «هذا الكائن نفسه». والسهم -> هو طريقة الوصول إلى خصائص الكائن وتوابعه.
  • priceWithTax دالة داخل الكلاس، وتسمى تابعاً (Method).

لاحظ أن $this->name تُكتب بدون علامة $ قبل name.

المُنشئ المختصر وحماية البيانات

Section titled “المُنشئ المختصر وحماية البيانات”

كتابة الخاصية ثم المعامل ثم سطر الإسناد تكرار ممل. PHP الحديثة تسمح بتعريف الخصائص داخل أقواس المُنشئ مباشرة. وفي المثال التالي ميزة أهم: التحكم في الوصول.

<?php
class BankAccount {
public function __construct(
public readonly string $owner,
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;
}
}
$account = new BankAccount("ليلى", 100);
$account->deposit(50);
echo $account->getBalance() . "\n";
try {
$account->deposit(-20);
} catch (InvalidArgumentException $e) {
echo $e->getMessage() . "\n";
}
الناتج
150
المبلغ يجب أن يكون موجباً

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

الكلمة من يصل إليها؟
public أي كود في أي مكان
private الكلاس نفسه فقط
protected الكلاس نفسه والكلاسات التي ترث منه
readonly تُقرأ من الخارج، ولا تتغير بعد الإنشاء

الرصيد هنا private. لو كتبت خارج الكلاس $account->balance = 1000000; فستتوقف PHP بالخطأ Cannot access private property BankAccount::$balance. الطريق الوحيد لتغيير الرصيد هو التابع deposit، وهو يرفض المبالغ غير المنطقية. هكذا يحمي الكائن بياناته من أي كود مهمل، وهذا يسمى التغليف (Encapsulation).

المدير مستخدم عادي، لكن له صلاحيات إضافية. بدلاً من نسخ كود المستخدم، نجعل كلاس المدير يرث منه:

<?php
class User {
public function __construct(protected string $name) {}
public function describe(): string {
return "المستخدم: {$this->name}";
}
}
class Admin extends User {
public function describe(): string {
return parent::describe() . " (مدير)";
}
public function deletePost(int $id): string {
return "{$this->name} حذف المنشور رقم $id";
}
}
$users = [new User("عمر"), new Admin("هدى")];
foreach ($users as $user) {
echo $user->describe() . "\n";
}
echo $users[1]->deletePost(7) . "\n";
الناتج
المستخدم: عمر
المستخدم: هدى (مدير)
هدى حذف المنشور رقم 7
  • Admin ورث المُنشئ والخاصية $name دون إعادة كتابتهما.
  • أعاد تعريف describe، واستدعى نسخة الأب بـ parent::describe() ليضيف عليها.
  • أضاف تابعاً جديداً deletePost لا يملكه المستخدم العادي.
  • الخاصية protected حتى يستطيع الابن استخدامها. لو كانت private لما وصل إليها Admin.

الثوابت والأعضاء الثابتة: static

Section titled “الثوابت والأعضاء الثابتة: static”

بعض الأشياء تخص الكلاس كله، لا كائناً بعينه. نعلّمها بالكلمة static، ونصل إليها باسم الكلاس ثم :: دون إنشاء كائن:

<?php
class Temperature {
const FREEZING = 0;
public static int $conversions = 0;
public static function toFahrenheit(float $celsius): float {
self::$conversions++;
return $celsius * 9 / 5 + 32;
}
}
echo Temperature::toFahrenheit(25) . "\n";
echo Temperature::toFahrenheit(Temperature::FREEZING) . "\n";
echo Temperature::$conversions . "\n";
الناتج
77
32
2

داخل الكلاس نستخدم self:: للوصول إلى الأعضاء الثابتة، كما نستخدم $this-> لأعضاء الكائن.

الواجهة (Interface) عقد: قائمة توابع يتعهد الكلاس بكتابتها. هي لا تحتوي كوداً، بل تضمن أن كل من يطبّقها له الشكل نفسه:

<?php
interface Shape {
public function area(): float;
}
class Rectangle implements Shape {
public function __construct(private float $width, private float $height) {}
public function area(): float {
return $this->width * $this->height;
}
}
class Circle implements Shape {
public function __construct(private float $radius) {}
public function area(): float {
return M_PI * $this->radius ** 2;
}
}
function printArea(Shape $shape): void {
printf("%.2f\n", $shape->area());
}
printArea(new Rectangle(4, 5));
printArea(new Circle(1));
الناتج
20.00
3.14

الدالة printArea تقبل أي كائن يطبّق Shape، ولا يهمها نوعه الحقيقي. وإذا نسي كلاس كتابة area فسترفض PHP تشغيل الملف.

اكتب كلاساً اسمه Cart فيه مصفوفة خاصة private array $items، وتابع add(string $name, float $price)، وتابع total() يعيد مجموع الأسعار، وتابع count() يعيد عدد المنتجات. أضف قلماً بـ 5 وحقيبة بـ 150 ودفتراً بـ 12.5:

الناتج
عدد المنتجات: 3
الإجمالي: 167.5
  • كتابة $this->$name: علامة $ تُكتب مرة واحدة فقط: $this->name.
  • نسيان $this: داخل التابع، $name وحدها متغير محلي فارغ، وليست خاصية الكائن.
  • الخلط بين -> و ::: السهم للكائنات ($pen->name)، والنقطتان المزدوجتان للكلاس نفسه (Temperature::FREEZING).
  • جعل كل شيء public: تفقد حماية البيانات. ابدأ بـ private، وافتح فقط ما يحتاجه الخارج.

سؤال

متى يعمل التابع __construct؟

سؤال

خاصية معرّفة بـ private، من يستطيع الوصول إليها؟

  • الكلاس مخطط، والكائن نسخة منه تُنشأ بالكلمة new.
  • __construct يجهّز الكائن، و $this-> تصل إلى خصائصه وتوابعه.
  • private و protected و public تتحكم في الوصول وتحمي البيانات.
  • extends للوراثة، و parent:: لاستدعاء تابع الأب.
  • static و const تخص الكلاس نفسه، و interface عقد يلتزم به الكلاس.