الأصناف والكائنات لتنظيم الكود
اجمع البيانات وقواعدها في وحدة واحدة تحميها. ⏱ نحو 35 دقيقة.
- تشرح الصنف والكائن والخصائص والدوال والمُنشئ.
- تنشئ الكائنات وتستخدمها بالسهم
->و $this. - تحمي بيانات الكائن بـ
privateوتطبّق القواعد في دواله. - تضع الصنف في ملف مستقل وتعيد استخدامه، وتعرف الوراثة.
١من الدوال المتفرقة إلى الأصناف
كلما كبر التطبيق كثرت الدوال والمتغيرات المترابطة: سلة المشتريات لها بياناتها (العناصر) ولها عملياتها (أضف، احسب المجموع). الصنف Class يجمعهما في وحدة واحدة، والكائن Object نسخة حقيقية منه. وإن درست البرمجة الكائنية في بايثون أو جافا فالأفكار نفسها، بصياغة PHP:
| بايثون | PHP | |
|---|---|---|
| تعريف صنف | class Product: | class Product { … } |
| المُنشئ | def __init__(self, name): | public function __construct(string $name) |
| الكائن الحالي | self.name | $this->name |
| إنشاء كائن | p = Product("قهوة") | $p = new Product("قهوة"); |
| استدعاء دالة | p.label() | $p->label() |
٢أول صنف
<?phpclass Product{ public string $name; public float $price; public function __construct(string $name, float $price) { $this->name = $name; $this->price = $price; } public function label(): string { return "$this->name: " . number_format($this->price, 2) . " ريال"; }} $a = new Product("قهوة عربية", 35);$b = new Product("تمر سكري", 45.5);echo $a->label() . "\n";echo $b->label() . "\n";echo $b->name;
- الخصائص
Properties: متغيرات الكائن، مثل$name. ولكل كائن نسخته. - الدوال
Methods: ما يفعله الكائن. داخلها تصل لخصائصه بـ$this->. - المُنشئ
__construct: يعمل تلقائيًّا معnewليجهّز الكائن. - السهم
->للوصول إلى خاصية أو دالة في الكائن (مقابل النقطة في بايثون).
وفي PHP 8 اختصار جميل: تعريف الخصائص داخل المُنشئ مباشرة (Constructor promotion):
<?phpclass Product{ public function __construct( public string $name, public float $price, ) {}}
٣الإخفاء: private تحمي قواعدك
لو كانت عناصر السلة public لاستطاع أي كود أن يضع فيها كمية سالبة مباشرة. نجعلها private، فلا يغيّرها أحد إلا عبر دوال الصنف التي تطبّق القواعد. هذا مبدأ التغليف Encapsulation:
<?phpclass Cart{ private array $items = []; public function add(string $name, float $price, int $qty = 1): void { if ($qty < 1) { return; // قاعدة: لا كميات سالبة أو صفرية } $this->items[$name] = [ 'price' => $price, 'qty' => ($this->items[$name]['qty'] ?? 0) + $qty, ]; } public function count(): int { return array_sum(array_column($this->items, 'qty')); } public function total(): float { $t = 0; foreach ($this->items as $it) { $t += $it['price'] * $it['qty']; } return $t; }} $cart = new Cart();$cart->add('قهوة', 35, 2);$cart->add('تمر', 45);$cart->add('قهوة', 35); // تزيد الكمية ولا تكرر العنصر$cart->add('شاي', 18, -5); // تُرفض حسب القاعدة echo "العناصر: " . $cart->count() . "\n";echo "المجموع: " . $cart->total() . "\n";
| الكلمة | من يصل إلى العضو؟ |
|---|---|
public | أي كود. |
private | دوال الصنف نفسه فقط. |
protected | الصنف والأصناف التي ترثه. |
جرّب في المختبر أن تكتب بعد إنشاء السلة $cart->items = [];: ستحصل على خطأ، لأن الخاصية private. وهذا بالضبط ما نريده.
٤صنف في ملف: وحدة قابلة لإعادة الاستخدام
العادة: كل صنف في ملف باسمه، داخل مجلد مثل src. والصفحات تضمّنه بـ require_once وتستخدمه:
<?phpsession_start();require_once __DIR__ . '/src/Cart.php'; $cart = new Cart();$cart->add('قهوة', 35, 2);$cart->add('تمر', 45);?><p>في السلة <?= $cart->count() ?> عناصر، والمجموع <?= $cart->total() ?> ريال.</p>
<?phpclass Cart{ private array $items = []; public function add(string $name, float $price, int $qty = 1): void { if ($qty < 1) { return; // قاعدة: لا كميات سالبة أو صفرية } $this->items[$name] = [ 'price' => $price, 'qty' => ($this->items[$name]['qty'] ?? 0) + $qty, ]; } public function count(): int { return array_sum(array_column($this->items, 'qty')); } public function total(): float { $t = 0; foreach ($this->items as $it) { $t += $it['price'] * $it['qty']; } return $t; }}
٥الوراثة باختصار
صنف يرث آخر بالكلمة extends، فيأخذ كل ما فيه ويضيف أو يعدّل:
<?phpclass User{ public function __construct(protected string $name) {} public function role(): string { return 'مستخدم'; } public function badge(): string { return "$this->name (" . $this->role() . ")"; }} class Admin extends User{ public function role(): string { return 'مدير'; }} $users = [new User('نورة'), new Admin('سعود')];foreach ($users as $u) { echo $u->badge() . "\n";}
الدالة badge() واحدة، لكنها أعطت نتيجتين لأن كل كائن نفّذ role() الخاصة به. وسنستخدم أصنافًا كهذه حين نبني تسجيل الدخول والصلاحيات.
توقّع وتحقّق
للوصول إلى دالة في كائن نكتب:
->الدالة التي تعمل تلقائيًّا عند new:
خاصية private يصل إليها:
الكلمة التي تجعل صنفًا يرث آخر:
class Admin extends Userالخلاصة
class= بيانات + عمليات. والكائن نسخة منه بـnew.- $
this->خاصية داخل الصنف، و $obj->دالة()خارجه. __constructيجهّز الكائن، واختصارPHP 8: معاملات المُنشئ بـpublic/private.private+ دوال تطبّق القواعد = بيانات لا تفسد (التغليف).- صنف لكل ملف في
src، وextendsللوراثة.
تحدٍّ برمجي: صنف المتدرب
متوسطاكتب الصنف Trainee في src/Trainee.php، واستخدمه في report.php لطباعة تقرير الدرجات.
- المُنشئ يستقبل الاسم
string $nameويخزنه في خاصيةprivate، والدرجات مصفوفة private تبدأ فارغة. - الدالة
addGrade(float $g): voidتضيف الدرجة فقط إن كانت بين 0 و 100. - الدالة
average(): floatتعيد المتوسط مقرّبًا لخانة واحدة (0 إن لم توجد درجات). - الدالة
summary(): stringتعيد مثلًا:ريم: 86.7 (ناجح)وناجح إن كان المتوسط 60 فأكثر، وإلا «متعثر». - report.php جاهز؛ لا تغيّره.
👀 اعرض الاستجابة المطلوبة
GET /report.php
- الكود يعمل بلا أخطاء أو تحذيرات
- ريم: 86.7 (ناجح) — والدرجة 150 مرفوضة
- خالد: 55 (متعثر)
- هند بلا درجات: 0 (متعثر)
- الدرجات والاسم خصائص private
- تستخدم $this-> للوصول إلى الخصائص
💡 تلميح 1
public function __construct(private string $name) {}💡 تلميح 2
round(array_sum($this->grades) / count($this->grades), 1) بعد التأكد أن المصفوفة غير فارغة.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
<?phprequire_once __DIR__ . '/src/Trainee.php'; $a = new Trainee('ريم');$a->addGrade(90);$a->addGrade(85);$a->addGrade(85);$a->addGrade(150); // مرفوضة $b = new Trainee('خالد');$b->addGrade(40);$b->addGrade(70); $c = new Trainee('هند'); echo "<p>" . $a->summary() . "</p>";echo "<p>" . $b->summary() . "</p>";echo "<p>" . $c->summary() . "</p>";
<?phpclass Trainee{ private array $grades = []; public function __construct(private string $name) {} public function addGrade(float $g): void { if ($g >= 0 && $g <= 100) { $this->grades[] = $g; } } public function average(): float { if (!$this->grades) { return 0; } return round(array_sum($this->grades) / count($this->grades), 1); } public function summary(): string { $avg = $this->average(); $status = $avg >= 60 ? 'ناجح' : 'متعثر'; return "$this->name: $avg ($status)"; }}