new self مقابل new static في PHP: أيهما تختار في عام 2025؟
تخيل: أنت تكتب بلغة PHP لعدة سنوات، وتشعر بالثقة، وفجأة يسألك زميلك (أو Stack Overflow): "لماذا يتم إنشاء كائن من الفئة الأم في الفئة الفرعية، على الرغم من أنني أستدعي المُنشئ عبر new self()؟". هل هذا مألوف؟ إذا كان الأمر كذلك، فأنت لست وحدك. موضوع new self vs new static هو أحد أكثر الأسئلة شيوعًا على Stack Overflow مع آلاف المشاهدات. وفي عام 2025، مع إصدار إصدارات جديدة من PHP، أصبح فهم هذا الفرق أكثر أهمية.
في هذه المقالة، سنفهم الفرق بين self و static عند إنشاء الكائنات، وكيف يعمل الربط الثابت المتأخر (Late Static Binding)، والأهم من ذلك، متى نستخدم كلًا منهما. هيا بنا!
المشكلة: self يشير دائمًا إلى الفئة الأم
لنبدأ بمثال كلاسيكي. لنفترض أن لدينا فئة أصل BaseController وفئة فرعية ApiController.
<?php
class BaseController { public static function create() { return new self(); // انتبه! self هو BaseController }}
class ApiController extends BaseController { // يرث create()}
$api = ApiController::create();var_dump($api); // object(BaseController)#1 (0) { }?>كنا نتوقع الحصول على كائن ApiController، لكننا حصلنا على BaseController. لماذا؟ لأن self في سياق الفئة الأم يشير دائمًا إلى الفئة التي كُتب فيها، وليس إلى الفئة التي تم الاستدعاء من خلالها. هذا هو الربط الثابت المبكر. PHP "تتذكر" الفئة أثناء التجميع، وليس أثناء التنفيذ.
الحل: استخدام static
لإصلاح الموقف، نحتاج إلى استبدال self بـ static. هذا يُفعّل آلية الربط الثابت المتأخر (LSP).
<?php
class BaseController { public static function create() { return new static(); // static هي الفئة التي استدعت الطريقة }}
class ApiController extends BaseController { // يرث create()}
$api = ApiController::create();var_dump($api); // object(ApiController)#1 (0) { }?>الآن $api هو كائن من ApiController. يتم حل static أثناء التنفيذ ويشير إلى الفئة التي تم من خلالها استدعاء الطريقة. هذا هو الربط الثابت المتأخر.
متى تستخدم self ومتى تستخدم static؟
في عام 2025، تفرق أفضل الممارسات بوضوح بين هذين النهجين. إليك القواعد الأساسية.
استخدم self عندما:
- تعمل داخل فئة واحدة ولا تتوقع وراثة لهذه الطريقة.
- تحتاج إلى إنشاء كائن من نفس الفئة التي كُتب فيها الكود، وليس من الفئة الفرعية.
- تصل إلى الثوابت أو الخصائص الثابتة التي لا ينبغي تجاوزها.
مثال: طريقة مصنع تضمن إرجاع كائن من الفئة الأساسية.
<?php
class Logger { protected static $instance;
public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); // دائمًا Logger } return self::$instance; }}?>استخدم static عندما:
- تصمم كودًا قابلاً للتوسيع - مثل الفئات المجردة أو traits.
- تحتاج إلى أن تتمكن الفئات الفرعية من إنشاء مثيلاتها الخاصة.
- تستخدم نمط Factory Method أو Builder.
مثال: فئة مجردة للكيانات.
<?php
abstract class Entity { public static function create(array $data): static {\