深入理解PHP内核(十四)类的成员变量及方法

摘要:
类的成员方法是现实世界实体行为的抽象,可以用来实现类的行为。此函数用来初始化类的基本信息,其中包括类的成员变量。这里针对类的静态成员变量有一个更新的过程,关于这个过程我们在下面有关于静态变量中做相关介绍。在PHP的类结构中,类本身的静态变量存在在类结构的default_static_memebers字段中。

原文链接:http://www.orlion.ga/1237/

类的成员变量在PHP中本质是一个变量,只是这些变量都归属于某个类,并且给这些变量是有访问控制的。

类的成员方法在PHP中本质是一个函数,只是这个函数以类的方法存在,他可能是一个类方法也可能是一个实例方法,并且在这些方法都加上了类的访问控制。类的成员方法是现实世界实体行为的抽象,可以用来实现类的行为。

一、成员变量

成员变量在编译时已经注册到了类的结构中。在编译时类的声明编译会调用zend_do_begin_class_declaration函数。此函数用来初始化类的基本信息,其中包括类的成员变量。其调用顺序为[zend_do_begin_class_declaration]–>[zend_initalize_class_data]–>[zend_hash_init_ex]

zend_hash_init_ex(&ce->default_properties,0,NULL,zval_ptr_dtor_func,persistent_hashes,0);

因为类的成员变量是保存在HashTable,所以其数据的初始化使用zend_hash_init_ex函数来进行。

在声明类的时候初始化了类的成员变量所在的HashTable,之后如果有新的成员变量属性声明时,在编译时zend_do_declare_property。函数首先检查成员变量不允许的一些情况:

    • 接口中不允许使用成员变量

    • 成员变量不能拥有抽象属性

    • 不能声明成员变量为final

    • 不能重复声明属性

如果在类中将一个属性声明为final:

publicfinal$var

会报错:Fatal error: Cannot declare property …这个错误由zend_do_declare_property函数抛出:

if(access_type&ZEND_ACC_FINAL){
zend_error(E_COMPILE_ERROR,"Cannotdeclareproperty%s::$%sfinal,the
finalmodifierisallowedonlyformethodsandclasses",
CG(active_class_entry)->name,var_name-
>u.constant.value.str.val);
}

在定义检查没有问题之后,函数会进行成员变量的初始化操作。

ALLOC_ZVAL(property);//¾ĘŴ

if(value){//ÒʻĻļUɩ	ȐďĤ
*property=value->u.constant;
}else{
INIT_PZVAL(property);
Z_TYPE_P(property)=IS_NULL;
}

在初始化过程中,程序会先分配内存,如果这个成员变量有初始化的数据,则将数据直接赋值给该属性,否则初始化ZVAL,并将其类型设置为IS_NULL。在初始化过程完成后,程序通过调用zend_declare_property_ex函数将此成员变量添加到指定的类结构中。

常规的成员变量最后都会注册到类的default_propertiles字段。在我们平时的工作中,可能会用不到上面所说的这些过程,但是我们可能会使用get_class_vars()函数来查看类的成员变量。此函数返回由类的默认属性组成的关联数组,这个数组的元素以varname=>value的形式存在。其实现核心代码如下:

if(zend_lookup_class(class_name,class_name_len,&pceTSRMLS_CC)==FAILURE){
RETURN_FALSE;
}else{
array_init(return_value);
zend_update_class_constants(*pceTSRMLS_CC);
add_class_vars(*pce,&(*pce)->default_properties,return_valueTSRMLS_CC);
add_class_vars(*pce,CE_STATIC_MEMBERS(*pce),return_valueTSRMLS_CC);
}

首先调用zend_lookup_class函数查找名为class_name的类,并将复制给pce变量。这个查找的过程最核心是一个HashTable的查找函数zend_hash_quick_find,它会查找EG(class_table)。判断类是否存在,如果存在则直接返回。如果不存在,则需要判断是否可以自动加载,如果可以自动加载,则会加载类后再返回。如果不能找到类,则返回FALSE。如果找到了类,则初始化返回的数组,更新类的静态成员变量,添加类的成员变量到返回的数组。这里针对类的静态成员变量有一个更新的过程,关于这个过程我们在下面有关于静态变量中做相关介绍。

二、静态成员变量

类的静态成员变量是所有实例公用的,它归属于这个类,因此它也叫做类变量。在PHP的类结构中,类本身的静态变量存在在类结构的default_static_memebers字段中。

与普通成员变量不同,类变量可以直接通过类名调用,这也体现其称作类变量的特别。一个PHP实例:

classTipi{
publicstatic$var=10;
}

Tipi::$var;

通过VLD扩展查看其生成的中间代码:

functionname:(null)
numberofops:6
compiledvars:!0=$var
line#*opfetchextreturnoperands
-------------------------------------------------------------------------------
-
-
20>EXT_STMT
1NOP
62EXT_STMT
3ZEND_FETCH_CLASS:1'Tipi'
4FETCH_Rstaticmember'var'
5>RETURN1

branch:#0;line:2-6;sop:0;eop:5
path#1:0,
ClassTipi:[nouserfunctions]

这段中间代码仅仅与Tipi::$var这段调用对应,它与前面的类定义没有多大关系。根据VLD生成的内容我们可以知道PHP代码:Tipi::$var,生成的中间代码包括ZEND_FETCH_CLASS和FETCH_R。这里只是一个静态变量的调用,但是它却生成了两个中间代码。原因:我们要调用一个类的静态变量,当然要先找到这个类,然后再获取这个类的变量。从PHP源码来看,这是由于在编译时其调用了zend_do_fetch_static_member函数,而在此函数中又调用了zend_do_fetch_class函数,从而会生成ZEND_FETCH_CLASS中间代码。它所对应的执行函数为ZEND_FETCH_CLASS_SPEC_CONST_HANDLER。此函数会调用zend_fetch_class函数(Zend/zend_execute_API.c)。而zend_fetch_class函数最终也会调用zend_lookup_class_ex函数查找类。

找到了类接着应该就是查找类的静态成员变量,其最终调用的函数为:zend_std_get_static_property。这里由于第二个参数的类型为ZEND_FETCH_STATIC_MEMBER。这个函数最后是从static_members字段中查找对应的值返回。而在查找前会和前面一样,执行zend_update_class_constant函数,从而更新此类的所有静态成员变量,静态变量更新流程图:

深入理解PHP内核(十四)类的成员变量及方法第1张

三、成员方法

成员方法从本质上来将也是一种函数,所以其存储结构也和常规函数一样,存储在zend_function结构体中。对于一个类的多个成员方法,它是以HashTable的数据结构存储了多个zend_function结构体。和前面的成员变量一样,在类声明时成员方法也通过调用zend_initalize_class_data方法,初始化了整个方法列表所在的HashTable。

除去访问控制关键字,一个成员方法和常规函数是一样的,从语法解析中调用的函数一样(都是zend_do_begin_function_declaration函数),但是其调用的参数有一些不同,第三个参数is_method,成员方法的赋值为1,表示它作为成员方法的属性。在这个函数中会有一系统的编译判断,比如在接口中不能声明私有的成员方法。
在此程序判断后,程序将方法直接添加到类结构的function_table字段,在此之后,又是若干的编译检测。比如接口的一些魔术方法不能设置为非公有,不能被设置为static,如__call()、__callStatic()、__get()等。

与成员变量一样,成员方法也有一个返回所有成员方法的函数–get_class_methods()。此函数返回由指定的类中定义的方法名所组成的数组。

四、静态成员方法

类的静态成员方法通常也叫做类方法。与静态成员变量不同,静态成员方法与成员方法都存储在类结构的function_table字段。

classTipi{
publicstaticfunctiont(){
echo1;
}
}

Tipi::t();

以上的代码在VLD扩展下生成的部分中间代码:

