>  기사  >  백엔드 개발  >  [번역][php 확장 개발 및 내장] 11장 -php5 객체

[번역][php 확장 개발 및 내장] 11장 -php5 객체

黄舟
黄舟원래의
2017-02-10 10:12:061488검색

맞는 말이지만, php5 객체가 사용하는 API 함수는 여전히 php4의 API에 따라 구축되었습니다. 10장 "php4 객체"를 읽었다면 이 장의 내용에 어느 정도 익숙해졌을 것입니다. Chapter 10과 마찬가지로 확장 이름을 Sample3으로 바꾸고 중복 코드를 정리하고 확장의 뼈대 코드만 남겨두세요. >
php5 개체 변수에는 두 가지 핵심 구성 요소가 있습니다. 9장 "리소스 데이터 타입"에서 소개한 숫자형 리소스 ID와 매우 유사한 숫자형 식별자로, 해당 테이블에서 객체 인스턴스를 찾는 키 역할을 한다. zend_class_entry 및 내부 속성 테이블에 대한 참조를 포함합니다.

두 번째 요소는 개체 변수입니다. Zend 엔진이 인스턴스를 처리하는 방법을 사용자 정의할 수 있는 핸들 테이블이 표시됩니다.

zend_class_entry

클래스 항목은 사용자 정의 클래스의 내부 표현입니다. 이전 장에서 본 것처럼 이 구조는 클래스 이름과 해당 함수 테이블을 사용하여 INIT_CLASS_ENTRY()를 호출하여 초기화됩니다. 다음 MINIT 단계에서 등록하려면 zend_register_internal_class()를 사용하세요. 지금까지는?", 그리고 지금까지는 당신이 옳을 것입니다. 이제 몇 가지 객체 메서드를 정의해 보겠습니다. 매우 명확하고 환영할 만한 차이점을 발견하게 될 것입니다.

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() 매크로는 Zend Engine 2에서 도입되었습니다. 이는 PHP_FUNCTION() 매크로를 캡슐화한 것으로, php4와 같이 수동으로 메소드를 정의하지 않고도 클래스 이름과 메소드 이름을 결합합니다.

정의

메서드 구현을 정의합니다. , 다른 함수와 마찬가지로 클래스의 함수 테이블에 연결하기만 하면 됩니다. 구현에 사용되는 PHP_METHOD() 매크로 외에도 함수 목록 정의에 사용할 수 있는 몇 가지 새로운 매크로가 있습니다.

PHP_ME(classname, methodname, arg_info, flags)

에 소개된 PHP_FE() 매크로와 비교 5장 "첫 번째 확장", PHP_ME()는 클래스 이름 매개 변수와 플래그 매개 변수를 끝에 추가합니다(공용, 보호, 개인, 정적 및 기타 액세스 제어와 추상 및 기타 옵션을 제공하는 데 사용됨). helloWorld 메소드를 정의하려면 다음과 같이 정의할 수 있습니다:

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

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

은 PHP_FALIAS() 매크로와 매우 유사합니다. 이 매크로를 사용하면 설명된(동일한 클래스에서) 구현에 별칭 매개변수를 제공하여 이름으로 지정된 새 이름을 제공할 수 있습니다. 예를 들어, helloWorld 메소드를 복사하려면 다음과 같이 정의할 수 있습니다:

