في الدرس السابق من هذا المسار، تعرّفنا على أساسيات البرمجة كائنية التوجه (OOP) بلغة PHP، وكيفية إنشاء الصفوف (Classes) والكائنات (Objects)، واستخدام الدالة البانية (Constructor). لكن عند بناء تطبيقات ويب الحقيقية مثل المتاجر الإلكترونية أو أنظمة إدارة المحتوى ستجد أنك تكرر كتابة الخصائص والدوال نفسها في عدة صفوف متقاربة.
هنا يأتي دور مفهومين أساسيين من أعمدة الوراثة ومستويات الوصول (Access Modifiers). في هذا الدرس، ستتعلم كيف تنشئ علاقات هرمية بين الصفوف لإعادة استخدام الكود بكفاءة، وكيف تحمي بيانات تطبيقك وتتحكم في نطاق رؤيتها وتنفيذها.
مفهوم مستويات الوصول (Access Modifiers) في PHP
مستويات الوصول هي كلمات مفتاحية تُحدد المكان الذي يمكن منه الوصول إلى خصائص (Properties) ودوال (Methods) الصف. يخدم هذا المفهوم مبدأ "الكبسلة" (Encapsulation)، وهو عدم كشف البيانات الداخلية للكائن إلا من خلال واجهات مخصصة وآمنة.
توفر لغة PHP ثلاثة مستويات رئيسية للوصول:
- public (عام): يمكن الوصول إلى العنصر من أي مكان، سواء من داخل الصف، أو من الصفوف المشتقة منه، أو من خارج الصف تماماً عبر الكائن.
- protected (محمي): يمكن الوصول إلى العنصر فقط من داخل الصف نفسه، أو من داخل الصفوف التي ترث منه (Child Classes)، ولا يمكن الوصول إليه مباشرة من خارج الكائن.
- private (خاص): يمكن الوصول إلى العنصر فقط حكماً من داخل الصف الذي عُرّف فيه. لا يمكن للصفوف الوارثة ولا للكود الخارجي الوصول إليه.
لنراقب هذا السلوك عبر مثال برمجية بسيط يوضح الفروق الجوهرية بين هذه المستويات:
<?php
class Account {
public string $username = "guest";
protected string $email = "user@example.com";
private string $passwordHash = "secret_hash_123";
public function getDetails(): string {
// يمكن الوصول لجميع المستويات من داخل الصف نفسه
return "User: {$this->username}, Email: {$this->email}, Hash: {$this->passwordHash}";
}
}
$user = new Account();
// 1. الوصول للخاصية العامة (مسموح)
echo $user->username; // يُطبع: guest
// 2. محاولة الوصول للخاصية المحمية (سيتسبب في خطأ Fatal Error)
// echo $user->email;
// 3. محاولة الوصول للخاصية الخاصة (سيتسبب في خطأ Fatal Error)
// echo $user->passwordHash;
// 4. استدعاء دالة عامة تقرأ البيانات الداخلية (مسموح)
echo $user->getDetails();
?>شرح الكود:
في هذا المثال، تسمح الخاصية public بالتعديل والقراءة المباشرة من الخرج. بينما تُعطّل الخصائص protected و private أي محاولة قراءة أو تعديل تأتي من النطاق العام (Global Scope). الاستثناء الوحيد لقراءة الخصائص المحمية أو الخاصة هو عبر دوال عامة (Public Methods) يوفرها الصف نفسه، مثل الدالة getDetails().
مفهوم الوراثة (Inheritance) باستخدام الكلمة المفتاحية extends
الوراثة هي آلية تسمح لصف جديد (تلميذ/ابن Child Class) بالحصول على الخصائص والدوال الخاصة بصف قائم بالفعل (أب/أساس Base/Parent Class). تسمح لك هذه الميزة بكتابة المنطق المشترك مرة واحدة في الصف الأب، ثم توسيعه في الصفوف الابنة دون تكرار الكود (DRY - Don't Repeat Yourself).
في PHP، تُستخدم الكلمة المفتاحية extends لربط الصف الابن بالصف الأب. تجدر الإشارة إلى أن PHP تدعم الوراثة الأحادية فقط (Single Inheritance)، مما يعني أن الصف الابن يمكنه أن يرث من صف أب واحد فقط في نفس الوقت.
مثال عملي: نظام مستخدمي موقع الويب
لنبدأ بتصميم نظام إدارة مستخدمين لصفحة ويب، حيث يوجد لدينا نوعان من المستخدمين: مستخدم عادي (User) ومُشرف على النظام (Admin). المشرف هو مستخدم عادي يمتلك صلاحيات إضافية.
<?php
// الصف الأب (Base Class)
class User {
protected string $name;
protected string $email;
public function __construct(string $name, string $email) {
$this->name = $name;
$this->email = $email;
}
public function getProfile(): string {
return "الاسم: {$this->name} | البريد: {$this->email}";
}
}
// الصف الابن (Child Class) يرث من User
class Admin extends User {
private array $permissions;
public function __construct(string $name, string $email, array $permissions = []) {
// استدعاء الدالة البانية للصف الأب لتمرير البيانات الأساسية
parent::__construct($name, $email);
$this->permissions = $permissions;
}
public function getAdminInfo(): string {
// يمكننا الوصول للخصائص name و email لأنها معرّفة كـ protected في الصف الأب
$profile = $this->getProfile();
$permList = implode(", ", $this->permissions);
return "{$profile} | الصلاحيات: [{$permList}]";
}
}
// إنشائ كائن من الصف الابن
$admin = new Admin("أحمد محمود", "ahmed@sitegeeky.com", ["delete_posts", "ban_users"]);
echo $admin->getProfile();
// مخرجات: الاسم: أحمد محمود | البريد: ahmed@sitegeeky.com
echo "<br>";
echo $admin->getAdminInfo();
// مخرجات: الاسم: أحمد محمود | البريد: ahmed@sitegeeky.com | الصلاحيات: [delete_posts, ban_users]
?>تحليل كود الوراثة:
- الكلمة المفتاحية
extends: جعلت الصفAdminيكتسب الخصائص والدوال الخاصة بـUser. - الكلمة المفتاحية
parent: استُخدمت داخل الدالة البانية لـAdminكـparent::__construct()، وذلك لضمان تنفيذ المنطق الموجود في بناء الصف الأب أولاً بدلاً من إعادة كتابته. - الوصول للخصائص المحمية: لو كانت $name و $email في الصف
Userمعرّفة كـprivate، لما استطاع الصفAdminقراءتها مباشرة. لذلك استخدمناprotectedلتسهيل التوسعة في الصفوف المشتقة مع بقائها مخفية عن الخرج العام.
إعادة كتابة الدوال (Method Overriding)
قد يحتاج الصف الابن إلى تغيير طريقة عمل دالة ورثها من الصف الأب لتناسب متطلباته الخاصة. يُعرف هذا بـ "إعادة كتابة الدوال" أو (Method Overriding).
لإعادة كتابة دالة، يقوم الصف الابن بإنشاء دالة تحمل نفس الاسم ونفس التوقيع (Signature) الخاص بالدالة في الصف الأب.
<?php
class User {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
public function renderDashboard(): string {
return "مرحباً بك {$this->name} في لوحة التحكم العادية.";
}
}
class PremiumUser extends User {
// إعادة كتابة الدالة لتشمل تخصيصات إضافية
public function renderDashboard(): string {
$baseText = parent::renderDashboard();
return $baseText . " تم تفعيل ميزات العضوية المدفوعة!";
}
}
$standard = new User("سارة");
echo $standard->renderDashboard();
// مخرجات: مرحباً بك سارة في لوحة التحكم العادية.
echo "<br>";
$vip = new PremiumUser("خالد");
echo $vip->renderDashboard();
// مخرجات: مرحباً بك خالد في لوحة التحكم العادية. تم تفعيل ميزات العضوية المدفوعة!
?>في هذا المثال، استخدمنا parent::renderDashboard() للوصول إلى النتيجة الأصلية للصف الأب، ثم أضفنا نصاً جديداً إليها، مما حافظ على تسلسل منطق التطبيق بدون تكرار.
منع الوراثة أو التعديل باستخدام الكلمة المفتاحية final
في بعض أجزاء التطبيقات الحساسة—مثل دوال معالجة التشفير أو أجزاء منطق الأمان—قد ترغب في منع الصفوف الأخرى من الوراثة منك، أو منع إعادة كتابة دالة معينة. توفر PHP الكلمة المفتاحية final لهذا الغرض.
- إذا وضعت
finalقبل اسم الصف، فلن يستطيع أي صف آخر أن ينفذextendsمنه. - إذا وضعت
finalقبل اسم الدالة، فلن يستطيع أي صف ابن إعادة كتابة هذه الدالة (Overriding).
<?php
class SecuritySystem {
// دالة نهائية لا يمكن إعادة كتابتها في أي صف مشتق
final public function hashPassword(string $password): string {
return password_hash($password, PASSWORD_BCRYPT);
}
}
// صف نهائي كامل لا يمكن الوراثة منه
final class EncryptionHelper extends SecuritySystem {
// حاول إعادة كتابة hashPassword وسيعطيك PHP خطأً نظامياً
}
// المحاولة التالية ستؤدي إلى خطأ سينتاكس فوري:
// class SubEncryption extends EncryptionHelper {} // Fatal Error
?>تطبيق عملي: بناء نظام بوابات دفع إلكترونية (Payment Gateways)
لتوضيح الصورة بشكل أفضل ضمن سياق تطوير الويب المتقدم، لنبني نظاماً مصغراً للتعامل مع بوابات الدفع الإلكتروني (مثل PayPal و Stripe). يوضح هذا المثال كيف توفر الوراثة ومستويات الوصول بنية برمجية متماسكة وسهلة الصيانة.
<?php
// الصف الأساسي لجميع بوابات الدفع
abstract class PaymentGateway {
protected string $apiKey;
protected string $merchantId;
public function __construct(string $apiKey, string $merchantId) {
$this->apiKey = $apiKey;
$this->merchantId = $merchantId;
}
// دالة تسجيل العمليات لغرض التتبع (Logging)
protected function logTransaction(float $amount, string $status): void {
// في الواقع يتم التخزين في قاعدة البيانات هنا
echo "[LOG]: تم تنفيذ عملية بقيمة {$amount} - الحالة: {$status}<br>";
}
abstract public function processPayment(float $amount): bool;
}
// بوابة دفع PayPal
class PayPalGateway extends PaymentGateway {
private string $paypalEmail;
public function __construct(string $apiKey, string $merchantId, string $paypalEmail) {
parent::__construct($apiKey, $merchantId);
$this->paypalEmail = $paypalEmail;
}
public function processPayment(float $amount): bool {
// منطق الاتصال بـ API الخاص بـ PayPal
echo "جاري الاتصال بـ PayPal باستخدام الحساب {$this->paypalEmail}...<br>";
// استدعاء دالة التسجيل المحمية من الصف الأب
$this->logTransaction($amount, "SUCCESS");
return true;
}
}
// بوابة دفع Stripe
class StripeGateway extends PaymentGateway {
public function processPayment(float $amount): bool {
// منطق الاتصال بـ API الخاص بـ Stripe
echo "جاري معالجة البطاقة عبر Stripe باستخدام المفتاح: " . substr($this->apiKey, 0, 5) . "...<br>";
$this->logTransaction($amount, "SUCCESS");
return true;
}
}
// استخدام النظام في التطبيق
$paypal = new PayPalGateway("pay_secret_9988", "m_123", "billing@sitegeeky.com");
$paypal->processPayment(150.00);
echo "<hr>";
$stripe = new StripeGateway("sk_live_51Mz...", "m_456");
$stripe->processPayment(299.99);
?>فوائد التصميم السابق:
- الأمان والكبسلة: الخصائص الحساسة مثل
$apiKeyمحفوظة كـprotected، فلا يمكن لأي كود خارجي تعديل المفاتيح أثناء العمل. - تقليل التكرار: دالة تسجيل العمليات
logTransactionومفاتيح الدخول عُرّفت مرة واحدة في الصف الأب واستُخدمت في الصفوف الفرعية. - قابلية التوسع: لإضافة بوابة دفع جديدة (مثل Tap أو Moyasar)، يكفي إنشاء صف جديد ينفذ
extends PaymentGatewayدون المساس بالكود القديم.
أخطاء شائعة عند التعامل مع الوراثة ومستويات الوصول
- جعل جميع الخصائص Public: يقع المبتدئون في خطأ جعل جميع المتغيرات
publicلتفادي مشاكل الوصول. يكسر هذا مبدأ الكبسلة ويجعل تتبع أخطاء تعديل البيانات صعبة للغاية في المشاريع الكبيرة. - التعمق المفرط في شجرة الوراثة (Deep Inheritance): إنشائ سلاسل وراثة طويلة جداً (مثلاً الصف A يمتد من B، و B من C، و C من D...) يجعل الكود معقداً وصعب القراءة والصيانة. يُفضل إبقاء التسلسل الهرمي ضيقاً وضحلاً.
- نسيان استدعاء الباني الأب (Parent Constructor): عند كتابة
__constructفي الصف الابن، ينبغي ألا تنسى استدعاءparent::__construct()إذا كان الصف الأب يتطلب تهيئة خصائص ضرورية. - خلط Private مع Protected: إذا كنت تخطط لإنشاء صفوف فرعية تعتمد على خصائص الأب، فلا تجعل تلك الخصائص
private، بل استخدمprotected.
خلاصة الدرس
تُعد الوراثة ومستويات الوصول أداة تنظيمية وأمنية لا غنى عنها عند التفكير بمنطق OOP في PHP. بفضل الوراثة، تصبح بناء الأنظمة المعقدة أكثر سلاسة من خلال إعادة استخدام الكود المكتوب سابقاً، بينما تضمن لك مستويات الوصول عدم عبث الأجزاء الخارجية ببيانات الكائنات الداخلية.
في الدرس القادم، سننتقل إلى مرحلة أكثر تقدماً في التصميم والمعمارية عبر دراسة الصفوف المجرّدة (Abstract Classes) والواجهات (Interfaces)، وكيف يحددان العقود (Contracts) البرمجية داخل تطبيقات الويب المتكاملة.
