الروابط الساكنة المتأخرة في كائنات PHP
تضمن الإصدار 5.3.0 من PHP خاصية تدعى بالروابط الساكنة المتأخرة (late static bindings) والتي يمكن استخدامها للإشارة إلى الصنف المستدعى في سياق وراثة ساكنة.
وبتعبير أدق تعمل الروابط الساكنة المتأخر عن طريق تخزين الصنف المسمّى في نهاية "الاستدعاء غير الموجِّه non-forwarding call". في حالة الاستدعاء الساكن للتوابع يكون هذا التابع هو التابع المصرّح عنه (يأتي عادة على يسار العامل ::) أما في حالة الاستدعاء غير الساكن للتوابع فيكون الصنف هو صنف الكائن. "الاستدعاء غير الموجِّه" هو استدعاء ساكن يقدَّم بواسطة self::
و parent::
و static::
أو بواسطة الدالة forward_static_call()
إذا ما توجّهنا صعودًا في التسلسل الهرمي للصنف. يمكن استخدام الدالة get_called_class()
للحصول على سلسلة نصية تتضمن اسم الصنف المستدعى ويمكن الحصول على نطاقه بواسطة الكلمة المفتاحية static::
.
سميت الميزة بهذا الاسم نظرًا لبعض الاعتبارات المرتبطة باللغة، فالربط المتأخر في الحقيقة ناتج من أنّ الكلمة المفتاحية static::
لن تعالج بواسطة الصنف عند تعريف التابع ولكنّها ستُحسب بواسطة المعلومات المتوفرة في وقت التشغيل. تدعى هذه الميزة أيضًا "بالرط الساكن static binding" وذلك لأنّها تستخدم في (ولكن لا تقتصر على ذلك فقط) الاستدعاء الساكن للتوابع.
أوجه قصور self::
تعالج المراجع الساكنة إلى الصنف الحالي مثل self::
أو __CLASS__
باستخدام الصنف الذي تنتمي إليه الدالة، والمكان الذي عُرّفت فيه:
المثال 1: استخدام self::
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
يعطي المثال السابق المخرجات التالية:
A
استخدام الروابط الساكنة المتأخرة
إنّ الهدف من الروابط الساكنة المتأخرة هو محاولة علاج ذلك القصور بتقديم كلمة مفتاحية تشير إلى الصنف الذي استدعي أولًا في وقت التشغيل، وتتيح هذه الكلمة المفتاحية بصورة بسيطة الإشارة للصنف B
من الدالة test()
في المثال السابق. لم تقدّم اللغة كلمة مفتاحية جديدة واستُخدمت الكلمة المحجوزة static
.
المثال 2: استخدام بسيط لـ static::
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // Here comes Late Static Bindings
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
يعطي المثال السابق المخرجات التالية
B
ملاحظة: يكون الصنف المستدعى في السياقات غير الساكنة هو صنف نسخة الكائن، ولمّا كانت الصيغة $this->
تستدعي التوابع من نوع private من نفس النطاق، فإنّ استخدام static::
قد يعطي نتائج مختلفة، إضافة إلى أنّ ::static
يمكن أن تشير إلى الخصائص الساكنة فقط.
المثال 3: استخدام ::static
في سياق غير ساكن
لاحظ أن التابع foo()
سيُنسخ إلى الصنف B
وهكذا يبقى نطاق التابع ضمن الصنف A
، ويكون الاستدعاء صحيحًا، أما في الصنف C
سيُستبدل التابع الأصلي ويكون النطاق الجديد ضمن الصنف C
.
<?php
class A {
private function foo() {
echo "success!\n";
}
public function test() {
$this->foo();
static::foo();
}
}
class B extends A {
/* سينسخ التابع إلى هذا الصنف ويكون الاستدعاء صحيحًا */
}
class C extends A {
private function foo() {
/* سيستبدل النطاق هنا بنطاق الصنف الجديد */
}
}
$b = new B();
$b->test();
$c = new C();
$c->test();
// سيفشل الاستدعاء
?>
يعطي المثال السابق المخرجات التالية:
success!
success!
success!
Fatal error: Call to private method C::foo() from context 'A' in /tmp/test.php on line 9
المثال 4: الاستدعاءات الموجِّهة وغير الموجِّهة
<?php
class A {
public static function foo() {
static::who();
}
public static function who() {
echo __CLASS__."\n";
}
}
class B extends A {
public static function test() {
A::foo();
parent::foo();
self::foo();
}
public static function who() {
echo __CLASS__."\n";
}
}
class C extends B {
public static function who() {
echo __CLASS__."\n";
}
}
C::test();
?>
يعطي المثال السابق المخرجات التالية:
A
C
C