numberofops:8
compiledvars:none
line#*opfetchextreturnoperands
-------------------------------------------------------------------------------
--
20>EXT_STMT
1NOP
82EXT_STMT
3ZEND_INIT_STATIC_METHOD_CALL
'Tipi','t'
4EXT_FCALL_BEGIN
5DO_FCALL_BY_NAME0
6EXT_FCALL_END
97>RETURN1

branch:#0;line:2-9;sop:0;eop:7
path#1:0,
ClassTipi:
Functiont:
Findingentrypoints
Branchanalysisfromposition:0

从以上的内容可以看出整个静态成员方法的调用是一个先查找方法再调用的过程。而对于调用操作,对应的中间代码为ZEND_INIT_STATIC_METHOD_CALL。由于类名和方法名都是常量,于是我们可以知道中间代码对应的函数是ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER。在这个函数中,它会首先调用zend_fetch_class函数,通过类名在EG(class_table)中查找类,然后再执行静态方法的获取方法。

if(ce->get_static_method){
EX(fbc)=ce->get_static_method(ce,function_name_strval,
function_name_strlenTSRMLS_CC);
}else{
EX(fbc)=zend_std_get_static_method(ce,function_name_strval,
function_name_strlenTSRMLS_CC);
}

如果类结构中的get_static_method方法存在,则调用此方法,如果不存在,则调用zend_std_get_static_method。在PHP的源码中get_static_method方法一般都是NULL,这里我们重点查看zend_std_get_static_method函数。此函数会查找ce->function_table列表,在查找到方法后检查方法的访问控制权限,如果不允许访问,则报错,否则返回函数结构体。

免责声明:文章转载自《深入理解PHP内核(十四)类的成员变量及方法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇windows下搭建nginx+php+laravel开发环境(转)[转]SQL Server 创建数据库邮件下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

VScode 搭建PHP环境

先按照这个搭建 使用Visual Studio Code搭建PHP调试环境 要注意的几个点 上文中的4.2. 配置Visual Studio Code 指定php的路径 配置 PHP Debug 配置完成后 文件夹下后有个 .vscode 文件夹 下有个 launch.json 内容如下 { // 使用 IntelliSense 了解相关属性。...

CentOS 7编译安装Tengine+PHP+MariaDB全程笔记

  安装环境:CentOS7 3.10.0-693.5.2.el7.x86_64   准备源码包:     pcre-8.41.tar.gz     openssl-1.0.1h.tar.gz     zlib-1.2.11.tar.gz     jemalloc-4.5.0.tar.bz2     tengine-2.1.0.tar.gz     lib...

用Zend Encoder加密PHP文件和PHP 优化配置

在发布一个你写好的PHP程序时,你是不是担心自已辛苦写出来的成果会被别人占为已有呢?其实我们可以用Zend Encoder为我们的PHP文件加上一层保护壳。 软件版本:2.0.1软件大小:10.2M适用平台:Win9X/2000/XP官方网址:http://www.zend.com/store/products/zend-encoder.php软件的使用方...

各种实用的 PHP 开源库推荐【转】

 转自: https://my.oschina.net/editorial-story/blog/882780 PHP 是一种通用开源脚本语言。语法吸收了 C 语言、Java 和 Perl 的特点,利于学习,使用广泛,主要适用于 Web 开发领域,是大多数后端开发者的首选。PHP 作为最受...

PHP开发基础知识

PHP的学习 PHP脚本以<?php 开始 ?>结束 <?php //此处写代码 ? > php默认文件扩展名.php php文件通常包含HTML标签以及一些PHP脚本代码 “echo”是php输出函数,下面是一个简单例子 <!DOCTYPE html> <html> <body>   <h...

Mac下搭建php开发环境【转】

Mac OS X 内置了Apache 和 PHP,这样使用起来非常方便。本文以Mac OS X 10.6.3为例。主要内容包括: 启动Apache 运行PHP 安装MySQL 使用phpMyAdmin 配置PHP的MCrypt扩展库 设置虚拟主机 启动Apache   有两种方法: 打开“系统设置偏好(System Preferences)” ->...