ホームページ > バックエンド開発 > PHPチュートリアル > [翻訳][php 拡張機能と埋め込み] 第 11 章 - php5 オブジェクト

[翻訳][php 拡張機能と埋め込み] 第 11 章 - php5 オブジェクト

WBOY
リリース: 2016-06-13 12:49:46
オリジナル
1077 人が閲覧しました

[翻訳] [php 拡張機能と埋め込み] 第 11 章 - php5 オブジェクト

完全な翻訳コンテンツ PDF ドキュメントのダウンロード アドレス: http://download.csdn.net/detail/lgg201/5107012

この本は現在、laruence (http://www.laruence.com) と walu (http://www.walu.cc) によって github 上で翻訳されています。翻訳プロジェクトのアドレスは https://github.com です。 /walu/phpbook

Github 上のこの本のアドレス: https://github.com/goosman-lei/php-eae

将来的には、この本は独立したバージョンを保持したまま、部分的に phpbook プロジェクトに統合される可能性があります。


元のタイトル:

原作者:サラ・ゴーレモン

翻訳者: goosman.lei (Lei Guo)

翻訳者のメールアドレス: lgg860911@yahoo.com.cn

翻訳者ブログ: http://blog.csdn.net/lgg201

php5 オブジェクト

php5 オブジェクトをその前身である php4 オブジェクトと比較するのは不公平ですが、php5 オブジェクトで使用される API 関数は依然として php4 API に従って構築されています。第 10 章「php4 オブジェクト」を読んでいれば、理解できるでしょう。この章の内容をある程度理解している方は、この章を開始する前に、第 10 章の冒頭で行ったのと同じように、拡張機能の名前をsample3 に変更し、冗長なコードをクリーンアップして、拡張機能のスケルトン コードのみを残します。

進化の歴史

php5 オブジェクト変数には 2 つの重要なコンポーネントがあります。1 つ目は数値識別子で、第 9 章「リソース データ型」で紹介した数値リソース ID と非常によく似ており、対応する役割を果たします。このインスタンス テーブルの要素には、zend_class_entry と内部属性テーブル

への参照が含まれています。

2 番目の要素はオブジェクト変数のハンドル テーブルで、Zend エンジンがインスタンスを処理する方法をカスタマイズするために使用できます。このハンドル テーブルについては、この章で後ほど説明します。

zend_class_entry

クラスエントリは、ユーザー空間で定義したクラスの内部表現です。前の章で説明したように、この構造体はクラス名とその関数テーブルを指定して INIT_CLASS_ENTRY() を呼び出すことによって初期化されます。その後、zend_register_internal_class が使用されます。 MINIT フェーズ ()登録します。

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;
}
ログイン後にコピー

メソッド

前の章を読んだ方は、「ここまではほとんど同じに見えるのでは?」と思ったかもしれません。ここまでは、いくつかのオブジェクト メソッドの定義を始めましょう。明確で歓迎すべき違いです。

PHP_METHOD(Sample3_SecondClass, helloWorld)
{
    php_printf("Hello World\n");
}
ログイン後にコピー

在Zend引擎2中引入了PHP_METHOD()宏, 它是对PHP_FUNCTION()宏的封装, 将类名和方法名联合起来, 不用像php4中手动定义方法名了. 通过使用这个宏, 在扩展中你的代码和其他维护者的代码的名字空间解析规范就保持一致了.

定义

定义一个方法的实现, 和其他函数一样, 只不过是将它连接到类的函数表中. 除了用于实现的PHP_METHOD()宏, 还有一些新的宏可以用在函数列表的定义中.

  • PHP_ME(classname, methodname, arg_info, flags)

PHP_ME()相比于第5章"你的第一个扩展"中介绍的PHP_FE()宏, 增加了一个classname参数, 以及末尾的一个flags参数(用来提供public, protected, private, static等访问控制, 以及abstract和其他一些选项). 比如要定义helloWorld方法, 就可以如下定义:

PHP_ME(Sample3_SecondClass,helloWorld,NULL,ZEND_ACC_PUBLIC)
ログイン後にコピー
  • PHP_MALIAS(classname, name, alias, arg_info, flags)

和PHP_FALIAS()宏很像, 这个宏允许你给alias参数描述的方法(同一个类中的)实现提供一个name指定的新名字. 例如, 要复制你的helloWorld方法则可以如下定义

PHP_MALIAS(Sample3_SecondClass, sayHi, helloWorld,
                            NULL, ZEND_ACC_PUBLIC)
ログイン後にコピー
  • 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

メソッドはサブクラスによってオーバーライドできません

テーブル>


たとえば、前に定義した Sample3_SecondClass::helloWorld() メソッドはオブジェクト インスタンスを必要としないため、その定義を単純な ZEND_ACC_PUBLIC から ZEND_ACC_PUBLIC に変更して、エンジンが (インスタンスを提供しないようにすることができます)知っています) ).

魔法のメソッド