PHP_ME(Sample3_SecondClass,helloWorld,NULL,ZEND_ACC_PUBLIC)

  • PHP_ABSTRACT_ME(classname , 메소드 이름, 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 只能在类中调用


예를 들어 앞서 정의한 Sample3_SecondClass::helloWorld() 메서드에는 객체 인스턴스가 필요하지 않으므로 정의를 단순에서 변경할 수 있습니다. ZEND_ACC_PUBLIC을 ZEND_ACC_PUBLIC | ZEND_ACC_STATIC으로 변경하여 엔진이 이를 알고 나면 (인스턴스를) 제공하지 않도록 합니다.

마법의 방법

ZE1의 매직 메소드 외에도 ZE2에는 다음 표와 같이 많은 새로운 매직 메소드가 추가되었습니다. (또는 http://www.php.cn/에서 확인할 수 있습니다.)


__destruct()인스턴스가 범위를 벗어날 때


还有其他的魔术方法功能, 它们可以通过某些接口来使用, 比如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()은 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() 메서드가 호출됩니다. 객체 자체는 다음과 같이 전달됩니다. 첫 번째 매개변수는 새 값이 두 번째 매개변수로 전달됩니다. ; 실제로 이러한 메소드는 산술 연산에 사용됩니다.

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

이 핸들러는 isset()이 호출될 때 호출됩니다. 기본적으로 표준 핸들러는 지정된 속성 이름을 확인합니다. 이 속성이 PHP 5.1.0에 없고 __isset() 메소드가 정의되어 있으면 chk_type 매개변수의 값이 2입니다. , 속성만 존재해야 합니다. chk_type 값이 0이면 존재해야 하며 IS_NULL 값이 아니어야 합니다. chk_type 값이 1이면 속성이 존재해야 하며 FALSE 이외의 값이어야 합니다. PHP 5.0.x, chk_type의 의미는 has_dimension의 chk_type과 일치합니다.

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

객체를 배열(예: isset($obj['idx']))로 처리할 때 isset()을 호출하면 이 프로세서가 사용됩니다. 객체 구현 여부 확인 ArrayAccess 인터페이스가 구현되어 있으면 offsetexists($idx) 메소드를 호출하고, 찾지 못하면(offsetexists() 호출 참조), offsetexists() 메소드가 구현되지 않은 것과 동일합니다. 그렇지 않고 chk_type이 0이면 직접 true(1)를 반환합니다. chk_type이 1이면 개체의 offsetget($idx) 메서드를 호출하고 반환 값을 테스트해야 함을 나타냅니다. 값이 FALSE가 아닌 경우에만 TRUE(1)를 반환합니다.

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

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

객체 속성을 언로드하려고 할 때 이 두 가지 방법을 사용하세요. (또는 배열의 객체에 unset()을 적용할 때) unset_property() 핸들러는 표준 속성 테이블(존재하는 경우)에서 속성을 제거하거나 구현된 __unset($prop) 메서드(php 5.1)를 호출하려고 시도합니다. .0), unset_dimension()은 클래스가 ArrayAccess를 구현할 때 offsetunset($idx) 메서드를 호출합니다.

  • HashTable *get_properties(zval *obj TSRMLS_DC)

이 핸들러는 내부 함수가 Z_OBJPROP() 매크로를 사용하여 표준 속성 테이블에서 속성을 읽을 때 실제로 호출됩니다. 프로세서는 압축을 풀고 실제 표준 속성 테이블인 Z_OBJ_P(객체)->properties를 반환합니다.

  • union _zend_function *get_method(zval **obj_ptr char *method_name, int 메소드 이름_len TSRMLS_DC)

이 핸들러는 클래스의 function_table에 있는 객체 메서드를 구문 분석할 때 호출됩니다. 해당 메서드가 기본 function_table에 없으면 기본 핸들러는 객체에 대한 포인터를 반환합니다. 이름, $args) 메소드 래핑 zend_function * 포인터.

  • 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 parent TSRMLS_DC)

get_class_entry()는 한 단계입니다. 객체의 zend_object를 가져온 후 객체의 클래스 이름이나 상위 클래스 이름을 사용합니다(이는 매개변수 parent 값에 따라 다름). 클래스 이름의 반환된 복사본을 반환해야 합니다. 비영구 저장소(emalloc())를 사용합니다.

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

비교 연산자(예: ==, !=, 6ccbb8b0f4c668de47d964671fff91b7, >=)를 사용하는 경우 두 개의 객체가 있는 경우 비교 연산자()를 호출 피연산자(비교에 참여하는 두 개체)는 이 작업의 첫 번째 부분입니다. 반환 값은 일반적으로 각각 초과, 같음, 미만을 나타내는 1, 0, -1입니다. 기본적으로 개체는 비교 기준으로 사용됩니다. 표준 속성 테이블에서 8장 "배열 및 해시 테이블 작업"에서 배운 배열 비교 규칙과 동일한 비교 규칙을 사용합니다.

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

