【PHP內核】運算符:instanceof的內核實現

(文中涉及所有代碼均爲:php-7.0.4版本)

PHP中有個類型運算符instanceof 用於確定一個 PHP 變量是否屬於某一類 class,引用官方文檔中的例子:

<?php
class MyClass
{
}

class NotMyClass
{
}
$a = new MyClass;

var_dump($a instanceof MyClass);
var_dump($a instanceof NotMyClass);
?>
==============================
bool(true)
bool(false)

instanceof也可用來確定一個變量是不是繼承自某一父類的子類,或是確定一個變量是不是實現了某個接口的對象。關於詳細的instanceof可以查看:http://php.net/manual/zh/language.operators.type.php

這裏要分析的是instanceof的內核實現過程。

我們還是從一個簡單的示例入手:

<?php
class MyClass
{
}

$a = new MyClass;
$b = new MyClass;

var_dump($a instanceof MyClass);
var_dump($a instanceof $b);
================================
bool(true)
bool(true)

編譯完成後的opcodes見下圖:

這裏寫圖片描述

這個例子opcode比較多,忽略其它opcode,這裏這討論ZEND_INSTANCEOF,對於“$a instanceof MyClass”,根據op1(16)、op2(1)計算:

//zend_vm_execute.h #49720
zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1_type] * 5 + zend_vm_decode[op->op2_type]];

138*25 + 4*5 + 0 = 3470

得到此opcode的處理handler爲:ZEND_INSTANCEOF_SPEC_CV_CONST_HANDLER

同樣可以算出ainstanceof b對應的handler爲:ZEND_INSTANCEOF_SPEC_CV_VAR_HANDLER

下面分別來看下這兩個函數是如何判斷的。

1、ZEND_INSTANCEOF_SPEC_CV_CONST_HANDLER

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INSTANCEOF_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE

    zval *expr;
    zend_bool result;

    SAVE_OPLINE();
    expr = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);

    if (Z_TYPE_P(expr) == IS_OBJECT) {
        zend_class_entry *ce;
        ...
        ce = zend_fetch_class_by_name(Z_STR_P(EX_CONSTANT(opline->op2)), EX_CONSTANT(opline->op2) + 1, ZEND_FETCH_CLASS_NO_AUTOLOAD);
        ...
        result = ce && instanceof_function(Z_OBJCE_P(expr), ce);
    }
}

zend_fetch_class_by_name這個函數是從EG(class_table)中查找對應的class結構,它返回一個zend_class_entry指針,用戶定義的類在內核中就是對應一個 zend_class_entry,而object結構中會保存它所屬的class,所以可以猜測instanceof的實現:要判斷兩個變量是否是同一個類實例化的對象只需要判斷一下這兩個對象指向的class是否爲同一個即可。

下面是class與object的結構:

//zend.h #131
struct _zend_class_entry {
    char type;
    zend_string *name;
    struct _zend_class_entry *parent;
    int refcount;
    uint32_t ce_flags;

    int default_properties_count;
    int default_static_members_count;
    zval *default_properties_table;
    zval *default_static_members_table;
    zval *static_members_table;
    HashTable function_table; //class method列表
    HashTable properties_info; //class屬性列表
    HashTable constants_table; //class靜態信息列表
    ...
    uint32_t num_interfaces; //此class實現的接口數量(class可以實現多個interface)
    zend_class_entry **interfaces; //實現的接口列表
    ...
}

//zend_type.h #275
struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle; //對象id
    zend_class_entry *ce; //所屬class指針
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];
};

具體的判斷邏輯在instanceof_function()函數中:

//zend_operators.c #2129
ZEND_API zend_bool ZEND_FASTCALL instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce)
{
    if (ce->ce_flags & ZEND_ACC_INTERFACE) {
        return instanceof_interface(instance_ce, ce);
    } else {
        return instanceof_class(instance_ce, ce);
    }
}

這裏將分情況判斷:

A.如果右值爲接口:instanceof interface

即判斷左值所屬class是否實現了右值的interface。

<?php
interface Type{}

class myClass implements Type{}

$obj = new myClass();

var_dump($obj instanceof Type);
?>
===============================
bool(true)

這種情況由instanceof_interface()處理:

//zend_operators.c #2098
static zend_bool ZEND_FASTCALL instanceof_interface(const zend_class_entry *instance_ce, const zend_class_entry *ce)
{
    uint32_t i;

    //遍歷左值class所有實現的接口,如果找到了與右值相同的則說明匹配成功,返回true
    for (i = 0; i < instance_ce->num_interfaces; i++) {
        if (instanceof_interface(instance_ce->interfaces[i], ce)) {
            return 1;
        }   
    }
    //實際還是通過下面這個方法判斷的,上面只是將左值實現的接口遍歷了一遍,逐個比較           
    return instanceof_class(instance_ce, ce);
}

這時候你可能會問:如果左值class繼承的父類實現了右值interface呢?這種情況是否爲true呢?就像下面這個例子:

<?php
interface Type{}

class myClassParent implements Type{}

class myClass extends myClassParent{}

$obj = new myClass();

var_dump($obj instanceof Type);
==============================
bool(true)

答案是肯定的,從上面的源碼可以看出實際還是由instanceof_class()函數判斷的,這種情況與右值不是interface的相同,下面一起討論。

B.如果右值爲普通類:instanceof 非interface

上面那種情況實際最終也是調的instanceof_class()進行判斷的:

//zend_operators.c #2086
static zend_always_inline zend_bool instanceof_class(const zend_class_entry *instance_ce, const zend_class_entry *ce)
{   
    while (instance_ce) {
        if (instance_ce == ce) {
            return 1;
        }
        //迭代父類進行比較
        instance_ce = instance_ce->parent;
    }
    return 0;
}

從這個方法可以很清楚的看到只要左值所屬class及其父類中有一個與右值class相同就表示instanceof爲true。

2、ZEND_INSTANCEOF_SPEC_CV_VAR_HANDLER

這種情況比較簡單,即A instanceof B,A、B都是object的情況,這種判斷的依據是比較A所屬class及所有父類與B所屬class是否相同,也就是說不考慮B的父類,只依據B所屬的class,例如:

<?php
interface Type{}

class myClassParent implements Type{}

class A extends myClassParent{}
class B extends myClassParent{}

$a = new A();
$b = new B();

var_dump($a instanceof $b);
===========================
bool(false)

雖然AB都繼承了myClassParent,但是判斷的時候是這個條件:(A == B || myClassParent == B)。

<?php
interface Type{}

class myClassParent implements Type{}

class A extends myClassParent{}
class B extends myClassParent{}

$a = new A();
$b = new myClassParent();

var_dump($a instanceof $b);
===========================
bool(true)

這個判斷的是:(A == myClassParent || myClassParent == myClassParent)。

3、總結

A instanceof B

可以按照這個規則判斷:找出A所屬的class、所有父類、所有實現的接口這三部分,其中只要有一個與B(或者B所屬class)相等,那麼(A instanceof B) == true.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章