ZE1 のマジック メソッドに加えて、ZE2 では、次の表に示すように、多くの新しいマジック メソッドが追加されました (または http://www.php.net/ language.oop5.magic で見つけることができます)


可见性标记

含义

ZEND_ACC_PUBLIC

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

ZEND_ACC_PROTECTED

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

ZEND_ACC_PRIVATE

只能在类中调用

可见性标记

含有

ZEND_ACC_PUBLIC

可以在对象外いかなる作用領域调用.これとphp4 の方法の可用性性は一样的

ZEND_ACC_PROTECTED

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

ZEND_ACC_PRIVATE

只能在类中调用

メソッド

使用法

__construct(...)

オプションの自動的に呼び出されるオブジェクト コンストラクター(以前に定義されたメソッドはクラス名と同じです)。If __construct()classname() 両方の実装が存在します インスタンス化中にprocess、__construct()

の呼び出しを優先します。

__destruct()

インスタンスがスコープを離れる場合、または完全な終了を要求する場合 は暗黙的に呼び出します。ファイルやネットワーク ハンドルを閉じるなどのクリーンアップ作業を処理するインスタンス __destruct() メソッド。

__clone()

デフォルトでは すべてのインスタンスは true 参照渡しです. () php5 で、オブジェクト インスタンス を実際にコピーしたい場合は、 clone キーワード.clone キーワードがオブジェクト インスタンスで呼び出されたとき 、 __clone() メソッドが暗黙的に実行されます,。これにより、オブジェクトは必要な内部リソース データをコピーできます.

__toString()

テキストを使用してオブジェクトを表す場合たとえば、echo または を直接使用する場合オブジェクト print ステートメント クラスがこのマジック メソッド メソッドはエンジン によって自動的に呼び出されます。 > は、オブジェクトの現在の状態を説明する . を含む文字列を返す必要があります

__get($var)

スクリプトがオブジェクトの非表示プロパティをリクエストした場合 ( が存在しないか、アクセス制御により非表示になっています )とき、 __get()マジック メソッドが呼び出されます唯一のパラメータは要求されたプロパティ名です実装独自の内部ロジックを使用して、.

を返す最も妥当な戻り値を決定できます。

__set($var, $value)

__get() に非常に似ています。 __set() は、オブジェクト の実装の非表示のプロパティに割り当てるロジックを処理するために使用される、反対の機能 を提供します。 __set() は、標準属性テーブルにこれらの変数を暗黙的に作成することを選択できます他のストレージ メカニズムを使用して値を設定します または、単にエラーをスローして値 .

を破棄します。

__call($fname, $args)

オブジェクトの未定義メソッドを呼び出す場合、__call()マジックメソッド. このメソッドは 2 つのパラメータ を受け入れます: 呼び出されるメソッドの名前 すべての実際のメソッドの数値インデックスを含む配列.

の呼び出し時に渡されるパラメータ

__isset($varname)

php5.1.0 の後の isset($obj->prop) の呼び出しは、$ objprop があるかどうか、 を呼び出します$obj __isset() で定義されたメソッド 動的評価を使用してみる 動的 __get() および __set() メソッドは、属性 の読み取りと書き込みを正常に行うことができます。

__unset($varname)

__isset() と同様に、PHP 5.1.0 では、単純な OOPInterfaceオブジェクトのプロパティに使用できますが、このプロパティは である可能性がありますが、 はオブジェクトの標準属性テーブルですが、__get() および __set では可能です () の動的属性空間は意味があります 、つまり、__unset() は、この問題を解決するために導入されました .


还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如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 型 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() は read_property() のバリアントで、呼び出し元のスコープが現在の zval * を新しいものに直接置き換えることができます。デフォルトの動作では、標準属性テーブル内のプロパティのポインター アドレスを返します。存在せず、__get()/__set() マジック メソッドがない場合、ポインタは暗黙的に作成されて返されます。__get() または __set() メソッドが存在すると、このハンドラが失敗し、エンジンが依存するようになります。代わりに read_property を分離し、write_property 呼び出しを行います。

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

read_dimension() と write_dimension() は、対応する read_property() と write_property(); に似ていますが、オブジェクトのクラスの $obj['idx'] メソッドを使用してオブジェクトが配列としてアクセスされるときにトリガーされます。 ArrayAccess インターフェイスは実装されていません。デフォルトの動作ではエラーが発生します。それ以外の場合は、マジック メソッド offsetget($idx) または offsetset($idx, $value) が呼び出されます。

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

オブジェクトの値を設定または取得する場合、そのオブジェクトに対して get() または set() メソッドが呼び出されます。set の場合、新しい値が 2 番目のパラメーターとして渡されます。 ; 実際には、これらのメソッドは算術演算で使用されます。

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

このハンドラーは、オブジェクトのプロパティで isset() が呼び出されたときに呼び出されます。デフォルトでは、PHP 5.1.0 では、プロパティが見つからず、定義されている __isset() メソッドが存在しない場合、標準プロセッサは prop で指定されたプロパティ名をチェックします。 chk_type パラメータの値が 2 の場合、このメソッドが呼び出されます。chk_type の値が 0 の場合、属性のみが存在する必要があります。chk_type の値が 1 の場合は、それを IS_NULL にすることはできません。 、属性は存在する必要があり、FALSE 以外の値である必要があります。 注: php 5.0.x では、 chk_type の意味は、has_dimension.

の chk_type と一致します。
  • int has_dimension(zval *obj, zval *idx, int chk_type TSRMLS_DC)

オブジェクトを配列として扱うときに isset() を呼び出すとき (isset($obj['idx']) など)、このプロセッサが使用され、オブジェクトが ArrayAccess インターフェイスを実装しているかどうかがチェックされます。存在する場合は、offsetexists($idx) メソッドを呼び出します。見つからない場合 (offsetexists() の呼び出しを参照)、offsetexists() メソッドが実装されていない場合と同じで、0 を返します。それ以外の場合は、chk_type が 0 です。 chk_type is 1 は、オブジェクトの offsetget($idx) メソッドを呼び出して戻り値をテストする必要があることを示します。 TRUE(1) を返す前に、値が FALSE かどうかを確認してください。

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

これら 2 つのメソッドは、オブジェクトのプロパティをアンロードしようとするとき (または、オブジェクトを配列として適用するときに unset() が呼び出されるとき)、unset_property() ハンドラーによって標準プロパティ テーブル (存在する場合) からプロパティが削除されます。 )、または実装された __unset($prop) メソッド (PHP 5.1.0 の場合) の呼び出しを試行し、クラスが ArrayAccess.