객체를 다른 데이터 유형으로 캐스팅하려고 하면 이 프로세서가 트리거됩니다. should_free가 0이 아닌 값으로 설정되면 dst에서 zval_dtor()가 호출되어 먼저 내부 리소스를 해제합니다. 즉, 프로세서는 src의 객체를 zval * 유형의 Out으로 dst로 나타내려고 시도해야 합니다. 이 핸들러는 기본적으로 정의되지 않지만 존재하는 경우 SUCCESS 또는 FAILURE를 반환해야 합니다.

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

배열 액세스를 구현하는 객체는 이 핸들러를 정의해야 합니다. 이 핸들러는 현재 요소 수를 count로 설정하고 현재 인스턴스가 배열을 구현하지 않는 경우 SUCCESS를 반환합니다. 액세스할 경우 엔진이 돌아가서 표준 속성 테이블을 확인하도록 하려면 FAILURE를 반환해야 합니다.

주석: 위의 핸들 테이블과 php-5.4.9에서 사용되는 번역기는 더 이상 완전히 일관성이 없습니다. 이 부분을 공부할 때 독자는 Zend/zend_object_handlers.c.

하단에 있는 표준 프로세서 핸들 테이블을 참조할 수 있습니다. 마법의 방법 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)!


방법

사용법

__construct(...)

자동으로 호출되는 객체 생성자 ( 클래스 이름과 일치하는 메서드를 이전에 정의함). __construct()classname()의 두 구현 모두 , 이 인스턴스화되어 존재합니다. 이 과정에서 ,__construct()

, 또는 완전 종료를 요청할 때 ,은 모두 인스턴스의 __destruct() 메소드가 일부 정리 작업을 처리하기 위해 암시적으로 호출되도록 합니다, 예를 들어 파일 또는 네트워크 핸들을 닫는 경우 .

__clone()

기본적으로 ,모든 인스턴스는 진정한 참조별 전달입니다.php5에서 객체 인스턴스,를 실제로 복사하려면 clone키워드를 사용해야 합니다.객체 인스턴스에서 clone 키워드를 호출할 때, __clone() 메서드는 암시적으로 실행됩니다,. 이를 통해 객체가 일부 필수 내부 리소스 데이터를 복사할 수 있습니다.

__toString()

텍스트를 사용하여 객체를 나타내는 경우 ,예를 들어 echo 또는 print 문을 객체에 직접 사용하는 경우, __toString() 메서드는 엔진 에 의해 자동으로 호출됩니다. 클래스가 이 마법 메서드 를 구현하는 경우 는 개체의 현재 상태에 대한 설명이 포함된 문자열을 반환해야 합니다 .

__get($var)

스크립트가 개체의 보이지 않는 속성을 요청하는 경우 (이 존재하지 않거나 접근 제어로 인해 보이지 않습니다 )할 때, __get()마법 메서드가 호출됩니다,유일한 매개 변수는 요청된 속성 이름입니다.구현 자체 내부 논리를 사용하여 .

__set($var , $value)

__get()과 매우 유사합니다. __set() 객체의 보이지 않는 속성에 할당하는 논리를 처리하는 데 사용되는 , 구현을 제공합니다. __set()은 표준 속성 테이블 , 에서 이러한 변수를 암시적으로 생성하고 다른 저장 메커니즘 , 아니면 오류를 발생시키고 .

__call($fname, $args)

객체의 정의되지 않은 메서드를 호출할 때 __call()을 사용하면 아름다운 결과를 얻을 수 있습니다. 🎜> 매직 메소드 처리.이 메소드는 두 개의 매개변수 를 허용합니다:호출된 메소드 이름,호출에 전달된 모든 인수의 숫자 인덱스를 포함하는 배열.

__isset ($varname )

php5.1.0 이후 isset($obj->prop) 호출은 🎜>$obj, , prop이 있는지 확인하세요. $obj__isset()method, 동적 평가 동적 사용 여부 __get()__set() 메서드는

__unset($varname)

__isset(), php 5.1과 유사 .0unset() 함수에 대한 간단한 OOP인터페이스를 소개합니다. 객체 속성에 사용할 수 있습니다,이 속성은 객체의 표준 속성 테이블에 없을 수도 있지만, 그러나 __get()__set() 의 동적 속성 공간에는 적합할 수 있습니다. 따라서 이 문제를 해결하기 위해 __unset()을 도입했습니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.