البرمجة الكائنية في PHP: الكلاسات والكائنات والوراثة
حتى الآن كتبت كودك على شكل متغيرات ودوال منفصلة. هذا يكفي للصفحات الصغيرة، لكن المواقع الحقيقية فيها منتجات ومستخدمون وطلبات، ولكل منها بياناته وعملياته. البرمجة الكائنية (Object-Oriented Programming، واختصارها OOP) تنظّم هذا كله، وهي الأسلوب الذي بُني عليه Laravel وكل أطر PHP الحديثة. فهمها شرط للانتقال إلى الكورس التالي.
الكلاس والكائن
Section titled “الكلاس والكائن”الكلاس (Class) مخطط يصف شيئاً: ما بياناته، وماذا يستطيع أن يفعل. والكائن (Object) نسخة حقيقية مصنوعة من هذا المخطط. تخيّل مخطط بيت عند المهندس: المخطط واحد، وتُبنى منه بيوت كثيرة، ولكل بيت لونه وساكنوه.
<?phpclass 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 الحديثة تسمح بتعريف الخصائص داخل أقواس المُنشئ مباشرة. وفي المثال التالي ميزة أهم: التحكم في الوصول.
<?phpclass 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).
الوراثة: extends
Section titled “الوراثة: extends”المدير مستخدم عادي، لكن له صلاحيات إضافية. بدلاً من نسخ كود المستخدم، نجعل كلاس المدير يرث منه:
<?phpclass 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";المستخدم: عمرالمستخدم: هدى (مدير)هدى حذف المنشور رقم 7Adminورث المُنشئ والخاصية$nameدون إعادة كتابتهما.- أعاد تعريف
describe، واستدعى نسخة الأب بـparent::describe()ليضيف عليها. - أضاف تابعاً جديداً
deletePostلا يملكه المستخدم العادي. - الخاصية
protectedحتى يستطيع الابن استخدامها. لو كانتprivateلما وصل إليهاAdmin.
الثوابت والأعضاء الثابتة: static
Section titled “الثوابت والأعضاء الثابتة: static”بعض الأشياء تخص الكلاس كله، لا كائناً بعينه. نعلّمها بالكلمة static، ونصل إليها باسم الكلاس ثم :: دون إنشاء كائن:
<?phpclass 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";77322داخل الكلاس نستخدم self:: للوصول إلى الأعضاء الثابتة، كما نستخدم $this-> لأعضاء الكائن.
الواجهات: interface
Section titled “الواجهات: interface”الواجهة (Interface) عقد: قائمة توابع يتعهد الكلاس بكتابتها. هي لا تحتوي كوداً، بل تضمن أن كل من يطبّقها له الشكل نفسه:
<?phpinterface 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.003.14الدالة printArea تقبل أي كائن يطبّق Shape، ولا يهمها نوعه الحقيقي. وإذا نسي كلاس كتابة area فسترفض PHP تشغيل الملف.
جرّب بنفسك
Section titled “جرّب بنفسك”اكتب كلاساً اسمه Cart فيه مصفوفة خاصة private array $items، وتابع add(string $name, float $price)، وتابع total() يعيد مجموع الأسعار، وتابع count() يعيد عدد المنتجات. أضف قلماً بـ 5 وحقيبة بـ 150 ودفتراً بـ 12.5:
عدد المنتجات: 3الإجمالي: 167.5أخطاء شائعة
Section titled “أخطاء شائعة”- كتابة
$this->$name: علامة$تُكتب مرة واحدة فقط:$this->name. - نسيان
$this: داخل التابع،$nameوحدها متغير محلي فارغ، وليست خاصية الكائن. - الخلط بين
->و::: السهم للكائنات ($pen->name)، والنقطتان المزدوجتان للكلاس نفسه (Temperature::FREEZING). - جعل كل شيء
public: تفقد حماية البيانات. ابدأ بـprivate، وافتح فقط ما يحتاجه الخارج.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
متى يعمل التابع __construct؟
المُنشئ يعمل تلقائياً مع كل new، ومهمته تجهيز خصائص الكائن الجديد.
سؤال
خاصية معرّفة بـ private، من يستطيع الوصول إليها؟
private تحصر الوصول في الكلاس نفسه. أما السماح للأبناء أيضاً فهو protected.
الخلاصة
Section titled “الخلاصة”- الكلاس مخطط، والكائن نسخة منه تُنشأ بالكلمة
new. __constructيجهّز الكائن، و$this->تصل إلى خصائصه وتوابعه.privateوprotectedوpublicتتحكم في الوصول وتحمي البيانات.extendsللوراثة، وparent::لاستدعاء تابع الأب.staticوconstتخص الكلاس نفسه، وinterfaceعقد يلتزم به الكلاس.