تعلم PHP : البرمجة كائنية التوجه
إذا كنت تكتب PHP منذ فترة وتعتمد غالبًا على الدوال والمتغيرات والصفحات البسيطة، فهناك لحظة معيّنة يبدأ فيها كل شيء يتغيّر: لحظة تكتشف فيها أن الكود يمكن أن يصبح أكثر تنظيمًا، وأسهل في التوسعة، وأقل فوضى بكثير عندما تفكر فيه على هيئة “كائنات” و“أصناف”. هذا هو مدخل البرمجة كائنية التوجه في PHP، وهو الباب الذي ينقلك من مجرد “كتابة سكربت يعمل” إلى “بناء نظام يمكن أن يعيش ويكبر”. كثير من المطورين يظنون أن OOP موضوع نظري أو معقّد أكثر من اللازم، لكن الحقيقة أبسط من ذلك بكثير: إذا فهمت الصنف class على أنه “قالب”، وفهمت الكائن object على أنه “نسخة حقيقية من هذا القالب”، فستكون قد أمسكت الخيط الأول المهم جدًا. من هنا تبدأ الصورة في الوضوح. بدل أن تكتب بيانات متفرقة في أماكن كثيرة، يمكنك أن تجمعها داخل كيان واحد يعبّر عن شيء حقيقي في مشروعك: مستخدم، كتاب، طلب، منتج، مقال، عربة تسوق، أو حتى جلسة تسجيل دخول. وعندما تبدأ في التفكير بهذه الطريقة، يصبح كودك أكثر قابلية للفهم بعد أسبوع، وبعد شهر، وبعد سنة، وهذا بحد ذاته فرق كبير جدًا.
في هذا المقال سنمشي بهدوء لكن بعمق في عالم PHP OOP، وسنركز خصوصًا على الأصناف والكائنات، لأنهما العمود الفقري لكل ما يأتي بعد ذلك. سنشرح المفهوم، ونرى كيف نكتب class وننشئ object، ونفهم الخصائص properties والطرق methods، ثم ننتقل إلى الرؤية والخصوصية public/private/protected، ثم البناء constructor، ثم هذا ومفهوم الكائن الحالي، ثم الإحصائيات static، ثم الوراثة inheritance، ثم التجريد abstract classes، ثم الواجهات interfaces، ثم الـ traits، ثم أسماء النطاقات namespaces، ثم التحميل التلقائي autoloading، مع أمثلة عملية كثيرة. وسأحاول أن أجعل الأمثلة قريبة من الواقع حتى لا يبقى الكلام نظريًا فقط، لأن أجمل طريقة لفهم PHP Objects and Classes هي أن تراها وهي تعمل فعلًا.
ما هي البرمجة كائنية التوجه في PHP؟
البرمجة كائنية التوجه، أو Object-Oriented Programming، هي طريقة لتنظيم الكود حول “الأشياء” بدل تنظيمه فقط حول “الإجراءات”. في البرمجة الإجرائية التقليدية، قد تجد نفسك تكتب مجموعة دوال لمعالجة المستخدمين، ومجموعة أخرى لمعالجة المنتجات، وأخرى للطلبات، وكل شيء يصبح قابلًا للنمو بشكل متداخل، وقد تبدأ المشكلة عندما تحتاج إلى تعديل جزء صغير فتكتشف أن التغييرات أثرت على أجزاء كثيرة. أما في OOP، فأنت تجمع البيانات والسلوك المرتبط بها داخل كيان واحد. المستخدم، مثلًا، ليس مجرد مصفوفة من الاسم والبريد الإلكتروني، بل هو كائن له خصائص مثل الاسم والبريد والحالة، وله سلوك مثل تسجيل الدخول وتحديث الملف الشخصي والتحقق من الصلاحية.
عندما تفكر بهذه الطريقة، يصبح التمثيل البرمجي أقرب إلى الواقع. في العالم الحقيقي، الشخص له اسم وعمر وعنوان، ويمكنه أن يتحدث أو يعمل أو يسجّل حضوره. في PHP، يمكنك إنشاء class يمثل هذا الشخص، ثم تنشئ منه objects متعددة تمثل أشخاصًا مختلفين. القالب واحد، لكن النسخ كثيرة. وهذا بالضبط ما يجعل الكود مرنًا جدًا. بدل أن تعيد كتابة نفس المنطق مرات كثيرة، تكتب مرة واحدة داخل class، ثم تستخدمه أينما احتجت. وهذه ليست فقط مسألة “اختصار أسطر”، بل مسألة “اختصار أخطاء” أيضًا، لأنك عندما تعدّل المنطق في مكان واحد، ينعكس التعديل على كل الأماكن التي تستخدم هذا الصنف.
ما هو الصنف Class؟
الصنف class هو الوصفة أو النموذج الذي يصف كيف سيكون الكائن. يمكن أن تتخيل الصنف كقالب ورقي في مطبعة؛ القالب نفسه لا يمثّل الشيء النهائي، لكنه يحدد الشكل العام. إذا كان لديك class اسمه User، فهذا لا يعني أنك أنشأت مستخدمًا فعليًا، بل يعني أنك عرّفت شكل المستخدم: ما هي بياناته؟ ما هي الأفعال التي يستطيع تنفيذها؟ ثم بعد ذلك، عندما تستدعي هذا القالب، تحصل على object حقيقي.
لنأخذ مثالًا بسيطًا جدًا:
<?php
class User
{
public $name;
public $email;
}
هنا أنشأنا class اسمه User. داخل هذا الصنف يوجد خاصيتان public: name و email. الصنف وحده لا يفعل شيئًا بعد، لكنه يحدد ما الذي يمكن أن يحمله المستخدم من بيانات. الآن يمكننا إنشاء object منه:
<?php
$user = new User();
$user->name = "Hassan";
$user->email = "hassan@example.com";
echo $user->name;
النتيجة هنا ستكون Hassan. لاحظ الفكرة الأساسية: User هو القالب، و$user هو نسخة من هذا القالب. وإذا أردنا مستخدمًا آخر، ننشئ object آخر من نفس الصنف. هذا ما يجعل البرمجة كائنية التوجه قوية جدًا، لأنك لا تبني كل شيء من الصفر كل مرة، بل تبني نظامًا من القوالب والنسخ.
ما هو الكائن Object؟
الكائن object هو النسخة الفعلية التي يتم إنشاؤها من الصنف. إذا كان class هو مخطط المنزل، فالكائن هو البيت المبني فعليًا على هذا المخطط. الكائن يمتلك قيمًا حقيقية للخصائص، ويمكنه تنفيذ الطرق الموجودة في الصنف. في PHP، يتم إنشاء الكائن باستخدام الكلمة new.
مثال أبسط:
<?php
class Car
{
public $brand;
public $color;
}
$car1 = new Car();
$car1->brand = "Toyota";
$car1->color = "Black";
$car2 = new Car();
$car2->brand = "BMW";
$car2->color = "White";
echo $car1->brand; // Toyota
echo $car2->brand; // BMW
هنا لديك كائنان مختلفان من نفس الصنف. كل واحد له قيم خاصة به، رغم أن الهيكل نفسه واحد. وهذه نقطة مهمّة جدًا لأن بعض المبتدئين يظنون أن الصنف والكائن شيء واحد. ليس تمامًا. الصنف هو التعريف، والكائن هو التطبيق.
الخصائص Properties
الخصائص هي البيانات التي يحملها الصنف. في مثال المستخدم، الخصائص قد تكون الاسم، البريد الإلكتروني، العمر، الحالة، تاريخ الإنشاء، أو أي شيء آخر. الخصائص تمثّل “ما يملكه” الكائن. في PHP، نعلن عنها داخل الصنف عادة باستخدام public أو protected أو private.
مثال:
<?php
class Product
{
public $title;
public $price;
public $stock;
}
هنا الصنف Product يحتوي على ثلاث خصائص. يمكننا استخدامها هكذا:
<?php
$product = new Product();
$product->title = "Laptop";
$product->price = 1200;
$product->stock = 5;
echo $product->title;
عندما ترى الخصائص، فكّر فيها كبيانات مرتبطة بالكائن. هذا الارتباط مهم جدًا، لأنك لا تريد أن تضيع البيانات في متغيرات عشوائية خارجية. كلما جمعت البيانات والسلوك معًا، كلما صار المشروع أوضح وأسهل في الصيانة.
الطرق Methods
الطرق أو methods هي الدوال الموجودة داخل الصنف. إذا كانت الخصائص تمثل “ما يملكه” الكائن، فالطرق تمثل “ما يفعله” الكائن. مثلًا، مستخدم يمكنه التسجيل، تسجيل الدخول، تحديث الملف الشخصي، أو الخروج. منتج يمكنه تغيير السعر أو حساب الضريبة. طلب يمكنه الانتقال من حالة إلى أخرى.
مثال:
<?php
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
public function subtract($a, $b)
{
return $a - $b;
}
}
$calc = new Calculator();
echo $calc->add(5, 3);
الطريقة add هنا موجودة داخل الصنف Calculator. نستدعيها من الكائن باستخدام السهم ->. هذا السهم من العلامات المهمة جدًا في PHP OOP، لأنه يربط الكائن بطريقة أو خاصية داخل الصنف.
من الجميل أن تدرك أن methods ليست مجرد “دوال داخل class”، بل هي جزء من سلوك الكائن نفسه. وهذا يجعل الكود أكثر طبيعية. بدل أن يكون لديك دالة خارجية تأخذ البيانات كوسيطات من كل مكان، يصبح الكائن نفسه مسؤولًا عن سلوكه.
بناء Constructor
الـ constructor هو دالة خاصة يتم استدعاؤها تلقائيًا عند إنشاء object جديد. فائدته الأساسية هي تهيئة الخصائص بالقيم الأولية. بدل أن تنشئ object ثم تعطيه القيم واحدة واحدة، يمكنك تمرير القيم منذ اللحظة الأولى.
مثال:
<?php
class User
{
public $name;
public $email;
public function __construct($name, $email)
{
$this->name = $name;
$this->email = $email;
}
}
$user = new User("Hassan", "hassan@example.com");
echo $user->name;
هنا بمجرد أن كتبنا new User(...)، تم استدعاء __construct تلقائيًا. هذا مفيد جدًا عندما تحتاج إلى ضمان أن الكائن يُنشأ بحالة صحيحة. بدل أن تقول: “سأنشئ مستخدمًا ثم أملأ بياناته لاحقًا”، يمكنك أن تقول: “عند الولادة، هذا المستخدم يجب أن يحمل اسمه وبريده”.
هذا الأسلوب يقلل الأخطاء، خصوصًا في المشاريع الكبيرة. لأنك عندما تفرض على نفسك أن الكائن لا يُستخدم إلا بعد تهيئته بشكل صحيح، تقلّ الحالات الغريبة التي تسبب مشاكل في وقت لاحق.
كلمة this: الإشارة إلى الكائن الحالي
داخل الصنف، غالبًا ستحتاج إلى الإشارة إلى الخصائص أو الطرق الخاصة بالكائن الحالي. هنا يأتي دور $this. الكلمة $this تشير إلى الكائن نفسه الذي يعمل الآن داخل الطريقة. عندما نكتب $this->name، فنحن نقول: “خذ خاصية name الموجودة في هذا الكائن الحالي”.
مثال واضح:
<?php
class Book
{
public $title;
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
$book = new Book();
$book->setTitle("The Power of Now");
echo $book->getTitle();
بدون $this، لن يعرف PHP أنك تقصد خاصية الكائن، لأن المتغير title داخل الدالة قد يكون مجرد متغير محلي. لذلك $this أساسية جدًا في فهم OOP. كثير من الأخطاء الأولى في PHP OOP تحدث بسبب الخلط بين المتغيرات المحلية وخصائص الكائن.
محددات الوصول: public و private و protected
واحدة من أهم أفكار البرمجة كائنية التوجه هي “التغليف” أو encapsulation. والفكرة هنا بسيطة: ليس من الضروري أن يكون كل شيء مكشوفًا من الخارج. أحيانًا تريد أن تسمح بالوصول، وأحيانًا تريد أن تمنعه، وأحيانًا تريد الوصول فقط من داخل الصنف نفسه ومن الأصناف الوريثة. لهذا نستخدم محددات الوصول.
public
أي شيء معلن بـ public يمكن الوصول إليه من داخل الصنف وخارجه.
<?php
class User
{
public $name;
}
private
أي شيء معلن بـ private لا يمكن الوصول إليه إلا داخل الصنف نفسه.
<?php
class User
{
private $password;
public function setPassword($password)
{
$this->password = $password;
}
}
protected
أي شيء معلن بـ protected يمكن الوصول إليه داخل الصنف نفسه، وكذلك داخل الأصناف التي ترث منه، لكنه لا يكون متاحًا مباشرة من الخارج.
<?php
class Animal
{
protected $name;
}
هذه الفكرة ليست تعقيدًا زائدًا، بل هي طريقة لحماية البيانات وتنظيم الوصول إليها. في المشاريع الجدية، لا تريد أن يعبث أي جزء من التطبيق بأي قيمة من دون ضوابط. مثلًا، لا تريد أن يغيّر أحد كلمة المرور مباشرة عبر $user->password = '...'. الأفضل أن تجبره على استخدام method مخصصة تقوم بالتحقق والتشفير والحفظ بالشكل الصحيح.
مثال عملي: حساب المستخدم
لنأخذ مثالًا أكثر واقعية. لنفترض أننا نبني كلاس يمثل مستخدمًا.
<?php
class User
{
private $name;
private $email;
public function __construct($name, $email)
{
$this->name = $name;
$this->email = $email;
}
public function getName()
{
return $this->name;
}
public function getEmail()
{
return $this->email;
}
public function updateEmail($newEmail)
{
if (filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
$this->email = $newEmail;
return true;
}
return false;
}
}
$user = new User("Sara", "sara@example.com");
echo $user->getName();
هنا جعلنا الخصائص private بدل public. لماذا؟ لأننا لا نريد أن يغير أي أحد القيم مباشرة. بدلًا من ذلك، نستخدم methods مثل getName و updateEmail. هذا أسلوب أنظف وأكثر أمانًا. وهو من النقاط التي تجعل OOP ليست مجرد شكل للكود، بل أسلوبًا في التفكير.
getters و setters
غالبًا ستسمع هذين المصطلحين كثيرًا في عالم OOP. الـ getter هي دالة تسترجع قيمة خاصية، والـ setter هي دالة تضبط قيمة خاصية. استخدامهما يساعدك على التحكم في القيم بدل تركها مفتوحة.
مثال:
<?php
class Person
{
private $age;
public function setAge($age)
{
if ($age < 0) {
throw new Exception("Age cannot be negative");
}
$this->age = $age;
}
public function getAge()
{
return $this->age;
}
}
لاحظ هنا الفائدة الحقيقية. لو كانت age public، ربما يكتب أحدهم -10 أو قيمة غير منطقية. لكن عبر setter، يمكنك وضع قواعد. هذا هو الفرق بين كود عشوائي وكود منضبط. في المشاريع الكبيرة، هذه الضوابط تمنع الكثير من الأعطال.
الثوابت Constants داخل الأصناف
أحيانًا تحتاج إلى قيمة لا تتغير، وتكون مرتبطة بالصنف نفسه. هنا نستخدم const.
<?php
class MathHelper
{
const PI = 3.14159;
}
echo MathHelper::PI;
لاحظ هنا أننا لا ننشئ object للوصول إلى الثابت. نستخدم :: وهو ما يسمى scope resolution operator أو “مشغّل النطاق”. هذا المشغّل مهم جدًا عندما تتعامل مع static members أو constants أو استدعاء طرق ثابتة.
الطرق الثابتة Static Methods
الطريقة الثابتة تنتمي إلى الصنف نفسه لا إلى كائن معيّن. هذا يعني أنك تستدعيها مباشرة من خلال اسم الصنف.
<?php
class StringHelper
{
public static function toUpper($text)
{
return strtoupper($text);
}
}
echo StringHelper::toUpper("hello");
متى تستخدم static؟ عندما تكون العملية عامة ولا تحتاج إلى حالة object. لكن لا تفرط في استخدامها. لأن الاعتماد الزائد على static قد يجعل الكود أقل مرونة وأصعب في الاختبار. من الأفضل استخدامها عندما يكون المعنى واضحًا: عمليات مساعدة، إعدادات، حسابات عامة، أو أدوات لا تحتاج إلى بيانات داخل object.
الخصائص الثابتة Static Properties
يمكن أيضًا أن تكون الخاصية ثابتة:
<?php
class Counter
{
public static $count = 0;
public function __construct()
{
self::$count++;
}
}
new Counter();
new Counter();
echo Counter::$count;
هنا استخدمنا self:: للوصول إلى الخاصية الثابتة من داخل الصنف. الفكرة أن هذه القيمة مشتركة بين كل الكائنات من نفس الصنف، وليست قيمة مستقلة لكل object. وهذا مفيد في حالات مثل العدّادات أو الإعدادات المشتركة.
الوراثة Inheritance
الوراثة من أهم ميزات OOP. تسمح لك بإنشاء صنف جديد مبني على صنف آخر. الصنف الجديد يرث الخصائص والطرق العامة والمحمية من الأب، ويمكنه إضافة أو تعديل ما يحتاجه. في PHP نستخدم extends.
مثال:
<?php
class Animal
{
public function eat()
{
return "Eating...";
}
}
class Dog extends Animal
{
public function bark()
{
return "Woof!";
}
}
$dog = new Dog();
echo $dog->eat();
echo $dog->bark();
هنا Dog يرث من Animal. وبالتالي يمكنه استخدام eat() رغم أننا لم نكتبها داخل Dog. هذا رائع لأنه يقلل التكرار. لكن يجب أن تستخدم الوراثة بحكمة. ليست كل علاقة بين كائنين تحتاج وراثة. أحيانًا composition أفضل، أي أن يكون الصنف “يحتوي” على شيء آخر بدل أن “يرث” منه مباشرة.
إعادة تعريف الطرق Override
عندما يرث صنف من صنف آخر، يمكنه أن يعيد تعريف طريقة موجودة بالفعل. هذا يسمى override.
<?php
class Animal
{
public function sound()
{
return "Some sound";
}
}
class Cat extends Animal
{
public function sound()
{
return "Meow";
}
}
$cat = new Cat();
echo $cat->sound();
هنا Cat غيّر سلوك sound. هذا مفيد جدًا عندما تكون هناك قاعدة عامة، لكن كل نوع يحتاج سلوكًا خاصًا به.
الدوال النهائية Final Methods و Final Classes
كلمة final تمنع الوراثة أو إعادة التعريف. إذا أعلنت class على أنها final، فلا يمكن لأي صنف أن يرث منها. وإذا أعلنت method على أنها final، فلا يمكن إعادة تعريفها في أبناء الصنف.
<?php
final class SecureConfig
{
public function getSecret()
{
return "secret";
}
}
هذا يستخدم عندما تريد تثبيت التصميم ومنع التعديلات اللاحقة. قد تحتاجه في بعض الحالات الحساسة أو عندما يكون التصميم مقصودًا أن يبقى ثابتًا.
الأصناف المجردة Abstract Classes
الصنف المجرد هو صنف لا يمكن إنشاء object منه مباشرة، لكنه يقدّم أساسًا للأصناف الأخرى. يمكن أن يحتوي على طرق عادية وطرق مجردة. الطريقة المجردة لا تحتوي على تنفيذ، بل تجبر الأبناء على توفير التنفيذ.
<?php
abstract class Payment
{
abstract public function pay($amount);
public function logPayment($amount)
{
return "Logged payment: " . $amount;
}
}
class CreditCardPayment extends Payment
{
public function pay($amount)
{
return "Paid " . $amount . " by credit card";
}
}
هذا مفيد جدًا عندما يكون لديك مفهوم عام، لكن التفاصيل تختلف من نوع إلى آخر. مثل الدفع، الشحن، أو الإشعارات.
الواجهات Interfaces
الواجهة interface تشبه العقد. هي تقول: “أي صنف يلتزم بهذه الواجهة يجب أن يوفّر هذه الطرق”. الواجهة لا تهتم بكيفية التنفيذ، بل فقط بما يجب أن يوجد.
<?php
interface Logger
{
public function log($message);
}
class FileLogger implements Logger
{
public function log($message)
{
return "Logged to file: " . $message;
}
}
الواجهات ممتازة عندما تريد فرض سلوك معيّن على عدة أصناف مختلفة. وهي من الأدوات المهمة جدًا في بناء تطبيقات مرنة وقابلة للتوسعة.
الفرق بين Abstract Classes و Interfaces
الـ abstract class يمكن أن يحتوي على تنفيذ وبعض الحالة والخصائص، بينما interface تركز على العقود فقط. إذا كنت تريد مشاركة سلوك أساسي بين عدة أصناف متقاربة، فالـ abstract class مناسبة. وإذا كنت تريد فرض طريقة معينة على أصناف غير متقاربة ربما من عوالم مختلفة، فالـ interface أنسب. أحيانًا تستخدم الاثنين معًا في نفس المشروع، وهذا أمر طبيعي جدًا.
Traits
الـ trait هو حل جميل عندما تريد مشاركة مجموعة من الطرق بين أصناف مختلفة من دون استخدام الوراثة الكاملة. لأن PHP لا تدعم الوراثة المتعددة للأصناف، لكن يمكن استخدام traits لإعادة استخدام الكود.
<?php
trait TimestampTrait
{
public function now()
{
return date('Y-m-d H:i:s');
}
}
class Post
{
use TimestampTrait;
}
$post = new Post();
echo $post->now();
الـ traits مفيدة جدًا في الكود الحقيقي عندما تجد نفسك تعيد نفس الطرق في أكثر من class. لكن، وكما هو الحال مع أي ميزة قوية، لا تكثر منها بشكل عشوائي. استخدمها عندما تكون فعلاً طريقة عملية لإعادة الاستخدام.
الأسماء namespaces
في المشاريع الكبيرة، تبدأ أسماء الأصناف بالتكاثر، وقد تجد صنفين يحملان نفس الاسم لكن في سياقات مختلفة. هنا يأتي دور namespaces. فهي تساعد على تنظيم الأصناف ومنع التعارض.
<?php
namespace App\Models;
class User
{
}
ثم يمكن استخدامه هكذا:
<?php
use App\Models\User;
$user = new User();
هذا مهم جدًا في مشاريع Laravel أو أي مشروع منظم. namespaces تجعل البنية أوضح، وتساعد على معرفة أين يوجد كل صنف، وما دوره، وكيف يرتبط بباقي التطبيق.
التحميل التلقائي Autoloading
عندما يكبر المشروع، لا تريد أن تكتب require أو include لكل ملف يدويًا. هنا نستخدم autoloading، وغالبًا عبر Composer. الفكرة أن PHP تحمل الملف المناسب تلقائيًا عند الحاجة إلى class.
في المشاريع الحديثة، هذه النقطة ليست رفاهية، بل أساس تقريبًا. لأن عدد الأصناف قد يصبح كبيرًا، والتنظيم اليدوي يصبح مرهقًا ومصدرًا للأخطاء. عندما تعتمد على autoloading، يصبح لديك هيكل نظيف، وتخف أعباء الإدارة اليدوية للملفات.
مثال تطبيقي: نظام كتب بسيط
لنطبّق ما تعلمناه على مثال يشبه الواقع: نظام بسيط لإدارة الكتب.
<?php
class Book
{
private $title;
private $author;
private $price;
public function __construct($title, $author, $price)
{
$this->title = $title;
$this->author = $author;
$this->price = $price;
}
public function getTitle()
{
return $this->title;
}
public function getAuthor()
{
return $this->author;
}
public function getPrice()
{
return $this->price;
}
public function applyDiscount($percent)
{
if ($percent < 0 || $percent > 100) {
throw new Exception("Invalid discount percentage");
}
$this->price = $this->price - ($this->price * $percent / 100);
}
}
$book = new Book("Clean Code", "Robert C. Martin", 100);
$book->applyDiscount(20);
echo $book->getTitle();
echo $book->getPrice();
في هذا المثال، الصنف Book يجمع كل ما يخص الكتاب. هذا منطق طبيعي جدًا. بدل أن تبقى البيانات موزعة في مصفوفات أو متغيرات منفصلة، أصبحت متماسكة داخل كائن واحد. وإذا أردت لاحقًا إضافة ISBN أو عدد الصفحات أو التصنيف، فالأمر سهل جدًا.
مثال آخر: سلة تسوق بسيطة
<?php
class Cart
{
private $items = [];
public function addItem($name, $price)
{
$this->items[] = [
'name' => $name,
'price' => $price
];
}
public function getTotal()
{
$total = 0;
foreach ($this->items as $item) {
$total += $item['price'];
}
return $total;
}
public function getItems()
{
return $this->items;
}
}
$cart = new Cart();
$cart->addItem("Keyboard", 50);
$cart->addItem("Mouse", 25);
echo $cart->getTotal();
هذا المثال يوضح لك كيف يمكن للصنف أن يحمل الحالة state ويتعامل معها. هنا الكائن Cart يعرف العناصر بداخله، ويحسب الإجمالي بنفسه. هذا أفضل من ترك الحساب موزعًا في أماكن كثيرة من التطبيق.
لماذا OOP مهم في مشاريع PHP الحديثة؟
لأن المشاريع الحديثة ليست صفحات قليلة تنتهي في يوم أو يومين. المشاريع الحقيقية تتوسع. قد تبدأ بصفحة تسجيل دخول فقط، ثم لوحة تحكم، ثم نظام صلاحيات، ثم إشعارات، ثم تقارير، ثم API، ثم تكامل مع خدمات خارجية. إذا لم يكن الكود منظّمًا، ستتحول الإضافات الصغيرة إلى فوضى. OOP يساعدك على تقسيم المشروع إلى وحدات واضحة. كل class له مسؤولية محددة. وهذا يجعل الفريق كله يفهم المشروع بشكل أفضل، ويجعل الإصلاحات أقل تكلفة.
وإذا كنت تعمل مع Laravel أو Symfony أو أي إطار حديث، فستجد أن فهم الكائنات والأصناف ليس اختيارًا ثانويًا، بل هو قلب الفهم الحقيقي للإطار. الخدمات، النماذج، المتحكمات، الأحداث، المستودعات، والعديد من المفاهيم الأخرى كلها مبنية على هذا العالم. لذلك كل دقيقة تقضيها في فهم classes وobjects ستوفر عليك ساعات لاحقًا.
نصائح عملية عند كتابة Classes في PHP
عندما تكتب صنفًا، حاول أن تجعله مسؤولًا عن شيء واحد واضح. لا تضع في الصنف الواحد منطقًا متعلقًا بحفظ البيانات، وإرسال البريد، وحساب الضرائب، وطباعة التقارير، والتعامل مع الملفات. هذا يربك الكود. الأفضل أن يكون لكل class مسؤولية محددة. كذلك، لا تجعل الخصائص public من دون سبب قوي. كلما زادت السيطرة على البيانات عبر methods، زادت جودة التصميم. واستخدم أسماء واضحة. الاسم الجيد للصنف أو للطريقة يوفر نصف الفهم تقريبًا. عندما ترى calculateTotal() تفهم فورًا وظيفتها، بينما doSomething() لا تقول شيئًا مفيدًا.
حاول أيضًا أن تعتمد على القيم المهيأة في constructor عندما يكون ذلك منطقيًا. لا تترك الكائن في حالة غير مكتملة. وعندما تستخدم inheritance، اسأل نفسك: هل هذه علاقة “is-a” فعلًا؟ أي: هل Dog هو Animal؟ نعم. هل Cart هو Order؟ غالبًا لا. هذه الأسئلة البسيطة تمنع تصميمًا سيئًا من البداية. وأخيرًا، لا تخلط بين ما هو سلوك عام وما هو سلوك خاص بالكائن. لو كانت العملية لا تحتاج حالة داخلية، فstatic قد تكون مناسبة، لكن إن كانت العملية مرتبطة بحالة object، فالأفضل أن تبقى داخل الكائن.
أخطاء شائعة يقع فيها المبتدئون
من أكثر الأخطاء شيوعًا استخدام public لكل شيء، ثم اكتشاف أن أي جزء من التطبيق يستطيع تغيير البيانات في أي وقت. هذا يخلق مشاكل صعبة التتبع. خطأ آخر هو الخلط بين $this وself واسم الصنف. self تُستخدم للوصول إلى أعضاء الصنف نفسه بشكل ثابت، بينما $this للكائن الحالي. كذلك، بعض المبتدئين يضعون كل شيء داخل صنف واحد كبير جدًا. هذا يجعل الصنف ثقيلًا ومربكًا، ويصعّب إعادة الاستخدام. وهناك أيضًا من يبالغ في استخدام الوراثة بدل التفكير في composition أو traits، فينتهي الأمر بهرم معقد من الأصناف يصعب فهمه.
خطأ آخر مهم هو عدم احترام مسؤولية الصنف. عندما يبدأ الصنف يفعل كل شيء، يفقد معناه. الصنف الجيد يشبه الأداة الجيدة: واضح، محدد، ويمكن الاعتماد عليه. لا تحاول أن تجعل class واحدًا يعمل كقاعدة بيانات ومحقق صحة ورسّام شاشة ومدير بريد في الوقت نفسه. هذا طريق سريع إلى التعب.
كيف تفكر أثناء تصميم Class جديد؟
قبل أن تكتب أول سطر، اسأل نفسك: ما الشيء الحقيقي الذي أمثله؟ ما البيانات التي تخصه؟ ما العمليات التي يجب أن يعرفها؟ ما الأمور التي يجب أن تُمنع من الخارج؟ ما القيم التي يجب أن تكون موجودة عند إنشاء الكائن؟ هذه الأسئلة القصيرة تمنحك تصميمًا أفضل بكثير. مثلًا، إذا كنت تصمم Order، فكر في خصائص مثل رقم الطلب، العناصر، الإجمالي، الحالة، تاريخ الإنشاء. ثم فكر في طرق مثل إضافة عنصر، حساب الإجمالي، تغيير الحالة، إلغاء الطلب. هكذا يصبح الصنف مفهومًا ومنطقيًا. وإذا وجدت نفسك تكتب منطقًا لا يخص هذا الكيان مباشرة، فقد تكون بحاجة إلى class آخر.
مثال أكثر اكتمالًا: طلب شراء
<?php
class Order
{
private $items = [];
private $status = 'pending';
public function addItem($name, $price, $quantity = 1)
{
$this->items[] = [
'name' => $name,
'price' => $price,
'quantity' => $quantity
];
}
public function getTotal()
{
$total = 0;
foreach ($this->items as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total;
}
public function getStatus()
{
return $this->status;
}
public function confirm()
{
if (empty($this->items)) {
throw new Exception("Cannot confirm an empty order");
}
$this->status = 'confirmed';
}
public function cancel()
{
if ($this->status === 'confirmed') {
throw new Exception("Confirmed orders cannot be canceled");
}
$this->status = 'canceled';
}
}
$order = new Order();
$order->addItem("Book", 20, 2);
$order->addItem("Pen", 5, 3);
echo $order->getTotal();
$order->confirm();
echo $order->getStatus();
هذا المثال يوضح كيف أن الصنف لا يحفظ البيانات فقط، بل يحمي قواعد العمل business rules. هذه نقطة مركزية جدًا. الكائن ليس مجرد وعاء بيانات، بل هو كيان يفرض بعض المنطق المناسب له.
الربط بين Objects وClasses والحياة العملية
من الجميل أنك حين تفهم هذه الفكرة جيدًا، تبدأ ترى مشاريعك بشكل مختلف. لم تعد تنظر إلى البيانات كحقول مبعثرة، بل ككيانات لها معنى. في متجر إلكتروني، لديك User وProduct وCart وOrder وPayment. في مدونة، لديك Post وCategory وComment وAuthor. في نظام مدارس، لديك Student وTeacher وClassroom وAttendance. وفي كل حالة، يمكن للصنف أن يحمل ما يخصه فقط. هذا التقسيم الطبيعي يجعل التفكير البرمجي أسهل بكثير، ويخفف الضغط الذهني عند التوسع.
والأهم أن هذا الأسلوب يجعل التعاون مع الآخرين أفضل. عندما يرى المطور الآخر صنفًا يحمل اسمًا واضحًا ومسؤولية محددة، يفهمه بسرعة. وعندما يقرأ methods صغيرة ومباشرة، لا يحتاج إلى مطاردة الكود في عشرين ملفًا لفهم ما يحدث. وهذا الفرق الحقيقي بين كود “يعمل” وكود “يمكن العيش معه”.
خلاصة فكرية مهمة
الفكرة الكبرى في PHP Objects and Classes ليست مجرد تعلم كلمات جديدة مثل class وobject وpublic وprivate. الفكرة الحقيقية هي تغيير طريقة تفكيرك في البرنامج نفسه. بدل أن تراه ككتلة من الأوامر، تبدأ تراه كمجموعة من الكيانات التي تتعاون فيما بينها. كل كيان له دور، وله بيانات، وله سلوك، وله حدود. وعندما تلتزم بهذه الرؤية، يصبح الكود أنظف، وأقوى، وأسهل في التوسع، وأسهل في الفهم. نعم، قد يبدو الأمر في البداية أطول من كتابة دوال بسيطة متفرقة، لكن هذا “الطول” الأولي هو استثمار. أنت تدفع قليلًا الآن كي لا تدفع كثيرًا لاحقًا عندما يكبر المشروع وتبدأ الفوضى.
إذا كنت مبتدئًا، لا تحاول أن تحفظ كل شيء مرة واحدة. ابدأ بالصنف والكائن، ثم خصائص وطرق، ثم constructor و$this، ثم visibility، ثم inheritance، ثم interfaces وtraits. خذها خطوة خطوة، وجرّب كل مفهوم بيدك. في البرمجة، الفهم الحقيقي لا يأتي من القراءة وحدها، بل من الكتابة والتجربة والخطأ الصغير الذي تتعلم منه أكثر من عشر صفحات شرح. ومع الوقت ستلاحظ أن OOP لم تعد موضوعًا مخيفًا، بل أصبحت لغة طبيعية تفكر بها عندما تبدأ مشروعًا جديدًا.
وفي النهاية، تذكّر أن أفضل كود هو الكود الذي يشرح نفسه بهدوء. عندما تكتب class واضحًا، وobject مسؤولًا، وmethods صغيرة ومفهومة، فأنت لا تسهّل الحياة على الحاسوب فقط، بل تسهّلها على نفسك وعلى من يأتي بعدك. وهذا، بصراحة، أحد أجمل الأشياء في تعلم PHP Objects and Classes: أنك لا تتعلم تقنية فقط، بل تتعلم كيف تنظم الفكرة نفسها.