PHP内核
2019-04-25 16:24:17
8
0
0
成员方法从本质上来讲也是一种函数,所以其存储结构也和常规函数一样,存储在zend_function结构体中。 对于一个类的多个成员方法,它是以HashTable的数据结构存储了多个zend_function结构体。 和前面的成员变量一样,在类声明时成员方法也通过调用zend_initialize_class_data方法,初始化了整个方法列表所在的HashTable。 在类中我们如果要定义一个成员方法,格式如下:
除去访问控制关键字,一个成员方法和常规函数是一样的,从语法解析中调用的函数一样(都是zend_do_begin_function_declaration函数), 但是其调用的参数有一些不同,第三个参数is_method,成员方法的赋值为1,表示它作为成员方法的属性。 在这个函数中会有一系统的编译判断,比如在接口中不能声明私有的成员方法。 看这样一段代码:
2 | private function method(); |
如果直接运行,程序会报错:Fatal error: Access type for interface method Ifce::method() must be omitted in 这段代码对应到zend_do_begin_function_declaration函数中的代码,如下:
02 | if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) { |
03 | if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC))) { |
04 | zend_error(E_COMPILE_ERROR, "Access type for interface method %s::%s() must be omitted" , |
05 | CG(active_class_entry)->name, function_name->u.constant.value.str |
PHP内核
2019-04-25 16:23:12
11
0
0
在上一小节,我们介绍了类的结构和声明过程,从而,我们知道了类的存储结构,接口抽象类等类型的实现方式。 在本小节,我们将介绍类的成员变量和成员方法。首先,我们看一下,什么是成员变量,什么是成员方法。
类的成员变量在PHP中本质上是一个变量,只是这些变量都归属于某个类,并且给这些变量是有访问控制的。 类的成员变量也称为成员属性,它是现实世界实体属性的抽象,是可以用来描述对象状态的数据。
类的成
PHP内核
2019-04-25 16:22:33
11
0
0
面向对象编程中我们的编程都是围绕类和对象进行的。那在PHP内部类是怎么实现的呢? 它的内存布局以及存储是怎么样的呢?继承、封装和多态又是怎么实现的呢?
类的结构
首先我们看看类是什么。类是用户定义的一种抽象数据类型,它是现实世界中某些具有共性事物的抽象。 有时我们也可以理解其为对象的类别。类也可以看作是一种复合型的结构,其需要存储多元化的数据, 如属性、方法、以及自身的一些性质等。
类和函数类似,PHP内置及PHP扩展均可以实现自己的内部类,也可以由用户使用PHP代码进行定义。 当然我们在编写代码时通常是自己定义。
使用上,我们使用class关键字进行定义,后面接类名,类名可以是任何非PHP保留字的名字。 在类名后面紧跟着一对花括号,里面是类的实体,包括类所具有的属性,这些属性是对象的状态的抽象, 其表现为PHP中支持的数据类型,也可以包括对象本身,通常我们称其为成员变量。 除了类的属性, 类的实体中也包括类所具有的操作,这些操作是对象的行为的抽象,其表现为用操作名和实现该操作的方法, 通常我们称其为成员方法或成员函数。看类示例的代码:
05 | public function iMethod(); |
08 | final class Tipi extends ParentClass implements Ifce { |
09 | public static $sa = 'aaa' ; |
12 | public function __constrct() { |
15 | public function iMethod() { |
18 | private function _access() { |
21 | public static function access() { |
这里定义了一个父类ParentClass,一个接口Ifce,一个子类Tipi。子类继承父类ParentClass, 实现接口Ifce,并且有一个静态变量$sa,一个类常量 CA,一个公用方法,一个私有
PHP内核
2019-04-25 16:21:57
16
0
0
面向对象是一种编程范式,它将对象作为程序的基本单元,将程序和数据封装起来, 以此来提高程序的重用性、灵活性和可扩展性。
目前很多语言都支持面向对象编程,既然对象对象是一种范式,其实这就和具体的编程语言没有直接关系, 只不过很多语言将这个范式作为语言的基本元素,使用C语言也能够进行面向对象编程。
面向对象的程序设计中包含:
- 类。类是具体事物的抽象。通常类定义了事物的属性和所能完成的工作。有一点需要注意, 并不是所有的面向对象编程语言的类都具有class这个明确的实体。例如Javascript就不是基于类的。 Javascript中的类(Function)也具有类定义的特性。这也印证了面向对象只是一种编程范式。
- 对象。对象是类的实例。对象是具体的。
- 方法。方法是类定义对象可以做的事情。
- 继承性。继承是类的具体化,子类是比具备更多特性和行为的类。面向对象是对现实世界的一个抽象。 在很多时候的关系并不一定是继承关系。能在一定程序上实现代码的重用。
- 封装性、抽象性。封装性能实现的复杂性隐藏,减少出错的可能。
从我们接触PHP开始,我们最先遇到的是函数:数组操作函数,字符串操作函数,文件操作函数等等。 这些函数是我们使用PHP的基础,也是PHP自出生就支持的面向过程编程。面向过程将一个个功能封装, 以一种模块化的思想解决问题。
面向对象听起来很美,但是现实中的编程语言中很少有纯粹的面向对象的语言, 处于性能或者程序员的开发习惯,通常的编程语言都同时支持两种变编程方式。
PHP就是如此,从PHP4起开始支持面向对象编程。但PHP4的面向对象支持不太完善。 从PHP5起,PHP引入了新的对象模型(Object Model),增加了许多新特性,包括访问控制、 抽象类和final类、类方法、魔术方法、接口、对象克隆和类型提示等。 并且在近期发布的PHP5.3版本中,针对面向对象编程增加了命名空间、延迟静态绑定(Late Static Binding) 以及增加了两个魔术方法__callStatic()和__invoke()。
PHP中对象是按引用传递的,即对象进行赋值和操作的时候是按引用(reference)传递的,而不是整个对象的拷贝。
这一章我们从面向对象讲起,会说到PHP中的类,包括类的定义和实现、接口、抽象类以及与类相关的访问控制、 对象和命名空间等。除此之外也会从其存储的内部结构,类的单继承的实现,
PHP内核
2019-04-25 16:20:45
13
0
0
匿名函数在编程语言中出现的比较早,最早出现在Lisp语言中,随后很多的编程语言都开始有这个功能了, 目前使用比较广泛的Javascript以及C#,PHP直到5.3才开始真正支持匿名函数, C++的新标准C++0x也开始支持了。
匿名函数是一类不需要指定标示符,而又可以被调用的函数或子例程,匿名函数可以方便的作为参数传递给其他函数, 最常见应用是作为回调函数。
闭包(Closure)
说到匿名函数,就不得不提到闭包了,闭包是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数, 这个被应用的自由变量将和这个函数一同存在,即使离开了创建它的环境也一样,所以闭包也可认为是有函数和与其相关引用组合而成的实体。 在一些语言中,在函数内定义另一个函数的时候,如果内部函数引用到外部函数的变量,则可能产生闭包。在运行外部函数时, 一个闭包就形成了。
这个词和匿名函数很容易被混用,其实这是两个不同的概念,这可能是因为很多语言实现匿名函数的时候允许形成闭包。
使用create_function()创建"匿名"函数
前面提到PHP5.3中才才开始正式支持匿名函数,说到这里可能会有细心读者有意见了,因为有个函数是可以生成匿名函数的: create_function函数, 在手册里可以查到这个函数在PHP4.1和PHP5中就有了,这个函数通常也能作为匿名回调函数使用, 例如如下:
2 | $array = array (1, 2, 3, 4); |
3 | array_walk ( $array , create_function( '$value' , 'echo $value' )); |
这段代码只是将数组中的值依次输出,当然也能做更多的事情。 那为什么这不算真正的匿名函数呢, 我们先看看这个函数的返回值,这个函数返回一个字符串, 通常我们可以像下面这样调用一个函数:
我们在实现回调函数的时候也可以采用这样的方式,例如:
2 | function do_something( $callback ) { |
这样就能实现在函数do_something()执
PHP内核
2019-04-25 16:18:59
12
0
0
一个函数的执行结果要返回给调用者,除了使用return功能,还有一种办法,那就是以引用的形式传递参数,然后在内部修改这个参数的值。前一种方法往往只能返回一个值,如果我们的函数执行结果具有多种数据,便需要把这些数据打包到一个数组、类等复合类型的变量中才能得以实现;但后一种方法相比而言就简单一些了。
运行时传递引用:Call-time Pass-by-ref
标题有点绕口,其实很简单,功能如以下php代码所示:
02 | function byref_calltime( $a ) { |
03 | $a = '(modified by ref!)' ; |
06 | $foo = 'I am a string' ; |
我们在传递参数的时候使用&操作符,便可以传递$foo变量的引用过去,而不是copy一份。当我们在函数内核修改这个参数时,函数外部的$foo也跟着被一起修改了。同样的功能我们如何在扩展里实现呢,其实很简单,请看下面的源码:
01 | ZEND_FUNCTION(byref_calltime) |
06 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z" , &a) == FAILURE) |
21 | ZVAL_STRING(a, " (modified by ref!)" ,1); |
编译时的传递引用Compile-time Pass-by-ref
如果每一次都在调用函数时候都对参数加一个&符号真是太罗嗦了,有没有一个简单的办法呢,比如在定义函数的时候便声明这个参数是引用形式的
PHP内核
2019-04-25 16:14:08
15
0
0
前面对函数的内部表示以及参数的传递,返回值都有了介绍,那函数是怎么被调用的呢?内置函数和用户定义函数在调用时会有什么不一样呢? 下面将介绍函数调用和执行的过程。
函数的调用
函数被调用需要一些基本的信息,比如函数的名称,参数以及函数的定义(也就是最终函数是怎么执行的), 从我们开发者的角度来看, 定义了一个函数我们在执行的时候自然知道这个函数叫什么名字,以及调用的时候给传递了什么参数,以及函数是怎么执行的。 但是对于Zend引擎来说,它并不能像我们这样能“看懂”php源代码,他们需要对代码进行处理以后才能执行。我们还是从以下两个小例子开始:
下面我们先看一下其对应的opcodes:
02 | line # * op fetch ext return operands |
03 | --------------------------------------------------------------------------------- |
09 | line # * op fetch ext return operands |
10 | --------------------------------------------------------------------------------- |
PHP内核
2019-04-25 16:13:35
17
0
0
PHP语言中函数的返回值是通过return来完成了,就像下面的程序:
2 | function sample_long() { |
C语言也一样使用return关键字:
5 | int bar = sample_long(); |
那我们在扩展中编写的PHP函数如何把返回值回馈给用户端的函数调用者呢?看好,这里指的是回馈给,而不是单单的return~
你也许会认为扩展中定义的函数应该直接通过return关键字来返回一个值,比如由你自己来生成一个zval并返回,就像下面这样:
1 | ZEND_FUNCTION(sample_long_wrong) |
但是,上面的写法是无效的!与其让扩展开发员每次都初始化一个zval并return之,zend引擎早就准备好了一个更好的方法。它在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值这个问题。在前面我们已经知道了ZEND_FUNCTION宏展开后是void name(INTERNAL_FUNCTION_PARAMETERS)的形式,现在是我们展开代表参数声明的INTERNAL_FUNCTION_PARAMETERS宏的时候了。
1 | #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC |
- int ht
- zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
- zval **return_value_ptr,
- zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$th
PHP内核
2019-04-25 16:13:08
40
0
0
在编程语言中,一个函数或一个方法一般都有返回值,但也存在不返回值的情况,此时,这些函数仅仅仅是处理一些事务, 没有返回,或者说没有明确的返回值,在pascal语言中它有一个专有的关键字 procedure 。 在PHP中,函数都有返回值,分两种情况,使用return语句明确的返回和没有return语句返回NULL。
return语句
当使用return语句时,PHP给用户自定义的函数返回指定类型的变量。 依旧我们查看源码的方式,对return 关键字进行词法分析和语法分析后,生成中间代码。 从 Zend/zend_language_parser.y文件中可以确认其生成中间代码调用的是 zend_do_return 函数。
01 | void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) |
04 | int start_op_number, end_op_number; |
07 | if (CG(active_op_array)->return_reference |
08 | && !zend_is_function_or_method_call(expr)) { |
09 | zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC); |
11 | zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC); |
17 | opline->opcode = ZEND_RETURN; |
22 | if (do_end_vparse && zend_is_function_or_method_call(expr)) { |
PHP内核
2019-04-25 16:12:44
20
0
0
最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数。
zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成便可以了,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表这参数的个数。
紧接着需要传递个zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样。下面表示了最常用的几个符号。
01 | type_spec是格式化字符串,其常见的含义如下: |
10 | O Object instance of a specified type 特定类型的对象 |
11 | z Non-specific zval 任意类型~ |
13 | f 表示函数、方法名称,PHP5.1里貌似木有... ... |
这个函数就像printf()函数一样,后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成C语言里的类型。
01 | ZEND_FUNCTION(sample_getlong) |
04 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l" , &foo) == FAILURE) |
08 | php_printf( "The integer value of the parameter is: %ld\n" , foo); |
一般来说,int和long这两种数据类型的数据往往是相同的,但也有例外情况。所以我们不应改把long的数组放在一个int里,尤其是在64位平台里,那将引发一些不容易排查的Bug。所以通过zend_parse_parameter()函数接收参数时,我们应该使用内