سؤال

أنا أكتب مكتبة في PHP 5.3 ، والتي يعد الجزء الأكبر منها فئة ذات العديد من الخصائص الثابتة التي يمتد منها الفئات الفرعية للسماح صفريًا بفصول الأطفال.

على أي حال ، إليك عينة لتوضيح الخصوصية التي وجدتها:

<?php

class A {
    protected static $a;
    public static function out() { var_dump(static::$a); }
    public static function setup($v) { static::$a =& $v; }
}
class B extends A {}
class C extends A {}

A::setup('A');
A::out(); // 'A'
B::out(); // null
C::out(); // null

B::setup('B');
A::out(); // 'A'
B::out(); // 'B'
C::out(); // null

C::setup('C');
A::out(); // 'A'
B::out(); // 'B'
C::out(); // 'C'

?>

الآن ، هذا السلوك المطلوب إلى حد كبير للميراث الثابت بقدر ما أشعر بالقلق ، ومع ذلك ، يتغير static::$a =& $v; إلى static::$a = $v; (لا توجد مرجعية) تحصل على السلوك الذي توقعته ، أي:

'A'
'A'
'A'

'B'
'B'
'B'

'C'
'C'
'C'

هل يستطيع أحد شرح لماذا هذا؟ لا أستطيع أن أفهم كيف تؤثر المراجع على الميراث الثابت بأي شكل من الأشكال:/

تحديث:

مرتكز على إجابة Artefacto, ، وجود الطريقة التالية في الفئة الأساسية (في هذه الحالة ، أ) والاتصال بها بعد إعلانات الفصل ينتج عن السلوك المسمى "المطلوب" أعلاه دون الحاجة إلى التخصيص بالرجوع إليه في المستقلين ، مع ترك النتائج عند استخدام الذات: : كما السلوك "المتوقع" أعلاه.

/*...*/
public static function break_static_references() {
    $self = new ReflectionClass(get_called_class());
    foreach($self->getStaticProperties() as $var => $val)
        static::$$var =& $val;
}
/*...*/
A::break_static_references();
B::break_static_references();
C::break_static_references();
/*...*/
هل كانت مفيدة؟

المحلول

TL ؛ إصدار DR

خاصية ثابتة $a هو رمز مختلف في كل فئة من الفئات ، لكنه في الواقع نفس المتغير بمعنى أنه في $a = 1; $b = &$a;, $a و $b هي نفس المتغير (أي أنها على نفس المجموعة المرجعية). عند إجراء مهمة بسيطة ($b = $v;) ، سوف تتغير قيمة كلا الرموسين ؛ عند إجراء مهمة بالرجوع إليها ($b = &$v;)، فقط $b سيتأثر.

نسخة أصلية

أول شيء ، دعونا نفهم كيف يتم ترجيح الخصائص الثابتة. zend_do_inheritance يكرر الخصائص الثابتة الفائقة inherit_static_prop:

zend_hash_apply_with_arguments(&parent_ce->default_static_members TSRMLS_CC,
    (apply_func_args_t)inherit_static_prop, 1, &ce->default_static_members);

تعريفه هو:

static int inherit_static_prop(zval **p TSRMLS_DC, int num_args,
    va_list args, const zend_hash_key *key)
{
    HashTable *target = va_arg(args, HashTable*);

    if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h)) {
        SEPARATE_ZVAL_TO_MAKE_IS_REF(p);
        if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p,
                sizeof(zval*), NULL) == SUCCESS) {
            Z_ADDREF_PP(p);
        }
    }
    return ZEND_HASH_APPLY_KEEP;
}

دعونا نترجم هذا. يستخدم PHP نسخة على الكتابة ، مما يعني أنه سيحاول مشاركة نفس تمثيل الذاكرة الفعلي (ZVAL) للقيم إذا كان لديهم نفس المحتوى. inherit_static_prop يتم استدعاؤه لكل واحد من الخصائص الثابتة الفائقة بحيث يمكن نسخها إلى الفئة الفرعية. تنفيذ inherit_static_prop يضمن أن تكون الخصائص الثابتة للفئة الفرعية هي مراجع PHP ، سواء تم مشاركة Zval من الوالد (على وجه الخصوص ، إذا كانت الطبقة الفائقة مرجعًا ، فسيشارك الطفل Zval ، إذا لم يكن كذلك ، فإن Zval سوف يتم نسخها وسيتم مرجع Zval الجديد ؛ الحالة الثانية لا تهمنا حقًا هنا).

لذلك ، عندما يتم تشكيل A ، B و C ، $a سيكون رمزًا مختلفًا لكل فئة من هذه الفئة (أي ، كل فصل له جدول تجزئة خصائصه وكل جدول تجزئة له إدخاله الخاص $a) ، لكن Zval الأساسي سيكون هو نفسه وسيكون مرجعًا.

لديك شيء مثل:

A::$a -> zval_1 (ref, reference count 3);
B::$a -> zval_1 (ref, reference count 3);
C::$a -> zval_1 (ref, reference count 3);

لذلك ، عندما تقوم بمهمة طبيعية

static::$a = $v;

نظرًا لأن المتغيرات الثلاثة تشترك في نفس Zval ومرجعها ، فإن المتغيرات الثلاثة ستفترض القيمة $v. سيكون هو نفسه إذا فعلت:

$a = 1;
$b = &$a;
$a = 2; //both $a and $b are now 1

من ناحية أخرى ، عندما تفعل

static::$a =& $v;

سوف تكسر مجموعة المرجع. لنفترض أنك تفعل ذلك في الفصل أ. لديك الآن:

//reference count is 2 and ref flag is set, but as soon as
//$v goes out of scope, reference count will be 1 and
//the reference flag will be cleared
A::$a -> zval_2 (ref, reference count 2);

B::$a -> zval_1 (ref, reference count 2);
C::$a -> zval_1 (ref, reference count 2);

سيكون المشابه

$a = 1;
$b = &$a;
$v = 3;
$b = &$v; //$a is 1, $b is 3

العمل

كما هو موضح في إجابة Gordon المحذوفة الآن ، يمكن أيضًا كسر المرجع بين خصائص الفئات الثلاثة عن طريق إعادة تجميع العقار في كل فئة من الفئات:

class B extends A { protected static $a; }
class C extends A { protected static $a; }

هذا لأنه لن يتم نسخ العقار إلى الفئة الفرعية من الطبقة الفائقة إذا كان يتم إعادة تشكيله (انظر الشرط if (!zend_hash_quick_exists(target, key->arKey, key->nKeyLength, key->h)) في inherit_static_prop).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top