Heim >Backend-Entwicklung >PHP-Tutorial >[Übersetzung] [Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 11 – PHP5-Objekte

[Übersetzung] [Entwicklung und Einbettung von PHP-Erweiterungen] Kapitel 11 – PHP5-Objekte

黄舟
黄舟Original
2017-02-10 10:12:061519Durchsuche

Okay, aber die von PHP5-Objekten verwendeten API-Funktionen basieren immer noch auf der API von PHP4. Wenn Sie Kapitel 10 „php4-Objekte“ gelesen haben, sind Sie mit dem Inhalt dieses Kapitels einigermaßen vertraut Kapitel, Sie können wie in Kapitel 10 die Erweiterung in „sample3“ umbenennen und den redundanten Code bereinigen, sodass nur der Skelettcode der Erweiterung übrig bleibt. Die erste ist eine numerische Kennung, die der in Kapitel 9 „Ressourcendatentyp“ eingeführten numerischen Ressourcen-ID sehr ähnlich ist und eine Rolle spielt Die Rolle des Schlüssels zum Suchen von Objektinstanzen in der entsprechenden Tabelle Die Elemente in dieser Instanztabelle enthalten Verweise auf zend_class_entry und die interne Attributtabelle.

Das zweite Element ist die Objektvariable. Eine Handle-Tabelle, mit der Sie anpassen können, wie die Zend Engine Instanzen behandelt später in diesem Kapitel.

zend_class_entry

Ein Klasseneintrag ist eine interne Darstellung einer Klasse, die Sie im Benutzer definieren Wie Sie im vorherigen Kapitel gesehen haben, wird diese Struktur durch den Aufruf von INIT_CLASS_ENTRY() mit dem Klassennamen und seiner Funktionstabelle initialisiert. Als nächstes denken Sie vielleicht: „Es sieht aus.“ Bisher ziemlich gleich? Das PHP_METHOD()-Makro wurde in Zend Engine 2 eingeführt, das eine Kapselung des PHP_FUNCTION()-Makros ist und im Gegensatz zu PHP4 den Klassennamen und den Methodennamen kombiniert. Definieren Sie den Methodennamen manuell in der Erweiterung. Die Namespace-Auflösungsspezifikationen Ihres Codes in der Erweiterung stimmen mit denen anderer Betreuer

Definition

Define überein Um eine Methode zu implementieren, verbinden Sie sie einfach mit der Funktionstabelle der Klasse. Zusätzlich zum PHP_METHOD()-Makro, das für die Implementierung verwendet wird, gibt es einige neue Makros, die bei der Definition von Funktionslisten verwendet werden können.

PHP_ME(classname, methodname, arg_info, flags)

zend_class_entry *php_sample3_sc_entry;
#define PHP_SAMPLE3_SC_NAME "Sample3_SecondClass"
static function_entry php_sample3_sc_functions[] = {
    { NULL, NULL, NULL }
};

PHP_MINIT_FUNCTION(sample3)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    return SUCCESS;
}
Verglichen mit PHP_FE ()-Makro, das in Kapitel 5 „Ihre erste Erweiterung“ eingeführt wurde. PHP_ME() fügt am Ende einen Klassennamen-Parameter und einen Flags-Parameter hinzu (für bietet Zugriffskontrolle wie öffentliche, geschützte, private, statische sowie abstrakte und andere Optionen). . Um beispielsweise die helloWorld-Methode zu definieren, können Sie sie wie folgt definieren:

PHP_MALIAS(classname, name, alias, arg_info, flags)

PHP_METHOD(Sample3_SecondClass, helloWorld)
{
    php_printf("Hello World\n");
}

ist dem Makro PHP_FALIAS() sehr ähnlich. Mit diesem Makro können Sie die Methode für den Alias-Parameter beschreiben. Die Implementierung (innerhalb derselben Klasse) bietet a Um beispielsweise Ihre helloWorld-Methode zu kopieren, können Sie sie wie folgt definieren:

PHP_ABSTRACT_ME(classname, methodname, arg_info)

  • 内部类中的抽象方法很像用户空间的抽象方法. 在父类中它只是一个占位符, 期望它的子类提供真正的实现. 你将在接口一节中使用这个宏, 接口是一种特殊的class_entry.

    • PHP_ME_MAPPING(methodname, functionname, arg_info)

    最后一种方法定义的宏是针对同时暴露OOP和非OOP接口的扩展(比如mysqli既有过程化的mysqli_query(), 也有面向对象的MySQLite::query(), 它们都使用了相同的实现.)的. 假定你已经有了一个过程化函数, 比如第5章写的sample_hello_world(), 你就可以使用这个宏以下面的方式将它附加为一个类的方法(要注意, 映射的方法总是public, 非static, 非final的):

    PHP_ME_MAPPING(hello, sample_hello_world, NULL)

    现在为止, 你看到的方法定义都使用了ZEND_ACC_PUBLIC作为它的flags参数. 实际上, 这个值可以是下面两张表的任意值的位域运算组合, 并且它还可以和本章后面"特殊方法"一节中要介绍的一个特殊方法标记使用位域运算组合.


    类型标记

    含义

    ZEND_ACC_STATIC

    方法可以静态调用.实际上,这就表示,方法如果通过实例调用, $this或者更确切的说this_ptr,并不会被设置到实例作用域中

    ZEND_ACC_ABSTRACT

    方法并不是真正的实现.当前方法应该在被直接调用之前被子类覆写.

    ZEND_ACC_FINAL

    方法不能被子类覆写



    可见性标记

    含义

    ZEND_ACC_PUBLIC

    可以在对象外任何作用域调用.这和php4方法的可见性是一样的

    ZEND_ACC_PROTECTED

    只能在类中或者它的子类中调用

    ZEND_ACC_PRIVATE

    只能在类中调用

    可见性标记

    含义

    ZEND_ACC_PUBLIC 可以在对象外任何作用域调用.这和php4方法的可见性是一样的 ZEND_ACC_PROTECTED 只能在类中或者它的子类中调用 ZEND_ACC_PRIVATE 只能在类中调用


    Da beispielsweise die zuvor definierte Methode Sample3_SecondClass::helloWorld() keine Objektinstanz erfordert, können Sie ihre Definition von „einfach“ ändern Ändern Sie ZEND_ACC_PUBLIC in ZEND_ACC_PUBLIC |, damit die Engine keine (Instanz) bereitstellt, nachdem sie es erkannt hat.

    Magische Methode

    Zusätzlich zu den magischen Methoden von ZE1 hat ZE2 viele neue magische Methoden hinzugefügt, wie in der folgenden Tabelle gezeigt (oder unter http://www.php.cn/ zu finden)



    还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如ArrayAccess接口以及一些SPL接口.

    在一个内部对象的实现中, 每个这样的"魔术方法"都可以和其他方法一样实现, 只要在对象的方法列表中正确的定义PHP_ME()以及PUBLIC访问修饰符即可.对于 __get(), __set(), __call(), __isset()以及__unset(), 它们要求传递参数, 你必须定义恰当的arg_info结构来指出方法需要一个或两个参数. 下面的代码片段展示了这些木梳函数的arg_info和它们对应的PHP_ME()条目:

    static
        ZEND_BEGIN_ARG_INFO_EX(php_sample3_one_arg, 0, 0, 1)
        ZEND_END_ARG_INFO()
    static
        ZEND_BEGIN_ARG_INFO_EX(php_sample3_two_args, 0, 0, 2)
        ZEND_END_ARG_INFO()
    static function_entry php_sample3_sc_functions[] = {
        PHP_ME(Sample3_SecondClass, __construct, NULL,
                            ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(Sample3_SecondClass, __destruct, NULL,
                            ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
        PHP_ME(Sample3_SecondClass, __clone, NULL,
                            ZEND_ACC_PUBLIC|ZEND_ACC_CLONE)
        PHP_ME(Sample3_SecondClass, __toString, NULL,
                            ZEND_ACC_PUBLIC)
        PHP_ME(Sample3_SecondClass, __get, php_sample3_one_arg,
                            ZEND_ACC_PUBLIC)
        PHP_ME(Sample3_SecondClass, __set, php_sample3_two_args,
                            ZEND_ACC_PUBLIC)
        PHP_ME(Sample3_SecondClass, __call, php_sample3_two_args,
                            ZEND_ACC_PUBLIC)
        PHP_ME(Sample3_SecondClass, __isset, php_sample3_one_arg,
                            ZEND_ACC_PUBLIC)
        PHP_ME(Sample3_SecondClass, __unset, php_sample3_one_arg,
                            ZEND_ACC_PUBLIC)
        { NULL, NULL, NULL }
    };

    要注意__construct, __destruct, __clone使用位域运算符增加了额外的常量. 这三个访问修饰符对于方法而言是特殊的, 它们不能被用于其他地方.

    属性

    php5中对象属性的访问控制与方法的可见性有所不同. 在标准属性表中定义一个公开属性时, 就像你通常期望的, 你可以使用zend_hash_add()或add_property_*()族函数.

    对于受保护的和私有的属性, 则需要使用新的ZEND_API函数:

    void zend_mangle_property_name(char **dest, int *dest_length,
                            char *class, int class_length,
                            char *prop, int prop_length,
                            int persistent)

    这个函数会分配一块新的内存, 构造一个"\0classname\0propname"格式的字符串. 如果类名是特定的类名, 比如Sample3_SecondClass, 则属性的可见性为private, 只能在Sample3_SecondClass对象实例内部可见.

    如果类名指定为*, 则属性的可见性是protected, 它可以被对象实例所属类的所有祖先和后辈访问. 实际上, 属性可以以下面方式增加到对象上:

    void php_sample3_addprops(zval *objvar)
    {
        char *propname;
        int propname_len;
        /* public */
        add_property_long(objvar, "Chapter", 11);
        /* protected */
        zend_mangle_property_name(&propname, &propname_len,
            "*", 1, "Title", sizeof("Title")-1, 0);
        add_property_string_ex(objvar, propname, propname_len,
            "PHP5 Objects", 1 TSRMLS_CC);
        efree(propname);
        /* Private */
        zend_mangle_property_name(&propname, &propname_len,
            "Sample3_SecondClass",sizeof("Sample3_SecondClass")-1,
            "Section", sizeof("Section")-1, 0);
        add_property_string_ex(objvar, propname, propname_len,
            "Properties", 1 TSRMLS_CC);
        efree(propname);
    }

    通过_ex()版的add_property_*()族函数, 可以明确标记属性名的长度. 这是需要的, 因为在protected和private属性名中会包含NULL字节, 而strlen()认为NULL字节是字符串终止标记, 这样将导致属性名被认为是空. 要注意的是_ex()版本的add_property_*()函数还要求显式的传递TSRMLS_CC. 而通常它是通过宏扩展隐式的传递的.

    定义类常量和定义类属性非常相似. 两者的关键不同点在于它们的持久性, 因为属性的生命周期是伴随的实例的, 它发生在请求中, 而常量是和类定义在一起的, 只能在MINIT阶段定义.

    由于标准的zval *维护宏的函数假定了非持久性, 所以你需要手动写不少代码. 考虑下面的函数:

    void php_sample3_register_constants(zend_class_entry *ce)
    {
        zval *constval;
    
    
        /* 基本的标量值可以使用Z_*()去设置它们的值 */
        constval = pemalloc(sizeof(zval), 1);
        INIT_PZVAL(constval);
        ZVAL_DOUBLE(constval, 2.7182818284);
        zend_hash_add(&ce->constants_table, "E", sizeof("E"),
                        (void*)&constval, sizeof(zval*), NULL);
    
        /* 字符串需要额外的空间分配 */
        constval = pemalloc(sizeof(zval), 1);
        INIT_PZVAL(constval);
        Z_TYPE_P(constval) = IS_STRING;
        Z_STRLEN_P(constval) = sizeof("Hello World") - 1;
        Z_STRVAL_P(constval) = pemalloc(Z_STRLEN_P(constval)+1, 1);
        memcpy(Z_STRVAL_P(constval), "Hello World",
                                Z_STRLEN_P(constval) + 1);
        zend_hash_add(&ce->constants_table,
                        "GREETING", sizeof("GREETING"),
                        (void*)&constval, sizeof(zval*), NULL);
    
    
        /* Objects, Arrays, and Resources can't be constants */
    }
    PHP_MINIT_FUNCTION(sample3)
    {
        zend_class_entry ce;
        INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                                php_sample3_sc_functions);
        php_sample3_sc_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_register_constants(php_sample3_sc_entry);
        return SUCCESS;
    }

    在这之下, 这些类常量就可以访问了, 分别是: Sample3_SecondClass::E和Sample3_SecondClass::GREETING.

    接口

    接口的定义和类的定义除了几个差异外基本一致. 首先是所有的方法都定义为抽象的, 这可以通过PHP_ABSTRACT_ME()宏来完成.

    static function_entry php_sample3_iface_methods[] = {
        PHP_ABSTRACT_ME(Sample3_Interface, workerOne, NULL)
        PHP_ABSTRACT_ME(Sample3_Interface, workerTwo, NULL)
        PHP_ABSTRACT_ME(Sample3_Interface, workerThree, NULL)
        { NULL, NULL, NULL }
    };

    由于这些方法是抽象的, 所以不需要实现. 接下来的第二个差异就是注册. 和一个实际的类注册类似, 首先调用INIT_CLASS_ENTRY和zend_register_internal_class.

    当类(zend_class_entry)可用时, 最后一部就是标记这个类是接口, 实现方法如下:

    zend_class_entry *php_sample3_iface_entry;
    PHP_MINIT_FUNCTION(sample3)
    {
        zend_class_entry ce;
        INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                            php_sample3_iface_methods);
        php_sample3_iface_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_iface_entry->ce_flags|= ZEND_ACC_INTERFACE;

    实现接口

    假设你想让Sample3_SecondClass这个类实现Sample3_Interface这个接口, 就需要实现这个接口定义的所有抽象方法:

    PHP_METHOD(Sample3_SecondClass,workerOne)
    {
        php_printf("Working Hard.\n");
    }
    PHP_METHOD(Sample3_SecondClass,workerTwo)
    {
        php_printf("Hardly Working.\n");
    }
    PHP_METHOD(Sample3_SecondClass,workerThree)
    {
        php_printf("Going wee-wee-wee all the way home.\n");
    }

    接着在php_sample3_sc_functions列表中定义它们:

    PHP_ME(Sample3_SecondClass,workerOne,NULL,ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass,workerTwo,NULL,ZEND_ACC_PUBLIC)
    PHP_ME(Sample3_SecondClass,workerThree,NULL,ZEND_ACC_PUBLIC)

    最后, 定义你新注册的类实现php_sample3_iface_entry接口:

    PHP_MINIT_FUNCTION(sample3)
    {
        zend_class_entry ce;
        /* 注册接口 */
        INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                            php_sample3_iface_methods);
    
        php_sample3_iface_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_iface_entry->ce_flags|= ZEND_ACC_INTERFACE;
        /* 注册实现接口的类 */
        INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                                php_sample3_sc_functions);
        php_sample3_sc_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_register_constants(php_sample3_sc_entry);
        /* 声明实现关系 */
        zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                    1, php_sample3_iface_entry);
        return SUCCESS;
    }

    如果Sample3_SecondClass实现了其他接口, 比如ArrayAccess, 就需要将对应的类(zend_class_entry)作为附加参数增加到zend_class_implements()调用中, 并将现在传递为数字1的参数值相应的增大为2:

    zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                2, php_sample3_iface_entry, php_other_interface_entry);

    句柄

    ZE2并没有把所有的对象实例看做是相同的, 它为每个对象实例关联了句柄表. 当在一个对象上执行特定的操作时, 引擎调用执行对象的句柄表中自定义的行为.

    标准句柄

    默认情况下, 每个对象都被赋予了std_object_handlers这个内建句柄表. std_object_handlers中对应的句柄方法以及它们的行为定义如下:

    • void add_ref(zval *object TSRMLS_DC)

    当对象值的refcount增加时被调用, 比如, 当一个对象变量赋值给新的变量时. add_ref和del_ref函数的默认行为都是调整内部对象存储的refcount.

    • void del_ref(zval *object TSRMLS_DC)

    和add_ref类似, 这个方法也在修改refcount时调用, 通常是在unset()对象变量时发生的.

    • zend_object_value clone_obj(zval *object TSRMLS_DC)

    用于利用已有的对象实例创建一个新的实例. 默认行为是创建一个新的对象实例, 将它和原来的句柄表关联, 拷贝属性表, 如果该对象的类定义了__clone()方法, 则调用它让新的对象执行一些附加的复制工作.

    • zval *read_property(zval *obj, zval *prop, int type TSRMLS_DC)

    • void write_property(zval *obj, zval *prop, zval *value TSRMLS_DC)

    在用户空间尝试以$obj->prop方式访问, 去读写对象的属性时, read_property/write_property对应的被调用. 默认的处理是首先在标准属性表中查找属性. 如果属性没有定义, 则检查是否存在__get()或__set()魔术方法, 如果有则调用该方法.

    • zval **get_property_ptr_ptr(zval *obj, zval *value TSRMLS_DC)

    get_property_ptr_ptr() ist eine Variante von read_property(), die es dem aufrufenden Bereich ermöglicht, den aktuellen zval * direkt durch einen neuen zu ersetzen. Das Standardverhalten besteht darin, die Standard-Eigenschaftstabelle zurückzugeben Die Zeigeradresse des Attributs in . Wenn es nicht vorhanden ist und keine magische Methode __get()/__set() vorhanden ist, wird der Zeiger implizit erstellt und zurückgegeben. Wenn die Methode __get() oder __set() vorhanden ist, wird dies der Fall sein Dies führt dazu, dass dieses Handle fehlschlägt, sodass die Engine stattdessen auf separate Aufrufe von read_property und write_property angewiesen ist Typ TSRMLS_DC)

    • void write_dimension(zval *obj, zval *idx, zval *value TSRMLS_DC)

    • read_dimension() und write_dimension() ähneln den entsprechenden read_property() und write_property(); sie werden jedoch ausgelöst, wenn auf das Objekt als Array mit der Methode $obj['idx'] zugegriffen wird Die Klasse des Objekts implementiert die ArrayAccess-Schnittstelle nicht. Das Standardverhalten besteht darin, einen Fehler auszulösen. Andernfalls werden die magischen Methoden offsetget($idx) oder offsetset($idx, $value) aufgerufen.

    zval *get( zval *obj TSRMLS_DC)

    • void set(zval *obj, zval *value TSRMLS_DC)

    • Beim Festlegen oder Abrufen des Werts eines Objekts wird die Methode get() oder set() für das Objekt aufgerufen. Das Objekt selbst wird als erster Parameter übergeben . Für set wird der neue Wert als zweiter Parameter übergeben. Es gibt keinen Standardhandler für diese Operationen 🎜>int has_property(zval *obj, zval *prop, int chk_type TSRMLS_DC)

    Dieser Handler wird aufgerufen, wenn isset() für eine Objekteigenschaft aufgerufen wird. Standardmäßig überprüft der Standardhandler den durch die Requisite angegebenen Attributnamen. Wenn dieses Attribut in PHP 5.1.0 nicht gefunden wird und die Methode __isset() definiert ist, wird diese Methode nur aufgerufen Attribut muss vorhanden sein. Wenn der chk_type-Wert 0 ist, muss er vorhanden sein und darf kein Wert von IS_NULL sein. Wenn der chk_type-Wert 1 ist, muss das Attribut vorhanden sein und einen anderen Wert als FALSE haben. X, Die Bedeutung von chk_type stimmt mit dem chk_type von has_dimension überein.

    • int has_dimension(zval *obj, zval *idx, int chk_type TSRMLS_DC)

    Beim Aufruf von isset() bei der Behandlung eines Objekts als Array (z. B. isset($obj['idx'])) wird dieser Prozessor verwendet Überprüfen Sie, ob das Objekt implementiert ist. Wenn die ArrayAccess-Schnittstelle implementiert ist, wird die Methode offsetexists($idx) aufgerufen. Wenn sie nicht gefunden wird (bezogen auf den Aufruf von offsetexists()), ist dies dasselbe, als ob die Methode offsetexists() nicht implementiert wäre. und 0 wird zurückgegeben, wenn chk_type 0 ist, direkt true(1) zurückgeben. Ein chk_type von 1 gibt an, dass die Methode offsetget($idx) des Objekts aufgerufen und der Rückgabewert getestet werden muss. Geben Sie TRUE(1) nur zurück, wenn der Prüfwert nicht FALSE ist.

    • void unset_property(zval *obj, zval *prop TSRMLS_DC)

    void unset_dimension(zval *obj, zval *idx TSRMLS_DC)

    • Diese beiden Methoden beim Entladen Objekteigenschaften (oder beim Anwenden von unset() auf ein Objekt in einem Array). Der Handler unset_property() entfernt entweder die Eigenschaft aus der Standardeigenschaftentabelle (falls vorhanden) oder versucht, die implementierte Methode __unset($prop) aufzurufen ( php 5.1.0), unset_dimension() ruft die Methode offsetunset($idx) auf, wenn die Klasse ArrayAccess implementiert.

    • HashTable *get_properties(zval *obj TSRMLS_DC)

    Dieser Handler wird tatsächlich aufgerufen, wenn die interne Funktion das Z_OBJPROP()-Makro verwendet, um eine Eigenschaft aus der Standard-Eigenschaftstabelle zu lesen. Standardmäßige Verarbeitung von PHP-Objekten Der Prozessor entpackt und gibt Z_OBJ_P(object)->properties zurück, was eine echte Standard-Eigenschaftentabelle ist.

    • union _zend_function *get_method(zval **obj_ptr char *method_name , int methodname_len TSRMLS_DC)

    Dieser Handler wird aufgerufen, wenn eine Objektmethode in der Funktionstabelle der Klasse analysiert wird. Wenn die Methode nicht in der Hauptfunktionstabelle vorhanden ist, gibt der Standardhandler einen Zeiger auf das Objekt _call($) zurück name, $args) Methode umschlossen zend_function * Zeiger.

    • int call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)

    Funktion, die als Typ ZEND_OVERLOADED_FUNCTION definiert ist, wird als call_method-Prozessor ausgeführt. Standardmäßig ist dieser Prozessor undefiniert.

    • union _zend_function *get_constructor(zval *obj TSRMLS_DC)

    Ähnlich wie der get_method()-Prozessor gibt dieser Prozessor eine entsprechende Objektmethodenreferenz zurück. Der Konstruktor ist im zend_class_entry der Klasse gespeichert eine besondere Art und Weise, die sie zu etwas Besonderem macht.

    • zend_class_entry * get_class_entry(zval *obj TSRMLS_DC)

    Ähnlich wie get_constructor() wird dieser Prozessor selten überschrieben. Sein Zweck besteht darin, eine Objektinstanz zuzuordnen. Zurück zur ursprünglichen Klassendefinition.

    • int get_class_name(zval *object, char **name zend_uint *len, int parent TSRMLS_DC)

    get_class_entry() ist ein Schritt von get_class_name(). Nach dem Abrufen des zend_object des Objekts wird der Klassenname des Objekts oder sein übergeordneter Klassenname (dies hängt vom Wert des Parameters parent ab) erstellt und die zurückgegebene Kopie des Klassennamens zurückgegeben Verwenden Sie nicht persistenten Speicher (emalloc()).

    • int Compare_objects(zval *obj1, zval * obj2 TSRMLS_DC)

    Wenn Vergleichsoperatoren (z. B. ==, !=, 6ccbb8b0f4c668de47d964671fff91b7, >=) verwendet werden. Wenn zwei Objekte vorhanden sind, wird der Aufruf von Compare_objects() auf dem Operanden (die beiden am Vergleich beteiligten Objekte) sind der erste Teil dieser Arbeit. Ihr Rückgabewert ist normalerweise 1, 0, -1, was jeweils „größer als“, „gleich“ und „kleiner als“ darstellt auf ihren Standardattributtabellen, wobei dieselben Vergleichsregeln wie die Array-Vergleichsregeln verwendet werden, die in Kapitel 8, „Arbeiten mit Arrays und HashTables“, gelernt wurden.

    • int cast_object( zval *src, zval *dst, int type, int Should_free TSRMLS_DC)

    Beim Versuch, das Objekt in einen anderen Datentyp umzuwandeln, wird dieser Prozessor ausgelöst. Wenn Should_free auf einen Wert ungleich Null gesetzt ist, wird zval_dtor() auf dst aufgerufen und gibt zunächst die internen Ressourcen frei. Kurz gesagt, der Prozessor sollte versuchen, das Objekt in src als dst vom Typ zval * darzustellen. Dieser Handler ist standardmäßig nicht definiert, aber wenn er vorhanden ist, sollte er ERFOLGREICH oder FEHLER zurückgeben.

    • int count_elements (zval *obj, long *count TSRMLS_DC)

    Objekte, die den Array-Zugriff implementieren, sollten diesen Handler definieren, der die aktuelle Anzahl der Elemente in „count“ setzt und ERFOLGREICH zurückgibt, wenn die aktuelle Instanz kein Array implementiert Zugriff, es sollte FAILURE zurückgeben, damit die Engine zurückgeht und die Standardattributtabelle überprüft.

    Anmerkung: Die obige Handle-Tabelle und das von PHP-5.4.9 verwendete Der Übersetzer ist nicht mehr vollständig konsistent. Beim Studium dieses Teils können sich die Leser auf die Standard-Prozessor-Handle-Tabelle am Ende von Zend/zend_object_handlers.c beziehen.

    Magische Methode Teil 2

    使用前面看到的对象句柄表的自定义版本, 可以让内部类提供与在用户空间基于对象或类的__xxx()魔术方法相比, 相同或更多的能力.将这些自定义的句柄设置到对象实例上首先要求创建一个新的句柄表. 因为你通常不会覆写所有的句柄, 因此首先将标准句柄表拷贝到你的自定义句柄表中再去覆写你想要修改的句柄就很有意义了:

    static zend_object_handlers php_sample3_obj_handlers;
    int php_sample3_has_dimension(zval *obj, zval *idx,
                            int chk_type TSRMLS_DC)
    {
        /* 仅在php版本>=1.0时使用 */
        if (chk_type == 0) {
           /* 重新映射chk_type的值 */
           chk_type = 2;
        }
        /* 当chk_type值为1时保持不变. 接着使用标准的hash_property方法执行逻辑 */
        return php_sample3_obj_handlers.has_property(obj,
                                idx, chk_type TSRMLS_CC);
    }
    PHP_MINIT_FUNCTION(sample3)
    {
        zend_class_entry ce;
        zend_object_handlers *h = &php_sample3_obj_handlers;
    
    
        /* 注册接口 */
        INIT_CLASS_ENTRY(ce, "Sample3_Interface",
                            php_sample3_iface_methods);
        php_sample3_iface_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_iface_entry->ce_flags = ZEND_ACC_INTERFACE;
        /* 注册SecondClass类 */
        INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                                php_sample3_sc_functions);
        php_sample3_sc_entry =
                    zend_register_internal_class(&ce TSRMLS_CC);
        php_sample3_register_constants(php_sample3_sc_entry);
    
    
        /* 实现AbstractClass接口 */
        zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                    1, php_sample3_iface_entry);
    
    
        /* 创建自定义句柄表 */
        php_sample3_obj_handlers = *zend_get_std_object_handlers();
    
    
        /* 这个句柄表的目的是让$obj['foo']的行为等价于$obj->foo */
        h->read_dimension = h->read_property;
        h->write_dimension = h->write_property;
        h->unset_dimension = h->unset_property;
    #if PHP_MAJOR_VERSION > 5 || \
                (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0)
        /* php-5.1.0中, has_property和has_dimension的chk_type含义不同, 为使它们行为一致, 自己包装一个函数 */
        h->has_dimension = php_sample3_has_dimension;
    
    #else
        /* php 5.0.x的has_property和has_dimension行为一致 */
        h->has_dimension = h->has_property;
    #endif
    
    
        return SUCCESS;
    }

    要将这个句柄表应用到对象上, 你有两种选择. 最简单也是最具代表性的就是实现一个构造器方法, 并在其中重新赋值变量的句柄表.

    PHP_METHOD(Sample3_SecondClass,__construct)
    {
        zval *objptr = getThis();
    
    
        if (!objptr) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING,
                        "Constructor called statically!");
            RETURN_FALSE;
        }
        /* 执行正常的构造器任务... */
        /* 覆写句柄表 */
        Z_OBJ_HT_P(objptr) = &php_sample3_obj_handlers;
    }

    当构造器返回时, 对象就有了新的句柄表以及对应的自定义行为. 还有一种更加受欢迎的方法是覆写类的对象创建函数.

    zend_object_value php_sample3_sc_create(zend_class_entry *ce
                                            TSRMLS_DC)
    {
        zend_object *object;
        zend_object_value retval;
    
    
        /* 返回Zend创建的对象 */
        retval = zend_objects_new(&object, ce TSRMLS_CC);
        /* 覆写create_object时, 属性表必须手动初始化 */
        ALLOC_HASHTABLE(object->properties);
        zend_hash_init(object->properties, 0, NULL,
                                        ZVAL_PTR_DTOR, 0);
        /* 覆写默认句柄表 */
        retval.handlers = &php_sample3_obj_handlers;
        /* 这里可能会执行其他对象初始化工作 */
        return retval;
    }

    这样就可以在MINIT阶段注册类(zend_class_entry)之后直接将自定义句柄表附加上去.

    INIT_CLASS_ENTRY(ce, PHP_SAMPLE3_SC_NAME,
                            php_sample3_sc_functions);
    php_sample3_sc_entry =
                zend_register_internal_class(&ce TSRMLS_CC);
    php_sample3_sc_entry->create_object= php_sample3_sc_create;
    php_sample3_register_constants(php_sample3_sc_entry);
    zend_class_implements(php_sample3_sc_entry TSRMLS_CC,
                1, php_sample3_iface_entry);

    这两种方法唯一可预见的不同是它们发生的时机不同. 引擎在碰到new Sample3_SecondClass后会在处理构造器及它的参数之前调用create_object. 通常, 你计划覆盖的各个点使用的方法(create_object Vs. __construct)应该一致.

    译注: php-5.4.9中, xxx_property/xxx_dimension这一组句柄的原型是不一致的, 因此, 按照原著中的示例, 直接将xxx_property/xxx_dimension进行映射已经不能工作, 要完成上面的功能, 需要对4个句柄均包装一个函数去映射. 由于译者没有详细跟踪具体在哪一个版本发生了这些改变, 因此这里不给出译者测试的示例(没有做兼容性处理检查), 如果读者碰到这个问题, 请检查自己所使用php版本中两组句柄原型的差异并进行相应修正.

    小结

    毋庸置疑, php5/ZE2的对象模型比它的前辈php4/ZE1中的对象模型更加复杂. 在看完本章中介绍的所有特性和实现细节后, 你可能已经被它的所包含的信息量搞得手足无措. 幸运的是, php中在OOP之上有一层可以让你选择你的任务所需的部分而不关心其他部分. 找到复杂性之上一个舒适的层级开始工作, 剩下的都会顺起来的.

    现在已经看完了所有的php内部数据类型, 是时候回到之前的主题了: 请求生命周期. 接下来的两章, 将在你的扩展中使用线程安全全局变量增加内部状态, 定义自定义的ini设置, 定义常量, 以及向使用你扩展的用户空间脚本提供超级全局变量.

    以上就是[翻译][php扩展开发和嵌入式]第11章-php5对象的内容,更多相关内容请关注PHP中文网(www.php.cn)!


    Methode

    Verwendung

    __construct(...)

    Optionaler automatisch aufgerufener Objektkonstruktor ( zuvor eine Methode definiert, die mit dem Klassennamen übereinstimmt).if Beide Implementierungen von __construct() und classname() existieren , in instanziierter Form Während des Prozesses wird , dem Aufruf von __construct()

    __destruct()

    Wenn die Instanz den Bereich verlässt, oder eine vollständige Beendigung anfordert , führt alle dazu, dass die Methode __destruct() der Instanz implizit aufgerufen wird, um einige Aufräumarbeiten durchzuführen,Zum Beispiel das Schließen einer Datei oder eines Netzwerk-Handles .

    __clone()

    Standardmäßig ,Alle Instanzen sind echte Pass-by-Reference.In php5, Um eine Objektinstanz tatsächlich zu kopieren,, müssen Sie das KlonenSchlüsselwort verwenden.Wenn Sie das Schlüsselwort Klonen für eine Objektinstanz aufrufen, Die Methode __clone() wird implizit ausgeführt, wodurch das Objekt einige erforderliche interne Ressourcendaten kopieren kann.

    __toString()

    Bei Verwendung von Text zur Darstellung eines Objekts ,Zum Beispiel, wenn die Anweisung echo oder print direkt auf dem Objekt verwendet wird, Die Methode __toString() wird automatisch von der Engine aufgerufen. Wenn die Klasse diese magische Methode implementiert, sollte eine Zeichenfolge mit einer Beschreibung des aktuellen Status des Objekts zurückgeben .

    __get($var)

    Wenn das Skript eine unsichtbare Eigenschaft eines Objekts anfordert ( nicht existiert oder aufgrund der Zugriffskontrolle unsichtbar ist )wenn, __get()magische Methode wird aufgerufen,Der einzige Parameter ist der angeforderte Eigenschaftsname.Implementierung Sie kann seine eigene interne Logik verwenden, um den sinnvollsten Rückgabewert für zu ermitteln.

    __set($var , $value)

    und __get() sind sehr ähnlich, __set() bietet die gegenteilige Fähigkeit , , die verwendet wird, um die Logik der Zuweisung zu unsichtbaren Eigenschaften eines Objekts zu handhaben __set() kann sich dafür entscheiden, diese Variablen implizit in Standardattributtabellen , zu erstellen und Werte mithilfe anderer Speichermechanismen festzulegen , Oder geben Sie einfach einen Fehler aus und verwerfen Sie den Wert .

    __call($fname, $args)

    Beim Aufrufen undefinierter Methoden eines Objekts können Sie mit __call()Magische Methode Die Verarbeitung von .Diese Methode akzeptiert zwei Parameter :Der aufgerufene Methodenname,Ein Array, das die numerischen Indizes aller im Aufruf übergebenen Argumente enthält.

    __isset ($varname )

    php5.1.0Nach ist der Aufruf von isset($obj->prop) nicht nur eine Überprüfungprop in 🎜>$obj gibt, , wird auch aufgerufen $obj__isset()Methode, dynamische Auswertung versuchen, dynamisches Ob zu verwenden Mit den Methoden __get() und __set() können Attribute

    __unset($varname)

    Ähnlich wie

    __isset(), PHP 5.1 .0 führt eine einfache OOPSchnittstelle für die Funktion unset() ein. Es kann für Objekteigenschaften verwendet werden,Obwohl diese Eigenschaft möglicherweise nicht in der Standardeigenschaftentabelle des Objekts vorhanden ist, Aber es könnte für den dynamischen Eigenschaftsbereich von __get() und __set() sinnvoll sein, Daher wird __unset() eingeführt, um dieses Problem zu lösen.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn