[Traduction][Développement d'extensions php et embarquées] Chapitre 11 -Objets php5

黄舟
Libérer: 2023-03-05 16:26:01
original
1485 Les gens l'ont consulté

Assez juste, mais les fonctions API utilisées par les objets php5 sont toujours construites selon l'API de php4. Si vous avez lu le chapitre 10 "Objets php4", vous serez un peu familier avec le contenu de ce chapitre avant de commencer. chapitre, vous pouvez aimer le chapitre 10 Comme au début, renommer l'extension en sample3 et nettoyer le code redondant, ne laissant que le code squelette de l'extension >
Il y a deux composants clés dans les variables d'objet php5. est un identifiant numérique, qui est très similaire à l'ID de ressource numérique introduit dans le chapitre 9 « Type de données de ressource », et joue un rôle utilisé dans Le rôle de la clé pour trouver les instances d'objet dans la table correspondante Les éléments de cette table d'instance. contient des références à zend_class_entry et à la table d'attributs interne

Le deuxième élément est la variable objet Une table de poignées qui vous permet de personnaliser la façon dont le moteur Zend gère les instances. plus loin dans ce chapitre.

zend_class_entry

Une entrée de classe est une représentation interne d'une classe que vous définissez dans l'utilisateur Comme vous l'avez vu dans le chapitre précédent, cette structure est initialisée en appelant INIT_CLASS_ENTRY() avec le nom de la classe et sa table de fonctions. Ensuite, utilisez zend_register_internal_class() pour vous inscrire dans la phase MINIT, vous pensez peut-être « Ça a l'air. à peu près la même chose jusqu'à présent ?", jusqu'à présent, vous avez raison. Commençons maintenant à définir quelques méthodes d'objet. Vous commencerez à voir des méthodes différentes très définies et populaires.

La macro PHP_METHOD() a été introduite dans Zend Engine 2, qui est une encapsulation de la macro PHP_FUNCTION() et combine le nom de la classe et le nom de la méthode, contrairement à php4. Définissez manuellement le nom de la méthode dans l'extension. En utilisant cette macro, les spécifications de résolution d'espace de noms de votre code dans l'extension sont cohérentes avec celles des autres responsables

définition

Définir. l'implémentation d'une méthode, tout comme les autres fonctions, il suffit de la connecter à la table de fonctions de la classe. En plus de la macro PHP_METHOD() utilisée pour l'implémentation, il existe de nouvelles macros qui peuvent être utilisées dans la définition des listes de fonctions.

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;
}
Copier après la connexion

Comparé au PHP_FE () introduite dans le chapitre 5 "Votre première extension", PHP_ME() ajoute un paramètre classname et un paramètre flags à la fin (pour fournit un contrôle d'accès tel que public, protégé, privé, statique, ainsi que des options abstraites et autres) . Par exemple, pour définir la méthode helloWorld, vous pouvez la définir comme suit :

PHP_METHOD(Sample3_SecondClass, helloWorld)
{
    php_printf("Hello World\n");
}
Copier après la connexion

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

est très similaire à la macro PHP_FALIAS(), cette macro permet de décrire la méthode du paramètre alias L'implémentation (au sein de la même classe) fournit un nouveau nom spécifié par name. Par exemple, pour copier votre méthode helloWorld, vous pouvez la définir comme suit

  • <🎜. >

    PHP_ABSTRACT_ME(nom de classe, nom de méthode, 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)
Copier après la connexion

现在为止, 你看到的方法定义都使用了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<🎜><🎜><🎜><🎜><🎜>只能在类中调用<🎜><🎜><🎜><🎜><🎜><🎜>


Par exemple, puisque la méthode Sample3_SecondClass::helloWorld() que vous avez définie précédemment ne nécessite pas d'instance d'objet, vous pouvez modifier sa définition de simple Remplacez ZEND_ACC_PUBLIC par ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, afin que le moteur ne fournisse pas (instance) après l'avoir connu.

Méthode magique

En plus des méthodes magiques de ZE1, ZE2 a ajouté de nombreuses nouvelles méthodes magiques, comme indiqué dans le tableau suivant (ou peut être trouvé sur //m.sbmmt.com/)



还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如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 }
};
Copier après la connexion

要注意__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)
Copier après la connexion

这个函数会分配一块新的内存, 构造一个"\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);
}
Copier après la connexion

通过_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&#39;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;
}
Copier après la connexion

在这之下, 这些类常量就可以访问了, 分别是: 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 }
};
Copier après la connexion

由于这些方法是抽象的, 所以不需要实现. 接下来的第二个差异就是注册. 和一个实际的类注册类似, 首先调用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;
Copier après la connexion

实现接口

假设你想让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");
}
Copier après la connexion

接着在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)
Copier après la connexion

最后, 定义你新注册的类实现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;
}
Copier après la connexion

如果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);
Copier après la connexion

句柄

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() est une variante de read_property(), ce qui signifie qu'elle permet à la portée appelante de remplacer directement le zval * actuel par un nouveau. Le comportement par défaut est de renvoyer le. table de propriétés standard Adresse du pointeur de l'attribut dans .S'il n'existe pas et qu'il n'y a pas de méthode magique __get()/__set(), le pointeur est implicitement créé et renvoyé si la méthode __get() ou __set() existe. , cela entraînera l'échec de ce handle, obligeant le moteur à s'appuyer sur des appels read_property et write_property distincts

  • zval *read_dimension(zval *obj, zval *. idx, int tapez TSRMLS_DC)

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

read_dimension () et write_dimension() sont similaires aux read_property() et write_property() correspondants mais ils sont déclenchés lorsque l'objet est accédé sous forme de tableau à l'aide de la méthode $obj['idx'] ; . Si la classe de l'objet n'implémente pas l'interface ArrayAccess, le comportement par défaut est de déclencher une erreur ; sinon elle appelle les méthodes magiques offsetget($idx) ou offsetset($idx, $value).

  • zval *get( zval *obj TSRMLS_DC)

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

Lors de la définition ou de la récupération de la valeur d'un objet, la méthode get() ou set() sera appelée sur l'objet. L'objet lui-même est passé comme. le premier paramètre. Pour set, la nouvelle valeur est passée comme deuxième paramètre ; En fait, ces méthodes sont utilisées dans les opérations arithmétiques.

  • int has_property(zval *obj, zval *prop, int chk_type TSRMLS_DC)

Ce gestionnaire est appelé lorsque isset() est appelé sur un propriété de l'objet. Par défaut, le gestionnaire standard vérifie le nom de l'attribut spécifié par la prop. Si cet attribut n'est pas trouvé dans PHP 5.1.0 et que la méthode __isset() est définie, cette méthode sera appelée si la valeur du paramètre chk_type est 2. , alors seul l'attribut doit exister. Si la valeur de chk_type est 0, alors doit exister et ne doit pas être une valeur IS_NULL. Si la valeur de chk_type est 1, l'attribut doit exister et doit être une valeur autre que FALSE. : En PHP 5.0.x, La signification de chk_type est cohérente avec le chk_type de has_dimension.

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

Lors de l'appel de isset() lors du traitement d'un objet comme d'un tableau (tel que isset($obj['idx'])), ce processeur est utilisé par défaut. vérifier si l'objet est implémenté. Si l'interface ArrayAccess est implémentée, la méthode offsetexists($idx) est appelée. Si elle n'est pas trouvée (en référence à l'appel de offsetexists()), c'est la même chose que si la méthode offsetexists() n'était pas implémentée. et 0 est renvoyé. Sinon, si chk_type est 0, renvoie directement true(1). Un chk_type de 1 indique qu'il doit appeler la méthode offsetget($idx) de l'objet et tester la valeur de retour, Renvoie TRUE(1) uniquement si la valeur n'est pas FALSE.

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

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

Ces deux méthodes lorsque vous essayez de décharger les propriétés d'un objet (ou lors de l'application d'unset() à un objet dans un tableau). Le gestionnaire unset_property() supprime la propriété de la table des propriétés standard (si elle existe) ou tente d'appeler la méthode __unset($prop) implémentée (php 5.1). . 0), unset_dimension() appelle la méthode offsetunset($idx) lorsque la classe implémente ArrayAccess.

  • HashTable *get_properties(zval *obj TSRMLS_DC)

Ce gestionnaire est en fait appelé lorsque la fonction interne utilise la macro Z_OBJPROP() pour lire une propriété de la table de propriétés standard. Gestion par défaut des objets PHP. Le processeur décompresse et. renvoie Z_OBJ_P(object)->properties, qui est une véritable table de propriétés standard.

  • union _zend_function *get_method(zval **obj_ptr char *method_name, int nom_méthode_len TSRMLS_DC)

Ce gestionnaire est appelé lors de l'analyse d'une méthode objet dans la table_fonction de la classe. Si la méthode n'existe pas dans la table_fonction principale, le gestionnaire par défaut renvoie un pointeur vers l'objet _call($. name, $args) méthode enveloppée zend_function * pointer.

  • int call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)

La fonction définie comme type ZEND_OVERLOADED_FUNCTION sera exécutée en tant que processeur call_method. Par défaut, ce processeur n'est pas défini.

  • union _zend_function *get_constructor(zval). *obj TSRMLS_DC)

Similaire au processeur get_method(), ce processeur renvoie une méthode d'objet correspondante. Référence Le constructeur dans le zend_class_entry de la classe est stocké dans. d'une manière spéciale, ce qui la rend spéciale. Le remplacement de cette méthode est très rare.

  • zend_class_entry * get_class_entry(zval *obj TSRMLS_DC)

Semblable à get_constructor(), ce processeur est rarement surchargé. Son but est de mapper une instance d'objet et de revenir à sa définition de classe d'origine.

    <🎜. >
  • int get_class_name(zval *object, char **name zend_uint *len, int parent TSRMLS_DC)

get_class_entry() est une étape de get_class_name(). Après avoir obtenu le zend_object de l'objet, il utilisera le nom de classe de l'objet ou son nom de classe parent (cela dépend de la valeur du paramètre parent ), fera une copie et retournera la copie renvoyée du nom de classe. utiliser un stockage non persistant (emalloc()).

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

Lorsque des opérateurs de comparaison (tels que : ==, !=, <=, <, >, >=) sont utilisés Lorsqu'il y a deux objets, appeler compare_objects() sur le Les opérandes (les deux objets participant à la comparaison) constituent la première partie de ce travail. Sa valeur de retour est généralement 1, 0, -1, ce qui représente respectivement supérieur à, égal à et inférieur à. Par défaut, les objets sont comparés en fonction. sur leurs tables attributaires standard, en utilisant les mêmes règles de comparaison que les règles de comparaison de tableaux apprises au chapitre 8, "Travailler avec des tableaux et des tables de hachage".

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

Lorsque vous essayez de convertir l'objet en un autre type de données, ce processeur sera déclenché. Si Should_free est défini sur une valeur non nulle, zval_dtor() sera appelé à l'heure d'été, libérant d'abord les ressources internes. En bref, le processeur devrait essayer de représenter l'objet dans src comme dst à Out du type zval *. Ce gestionnaire n'est pas défini par défaut, mais lorsqu'il est présent, il doit renvoyer SUCCESS ou FAILURE.

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

Les objets qui implémentent l'accès au tableau doivent définir ce gestionnaire, qui définira le nombre actuel d'éléments dans le nombre et retournera SUCCÈS si l'instance actuelle n'implémente pas le tableau. accès, il doit renvoyer FAILURE pour que le moteur revienne en arrière et vérifie la table d'attributs standard.

Annotation : la table de poignées ci-dessus et le php-5.4.9 utilisé par le traducteur n'est plus complètement cohérent. Lors de l'étude de cette partie, les lecteurs peuvent se référer au tableau des poignées de processeur standard en bas de Zend/zend_object_handlers.c.

Méthode Magique Partie 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[&#39;foo&#39;]的行为等价于$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;
}
Copier après la connexion

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

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;
}
Copier après la connexion

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

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;
}
Copier après la connexion

这样就可以在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);
Copier après la connexion

这两种方法唯一可预见的不同是它们发生的时机不同. 引擎在碰到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中文网(m.sbmmt.com)!


Étiquettes associées:
php
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal

Méthode

Utilisation

__construct(...)

Constructeur d'objet appelé automatiquement facultatif ( a préalablement défini une méthode cohérente avec le nom de la classe if). Les deux implémentations de __construct() et classname() existent , dans les versions instanciées Au cours du processus, , donnera la priorité à l'appel de __construct()

__destruct()

Lorsque l'instance quitte la portée, ou demande la résiliation complète , entraîneront tous l'appel implicite de la méthode __destruct() de l'instance pour gérer certains travaux de nettoyage, Par exemple, fermer un fichier ou un descripteur réseau .

__clone()

Par défaut ,Toutes les instances sont de véritables passages par référence.Dans php5, Pour copier réellement une instance d'objet,, vous devez utiliser le clonemot-clé.Lors de l'appel du mot-clé clone sur une instance d'objet, La méthode __clone() sera exécutée implicitement, ce qui permet à l'objet de copier certaines données de ressources internes requises.

__toString()

Lors de l'utilisation de texte pour représenter un objet ,Par exemple, lors de l'utilisation de l'instruction echo ou print directement sur l'objet, La méthode __toString() sera automatiquement appelée par le moteur Si la classe implémente cette méthode magique , <🎜. > devrait renvoyer Une chaîne contenant une chaîne décrivant l'état actuel de l'objet .

__get($var )

Si le script demande une propriété invisible d'un objet ( n'existe pas ou est invisible en raison du contrôle d'accès )quand, __get()la méthode magique sera appelée,Le seul paramètre est le nom de la propriété demandé.implémentation Vous peut utiliser sa propre logique interne pour déterminer la valeur de retour la plus raisonnable à renvoyer .

__set($var , $value)

et __get() sont très similaires à , __set() fournit la capacité opposée , qui est utilisée pour gérer la logique d'attribution de valeurs aux propriétés invisibles des objets . Les implémentations de __set() peuvent choisir de créer implicitement ces variables dans des tables attributaires standard , et de définir des valeurs à l'aide d'autres mécanismes de stockage , Ou lancez simplement une erreur et supprimez la valeur .

__call($ fname, $args)

Lorsque vous appelez des méthodes non définies d'un objet, vous pouvez obtenir de superbes résultats en utilisant __call() méthode magique Le traitement de .Cette méthode accepte deux paramètres  :Le nom de la méthode appelée ,Un tableau contenant les indices numériques de tous les arguments passés dans l'appel.

__isset($nomvar)

php5.1.0Après , l'appel d'isset($obj->prop) n'est pas seulement un chèque< S'il y a prop dans 🎜>$obj, , appellera également $obj__isset()méthode, évaluation dynamique essayez d'utiliser dynamique Que ce soit les méthodes __get() et __set() peuvent lire et écrire avec succès les attributs

__unset($varname)

Similaire à __isset(), php 5.1 .0 introduit une simple OOPinterface pour la fonction unset() , Il peut être utilisé pour les propriétés de l'objet,Bien que cette propriété puisse ne pas exister dans la table des propriétés standard de l'objet, Mais cela pourrait avoir du sens pour l'espace de propriété dynamique de __get() et __set() , Par conséquent, __unset() est introduit pour résoudre ce problème.