を実装する場合、unset_dimension() は offsetunset($idx) メソッドを呼び出します。
  • HashTable *get_properties(zval *obj TSRMLS_DC)

このハンドラーは、内部関数が Z_OBJPROP() マクロを使用して標準プロパティ テーブルからプロパティを読み取るときに実際に呼び出されます。PHP オブジェクトのデフォルトのハンドラーは、Z_OBJ_P(object)->properties をアンラップして返します。真の標準属​​性テーブル。

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

このハンドラーは、クラスの function_table 内のオブジェクト メソッドを解析するときに呼び出されます。メインの function_table にメソッドがない場合、デフォルトのハンドラーは、オブジェクトの __call($name, $args) メソッドへのポインターを返します。 * ポインター。

  • int call_method(char *method, INTERNAL_FUNCTION_PARAMETERS)

ZEND_OVERLOADED_FUNCTION タイプとして定義された関数は、call_method ハンドラーとして実行されます。デフォルトでは、このハンドラーは未定義です。

  • union _zend_function *get_constructor(zval *obj TSRMLS_DC)

get_method() ハンドラーと同様に、このハンドラーは対応するオブジェクト メソッドへの参照を返します。クラスの zend_class_entry 内のコンストラクターは特別な方法で格納されるため、このメソッドのオーバーライドは非常にまれです。

  • zend_class_entry *get_class_entry(zval *obj TSRMLS_DC)

get_constructor() と同様に、このハンドラーはめったにオーバーライドされません。その目的は、オブジェクト インスタンスを元のクラス定義にマップし直すことです。

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

get_class_entry() は get_class_name() の 1 ステップであり、オブジェクトの zend_object を取得した後、オブジェクトのクラス名またはその親クラス名 (parent パラメーターの値によって異なります) をコピーして返します。返されたクラス名のコピーは、非永続ストレージ (emalloc()) を使用する必要があります。

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

比較演算子 (==、!=、<=、<、>、>= など) が 2 つのオブジェクト、オペランド (比較に参加する 2 つのオブジェクト) で使用される場合、compare_objects( ) はこの作業の最初の部分です。通常、その戻り値は 1、0、-1 であり、これは、比較ルールを使用して、標準の属性テーブルに基づいて比較されることを意味します。第 8 章「配列とハッシュテーブルの操作」で学習した配列比較ルールと同じです。

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

当尝试将对象转换为其他数据类型时, 会触发这个处理器. 如果将should_free设置为非0值, zval_dtor()将会在dst上调用, 首先释放内部的资源. 总之, 处理器应该尝试将src中的对象表示为dst给出的zval *的类型中. 这个处理器默认是未定义的, 但当有它的时候, 应该返回SUCCESS或FAILURE.

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

实现了数组访问的对象应该定义这个处理器, 它将设置当前的元素数量到count中并返回SUCCESS. 如果当前实例没有实现数组访问, 则它应该返回FAILURE, 以使引擎回头去检查标准属性表.

译注: 上面的句柄表和译者使用的php-5.4.9中已经不完全一致, 读者在学习这一部分的时候, 可以参考Zend/zend_object_handlers.c中最下面的标准处理器句柄表.

魔术方法第二部分

使用前面看到的对象句柄表的自定义版本, 可以让内部类提供与在用户空间基于对象或类的__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进行映射已经不能工作,

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート