深入了解php底层机制
作为一门动态语言,php是如何实现的,其底层机制如何,具有什么样的特点,本文深入浅出介绍了包括php设计理念、整体结构、核心数据结构和变量在内的相关底层知识,对我们更好的开发php程序,优化性能等有一定的指导意义。
1、概述
what is php?
一种适用于web开发的动态语言。具体点说:就是一个用c语言实现包含大量组件的软件框架。更狭义点看,可以把它认为是一个强大的ui框架。
了解它底层实现的目的?
-
动态语言要像用好首先得了解它
-
内存管理、框架模型值得我们借鉴
-
通过扩展开发实现更多更强大的功能,优化我们程序的性能
2、php的设计理念及特点
-
多进程模型
由于php是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,php也早已支持多线程模型。
-
弱类型语言
和c/c++、java、c#等语言不同,Php是一门弱类型语言:一个变量的类型并不是一开始就确定不变,运行中才会确定并可能发生隐式或显式的类型转换,这种机制的灵活性在web开发中非常方便、高效,具体会在后面php变量中详述。
-
引擎(Zend)+组件(ext)的模式降低内部耦合
-
中间层(sapi)隔绝web server和php
-
语法简单灵活,没有太多规范。(导致风格混杂)
-
再差的程序员也不会写出太离谱危害全局的程序。
3、Php的四层体系
Php的核心架构如下图
从图上可以看出,php从下到上是一个4层体系
-
Zend引擎
Zend整体用纯c实现,是php的内核部分,它将php代码翻译(词法、语法解析等一系列编译过程)为可执行opcode的处理并实现相应的处理方法、实现了基本的数据结构(如hashtable、oo)、内存分配及管理、提供了相应的api方法供外部调用,是一切的核心,所有的外围功能均围绕zend实现。
-
Extensions
围绕着zend引擎,extensions通过组件式的方式提供各种基础服务,我们常见的各种内置函数(如array系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension以达到功能扩展、性能优化等目的(如贴吧正在使用的php中间层、富文本解析就是extension的典型应用)。
-
Sapi
Sapi全称是Server Application Programming Interface,也就是服务端应用编程接口,sapi通过一系列钩子函数,使得php可以和外围交互数据,这是php非常优雅和成功的一个设计,通过sapi成功的将php本身和上层应用解耦隔离,php可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。后面将在sapi章节中介绍
-
上层应用
这就是我们平时编写的php程序,通过不同的sapi方式得到各种各样的应用模式,如通过webserver实现web应用、在命令行下以脚本方式运行等等。
如果php是一辆车,那么
-
车的框架就是php本身
-
Zend是车的引擎(发动机)
-
Ext下面的各种组件就是车的轮子
-
Sapi可以看做是公路,车可以跑在不同类型的公路上
而一次php程序的执行就是汽车跑在公路上。
因此,我们需要:性能优异的引擎+合适的车轮+正确的跑道
4、Sapi
如前所述,sapi通过通过一系列的接口,使得外部应用可以和php交换数据并可以根据不同应用特点实现特定的处理方法,我们常见的一些sapi有:
-
apache2handler
这是以apache作为webserver,采用mod_php模式运行时候的处理方式,也是现在应用最广泛的一种。
-
cgi
这是webserver和php直接的另一种交互方式,也就是大名鼎鼎的fastcgi协议,在最近今年fastcgi+php得到越来越多的应用,也是异步webserver所唯一支持的方式。关于fastcgi和mod_php,可以参见另外一篇文章《php性能调研-mod_php vs fastcgi》
-
cli
命令行调用的应用模式
Sapi的定义及主要接口函数如下图
这里介绍一下其中一些主要函数
-
startup:php被调用时初始化操作
比如cgi模式,在startup的时候会加载所有的extension并执行模块初始化工作。
-
shutdown:php关闭时收尾工作
-
activate:请求初始化
-
dectivate:请求结束时收尾工作
-
ub_write:指定数据输出方式
比如apache2handler方式,由于php作为apache的一个so存在,因此其输出也就是调用apache的ap_write函数,而在cgi模式下,会系统调用write。
-
sapi_error:错误处理函数
-
read_post:读取post数据
-
register_server_variables:往$_SERVER中注册环境变量
这个一般根据不同协议标准注册注册的变量。
5、Php的执行流程&opcode
我们先来看看php代码的执行所经过的流程。
从图上可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。
通过上面描述不难看出,php的执行的核心是翻译出来的一条一条指令,也即opcode
Opcode是php程序执行的最基本单位。一个opcode由两个参数(op1,op2)、返回值和处理函数组成。Php程序最终被翻译为一组opcode处理函数的顺序执行
常见的几个处理函数
ZEND_ASSIGN_SPEC_CV_CV_HANDLER : 变量分配 ($a=$b)
ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函数调用
ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串拼接 $a.$b
ZEND_ADD_SPEC_CV_CONST_HANDLER: 加法运算 $a+2
ZEND_IS_EQUAL_SPEC_CV_CONST:判断相等 $a==1
ZEND_IS_IDENTICAL_SPEC_CV_CONST:判断相等 $a===1
6、HashTable — 核心数据结构
HashTable是zend的核心数据结构,在php里面几乎并用来实现所有常见功能,我们知道的php数组即是其典型应用,此外,在zend内部,如函数符号表、全局变量等也都是基于hash table来实现。
php的hash table具有如下特点:
-
支持典型的key->value查询
-
可以当做数组使用
-
添加、删除节点是O(1)复杂度
-
key支持混合类型:同时存在关联数组合索引数组
-
Value支持混合类型:array (“string”,2332)
-
支持线性遍历:如foreach
Zend hash table实现了典型的hash表散列结构,同时通过附加一个双向链表,提供了正向、反向遍历数组的功能。其结构如下图
可以看到,在hash table中既有key->value形式的散列结构,也有双向链表模式,使得它能够非常方便的支持快速查找和线性遍历。
-
散列结构
Zend的散列结构是典型的hash表模型,通过链表的方式来解决冲突。需要注意的是zend的hash table是一个自增长的数据结构,当hash表数目满了之后,其本身会动态以2倍的方式扩容并重新元素位置。初始大小均为8。
另外,在进行key->value快速查找时候,zend本身还做了一些优化,通过空间换时间的方式加快速度。比如在每个元素中都会用一个变量nKeyLength标识key的长度以作快速判定。
-
双向链表
Zend hash table通过一个链表结构,实现了元素的线性遍历。理论上,做遍历使用单向链表就够了,之所以使用双向链表,主要目的是为了快速删除,避免遍历。
Zend hash table是一种复合型的结构,作为数组使用时,即支持常见的关联数组也能够作为顺序索引数字来使用,甚至允许2者的混合。
-
Php关联数组
关联数组是典型的hash_table应用。一次查询过程经过如下几步
-
getKeyHashValue h;
-
index = n & nTableMask;
-
Bucket *p = arBucket[index];
-
while (p) {
-
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
-
RETURN p->data;
-
}
-
p=p->next;
-
}
-
RETURN FALTURE;
从代码可以看出,这是一个常见的hash查询过程并增加一些快速判定加速查找。
-
Php索引数组
索引数组就是我们常见的数组,通过下标访问。例如 $arr[0]
Zend HashTable内部进行了归一化处理,对于index类型key同样分配了hash值和nKeyLength(为0)。内部成员变量nNextFreeElement就是当前分配到的最大id,每次push后自动加一。
正是这种归一化处理,php才能够实现关联和非关联的混合
由于push操作的特殊性,索引key在php数组中先后顺序并不是通过下标大小来决定,而是由push的先后决定。
例如 $arr[1] = 2; $arr[2] = 3;
对于double类型的key,Zend HashTable会将他当做索引key处理
7、Php变量
概述
-
Php是一门弱类型语言,本身不严格区分变量的类型。
-
Php在变量申明的时候不需要指定类型。
-
Php在程序运行期间可能进行变量类型的隐示转换。
-
和其他强类型语言一样,程序中也可以进行显示的类型转换。
-
Php变量可以分为简单类型(int、string、bool)、集合类型(array resource object)和常量(const)
-
以上所有的变量在底层都是同一种结构 zval
Zval
Zval是zend中另一个非常重要的数据结构,用来标识并实现php变量,其数据结构如下
Zval主要由三部分组成:
1、 type:指定了变量所述的类型(整数、字符串、数组等)
2、 refcount&is_ref:用来实现引用计数(后面具体介绍)
3、 value:核心部分,存储了变量的实际数据
u zvalue
Zvalue是用来保存一个变量的实际数据。因为要存储多种类型,所以zvalue是一个union,也由此实现了弱类型。
Php变量类型和其实际存储对应关系如下
IS_LONG -> lvalue
IS_DOUBLE -> dvalue
IS_ARRAY -> ht
IS_STRING -> str
IS_RESOURCE -> lvalue
u 引用计数
引用计数在内存回收、字符串操作等地方使用非常广泛。Php中的变量就是引用计数的典型应用
Zval的引用计数通过成员变量is_ref和ref_count实现,通过引用计数,多个变量可以共享同一份数据。避免频繁拷贝带来的大量消耗
在进行赋值操作时,zend将变量指向相同的zval同时ref_count++,在unset操作时,对应的ref_count-1。只有ref_count减为0时才会真正执行销毁操作
如果是引用赋值,则zend会修改is_ref为1
u 写时拷贝
Php变量通过引用计数实现变量共享数据,那如果改变其中一个变量值呢?
当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共享,则为其复制一份ref_count为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。可见,只有在有写操作发生时zend才进行拷贝操作,因此也叫copy-on-write(写时拷贝)
对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。
整数、浮点数类型变量
整数、浮点数是php中的基础类型之一,也是一个简单型变量。
对于整数和浮点数,在zvalue中直接存储对应的值。其类型分别是long和double。
从zvalue结构中可以看出,对于整数类型,和c等强类型语言不同,php是不区分int、unsigned int、long、long long等类型的,对它来说,整数只有一种类型也就是long。由此,可以看出,在php里面,整数的取值范围是由编译器位数来决定而不是固定不变的。
对于浮点数,类似整数,它也不区分float和double而是统一只有double一种类型。
-
在php中,如果整数范围越界了怎么办?
这种情况下会自动转换为double类型,这个一定要小心,很多trick都是由此产生。
字符串变量
和整数一样,字符变量也是php中的基础类型和简单型变量
通过zvalue结构可以看出,在php中,字符串是由由指向实际数据的指针和长度结构体组成,这点和c++中的string比较类似。
由于通过一个实际变量表示长度,和c不同,它的字符串可以是2进制数据(包含\\0),同时在php中,求字符串长度strlen是O(1)操作。
在新增、修改、追加字符串操作时,php都会重新分配内存生成新的字符串。
最后,出于安全考虑,php在生成一个字符串时末尾仍然会添加\\0
-
常见的字符串拼接方式及速度比较
假设有如下4个变量:
$strA=‘123’; $strB = ‘456’; $intA=123; intB=456;
现在对如下的几种字符串拼接方式做一个比较和说明
1、$res = $strA.$strB和$res = “$strA$strB”
这种情况下,zend会重新malloc一块内存并进行相应处理,其速度一般
2、$strA = $strA.$strB
这种是速度最快的,zend会在当前strA基础上直接relloc,避免重复拷贝
3、$res = $intA.$intB
这种速度较慢,因为需要做隐式的格式转换,实际编写程序中也应该注意尽量避免
4、$strA = sprintf (“%s%s”,$strA.$strB);
这会是最慢的一种方式,因为sprintf在php中并不是一个语言结构,本身对于格式识别和处理就需要耗费比较多时间,另外本身机制也是malloc。不过sprintf的方式最具可读性,实际中可以根据具体情况灵活选择。
数组变量
如前所述,Php的数组通过Zend HashTable来天然实现
-
foreach操作如何实现?
对一个数组的foreach就是通过遍历hashtable中的双向链表完成。对于索引数组,通过foreach遍历效率比for高很多,省去了key->value的查找
Count操作直接调用HashTable->NumOfElements,O(1)操作
对于’123’这样的字符串,zend会转换为其整数形式。$arr[‘123’]和$arr[123]是等价的
资源类型变量
这是php中最复杂的一种变量,也是一种复合型结构。
Php的zval可以表示广泛的数据类型,但是对于自定义的数据类型却很难充分描述。由于没有有效的方式描绘这些复合结构,因此也没有办法对它们使用传统的操作符。要解决这个问题,只需要通过一个本质上任意的标识符(label)引用指针,这种方式被称为资源。
在zval中,对于resource,lval作为指针来使用,直接指向资源所在的地址。
Resource可以是任意的复合结构,我们熟悉的mysqli、fsock、memcached等都是资源。
-
使用资源
-
• 注册
对于一个自定义的数据类型,要想将它作为资源。首先需要进行注册,zend会为它分配全局唯一标示
-
• 获取一个资源变量
对于资源,zend维护了一个id->实际数据的hash_tale。对于一个resource,在zval中只记录了它的id。fetch的时候通过id在hash_table中找到具体的值返回
-
• 资源销毁
资源的数据类型是多种多样的。Zend本身没有办法销毁它。因此需要用户在注册资源的时候提供销毁函数。当unset资源时,zend调用相应的函数完成析构。同时从全局资源表中删除它。
-
持久化资源
资源可以长期驻留,不只是在所有引用它的变量超出作用域之后,甚至是在一个请求结束了并且新的请求产生之后。这些资源称为持久资源,因为它们贯通SAPI的整个生命周期持续存在,除非特意销毁。
很多情况下,持久化资源可以在一定程度上提高性能。比如我们常见的mysql_pconnect ,持久化资源通过pemalloc分配内存,这样在请求结束的时候不会释放。
对zend来说,对两者本身并不区分。
Php变量的作用域
-
Php中的局部变量和全局变量是如何实现的?
对于一个请求,任意时刻php都可以看到两个符号表(symbol_table和active_symbol_table),其中前者用来维护全局变量。后者是一个指针,指向当前活动的变量符号表,当程序进入到某个函数中时,zend就会为它分配一个符号表x同时将active_symbol_table指向a。通过这样的方式实现全局、局部变量的区分
-
获取变量值
php的符号表是通过hash_table实现的,对于每个变量都分配唯一标识,获取的时候根据标识从表中找到相应zval返回
-
函数中使用全局变量
在函数中,我们可以通过显式申明global来使用全局变量。在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建。
php经典书籍珍选与推荐
1.PHP从入门到精通
简介:初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用PHP进行网络开发应该掌握的各方面技术。书中所有知识都结合具体实例进行介绍,涉及的程序代码均附以详细的注释,可以使读者轻松领会PHP程序开发的精髓,快速提高开发技能。另外,本书除了纸质内容之外,配书光盘中还给出了海量开发资源库。
推荐语:很适合初学者,里面的例子很多,而且浅显易懂。
2.PHP和MySQL Web开发(原书第4版)
简介:将PHP开发与MySQL应用相结合,分别对PHP和MySQL做了深入浅出的分析,不仅介绍PHP和MySQL的一般概念,而且对PHP和MySQL的Web应用做了较全面的阐述,并包括几个经典且实用的例子。
推荐语:初读这本书的时候,很不适应感觉很晦涩,但深入学习后发现这本书的逻辑性很强。
3.深入理解PHP:高级技巧、面向对象与核心技术
简介:本书一本帮助中初级PHP程序员修炼成为高级PHP程序员的书,也是一本让PHP程序员能更快、更好地进行应用开发的书。国际知名Web开发专家和技术畅销书作家全新力作,是PHP领域经典著作之一。它从编程技巧(PHP与MySQL的高级技巧)、面向对象(系统的面向对象知识)和扩展(PHP与其他技术的结合使用)三个角度系统讲解和总结了成为中高级PHP程序员应该具备的技术和技能,包含大量实用案例,极具可操作性。
推荐语:可以说,这是深而不晦涩,很接地气的提升书籍。
4.PHP 6 高级 编程
简介:本书首先介绍了PHP专业开发的墓础,并回顾了面向对象编程(oop)的基本 概念,然后介绍了比较高级的开发技术。*后,将所介绍的内容综合应用于 实践,使用模型-视图-控制器(mvc)架构从头构建一个应用程序。
推荐语:虽说现在已经是PHP7了,但是这本书仍然值得一读,php的很多特性讲的很明白。
写在最后:
说出你的需求,让我做你的好助手,好管家,协助你来发现更多的优质资源。
留下你的评论与意见,集思广益共享价值。
回见啦,我是呆傻萌,每晚10点不见不散。
古言古语:
朱雀桥边野草花,乌衣巷口夕阳斜。
旧时王谢堂前燕,飞入寻常百姓家。
PHP 8:函数和方法
作者 | Deepak Vohra
译者 | 刘雅梦
策划 | 丁晓昀
本文属于专题文章《深入浅出 PHP 8》。 根据w3tech的数据,PHP 仍然是 Web 上使用最广泛的脚本语言之一,77.3%的网站使用 PHP 进行服务器端编程。PHP 8 带来了许多新特性和其他改进,我们将在本系列文章中进行探讨。PHP 8.0 添加了对多个函数和方法相关特性的支持,其中一些是对现有特性的改进,而另一些则是全新的特性。PHP 8.1 中增强的可调用语法可用于通过可调用对象创建匿名函数。命名函数参数可以与位置参数一起使用,另外还有一个好处,即命名参数没有顺序,可以通过它们的名称来传达含义。纤程(Fiber)是可中断的函数,增加了对多任务的支持。
对象继承是大多数面向对象语言(包括 PHP)所使用的编程范式。它可以从任何扩展类中重写公共和受保护的方法,以及在类中定义的类属性和常量。在 PHP 中,公共方法不能通过更严格的访问来重新实现,例如将 public 方法设为 private 。为了演示这一点,考虑一个扩展了类 A 的类 B,它重新实现了类 A 中一个公共方法。
运行时,脚本会生成如下的一条错误信息:
相反,在类中定义的私有方法不是继承的,可以在扩展它的类中重新实现。例如,类 B 在下面的脚本中扩展了类 A,并重新实现了类 A 中一个私有方法。
在 PHP 8.0 之前,对扩展类中私有方法的重新声明应用了两个限制:不允许更改 final 和 static 修饰符。如果 private 方法被声明为 final ,则不允许扩展类重新声明该方法。如果私有方法被声明为静态的,那么它将在扩展类中保持静态。而且,如果私有方法没有static 修饰符,则不允许扩展类添加static 修饰符。在 PHP 8 中,这两个限制都被取消了。以下脚本在 PHP 8 中能正常运行。
PHP 8 中唯一的私有方法限制是强制使用 private final 构造函数,当使用静态工厂方法作为替代时,有时会使用private final来禁用构造函数。
该脚本生成如下的错误信息:
在 PHP 8 中,单个可变参数可以替换任意数量的函数参数。考虑下面的脚本,其中类 B 扩展了类 A,并用一个可变参数替换函数 sortArray 的三个参数。
可以使用多个参数调用类 B 中的 sortArray 函数。
输出结果如下所示:
可调用(callable)是可以被调用的 PHP 表达式,例如实例方法、静态方法或可调用对象。例如,可调用可用于为方法调用创建简短的表达式。在 PHP 8.1 中,可以用新的可调用语法:
AVariableCallableExpression(…)
AVariableCallableExpression 表示一个变量可调用表达式。省略号…包含在语法中。
这有两个问题:
- 语法涉及字符串和数组
- 在创建可调用时,作用域不会被维护。为了演示这一点,请考虑如下用于对数组进行排序的脚本,其中 getSortArrayMethod() 方法返回 sortArray() 方法的可调用项,[$this,\’sortArray\’] 。
该脚本会生成如下的错误信息:
使用 Closure::fromCallable([$this, \’sortArray\’]) 而不是 [$this, \’sortArray\’] 可以解决作用域问题,但使用 Closure::fromCallable 方法会使调用变得冗长。新的可调用语法解决了作用域和语法冗长的问题。使用新的可调用语法,函数变为:
数组根据输出进行排序:
新语法可以与涉及字符串和数组的传统语法结合使用,以解决作用域问题。创建可调用的作用域将保持不变。
新的可调用语法也可以与静态方法一起使用,如下面的脚本所示,该脚本包含一个静态函数。
输出结果与之前的相同:
以下是调用方法的等效方法:
以下是调用静态方法的等效方法:
即使函数声明了形参,也可以使用新的可调用语法。
如果一个方法声明了任意参数,则必须使用它的参数来调用可调用对象。
简化的可调用语法可以用于任意的 PHP 可调用表达式。用于对象创建的 new 运算符不支持可调用语法,因为可调用语法 AVariableCallableExpression(…) 没有指定构造函数参数的规定,这可能是必需的。以下是不支持的示例:
生成的错误信息为:
以下的脚本演示了受支持的所有可调用表达式。
PHP 8.0 的另一个新特性是支持在函数的参数列表末尾添加一个尾逗号,以提高可读性。任何尾逗号都将被忽略。尾逗号可能并不总是有用的,但如果参数列表很长,或者参数名称很长,则可能会有用,因此可以垂直列出它们。闭包使用列表也支持尾逗号。
PHP 8.0 不支持在必选参数之前声明可选参数。在必选参数之前声明的可选参数都是隐式的必选参数。
下面的脚本演示了必选参数的隐式顺序,以及尾逗号的使用。
输出如下所示:
可空参数不会被视为可选参数,可以使用 $param=null 形式或显式可空类型在必选参数之前声明,脚本如下所示:
PHP 8.0 除了已经支持的位置形参和实参之外,还增加了对命名函数形参和实参的支持。命名参数在函数调用中的传递语法如下所示:
命名参数的一些好处如下所示:
- 可以为函数参数指定一个有意义的名称,使它们能够自我记录
- 按名称传递时,参数与顺序无关
- 可以任意跳过默认值。在下面的脚本中, array_hashtable 函数声明了命名参数。 该函数传递的实参值可以带参数名,也可以不带参数名。当传递位置实参时,使用函数形参声明顺序。但传递命名实参时,可以使用任意顺序。
输出结果为:
命名实参和位置实参可以在同一函数调用中使用。对相同的示例函数 array_hashtable 一起使用混合参数调用。
输出结果为:
请注意,命名参数只能用于位置参数之后。下面的脚本颠倒了顺序,在命名参数之后使用位置参数:
该脚本生成的错误信息为:
即使使用命名参数,也不推荐在必选参数之前声明可选参数,脚本如下所示:
输出将包括已弃用(不推荐)信息:
当在必选命名形参之后使用可选命名形参时,命名实参可用于跳过函数调用中的一个或多个可选形参,脚本如下所示:
输出结果为:
你可以只使用可选参数的子集来调用函数,而不用考虑它们的顺序。
输出结果如下所示:
即使使用可选参数的子集调用函数,也不能在命名参数之后使用位置参数,脚本如下所示:
生成的错误信息以下所示:
PHP 8.1 改进了命名实参特性,在解包实参后支持命名实参,脚本如下所示:
输出如下所示:
但是,命名的参数不能盖前面的参数,脚本如下所示:
输出如下所示:
在 PHP 8.0 之前,如果在静态上下文中调用非静态方法,或者静态调用,则只会收到一条已弃用(不推荐)的信息。使用 8.0,你现在会收到一条错误信息。此外, $this 在静态上下文中是未定义的。为了演示这一点,请考虑如下的脚本,其中使用静态语法 A::aNonStaticMethod() 调用了非静态方法 aNonStaticMethod() 。
如果你运行这个脚本,将会得到如下的错误信息:
PHP 8.1 添加了对纤程(Fiber)多任务的支持。纤程是一个可中断的函数,它具有自己的堆栈。纤程可以从调用堆栈中的任何位置挂起,然后再恢复。新的 Fiber 类是一个 final 类,它支持以下的公共方法:
纤程只能启动一次,但可以挂起并恢复多次。下面的脚本通过使用纤程在数组上执行不同类型的排序来演示多任务处理。纤程在每次排序后都会挂起,然后再恢复执行不同类型的排序。
输出如下所示:
如果纤程在第一次挂起后没有再恢复,则只进行一种类型的排序,这可以通过注释掉对 resume() 的两次调用来实现。
输出的是第一次排序的结果:
PHP 8.0 引入了一个名为 Stringable 的新接口,它只提供一个方法 __toString() 。 __toString() 方法如果在类中提供,将隐式实现 Stringable 接口。考虑提供 __toString() 方法的类 A。
该脚本从 Stringable 的类型检查中返回 1。
然而,反之则不然。如果类实现了 Stringable 接口,则必须显式提供 __toString() 方法,因为该方法不会自动添加,比如:
PHP 8 引入了许多属于其标准库的新函数。
str_contains 函数返回一个 bool 值,用于指示作为第一个参数的字符串是否包含作为第二个参数的字符串。以下脚本将返回 false :
下面的脚本返回 1,或 true:
str_starts_with 函数返回一个bool 值 ,指示作为第一个参数的字符串是否以作为第二个参数的字符串开头。以下脚本将返回 false 。
下面的脚本将返回 1,或 true。
str_ends_with 函数返回一个bool 值 ,指示作为第一个参数的字符串是否以作为第二个参数的字符串结尾。以下脚本将返回 false 。
下面的脚本将返回 1,或 true。
fdiv 函数将两个数字相除并返回一个 float 值,脚本如下所示:
输出为:
fdatasync 函数在 Windows 上的别名为 fsync ,用于将数据同步到文件上的流中。为了演示它的用法,在包含要运行的 PHP 脚本的脚本目录中创建一个空文件 test.txt 。运行脚本:
随后,打开 test.txt 文件会发现包含如下的文本:
array_is_list 函数返回布尔值,用于指示给定的数组是否为列表。数组必须从 0 开始,键必须是连续的整数,并且顺序正确。下面的脚本演示了 array_is_list 函数:
输出为:
魔术方法是 PHP 中用于覆盖默认操作的特殊方法。它们包括如下的方法,其中构造函数 __construct() 可能是大家最熟悉的:
从 PHP 8.0 开始,魔术方法定义的签名必须要是正确的,这意味着如果在方法参数或返回类型中使用类型声明,则它们必须与文档中的声明相同。新的 __toString() 方法的返回类型必须要声明为 string 。下面的演示将返回类型声明为 int :
将生成如下的错误信息:
但是,未通过定义声明返回类型的函数(如构造函数)不能声明返回类型,即使是 void 返回类型也不行。示例如下脚本所示:
该脚本将返回如下的错误信息:
所有魔术方法,除了少数例外(例如 __construct() )外,都必须声明为具有公共可见性。为了演示这一点,声明了一个带有 private 可见性的 __callStatic 。
输出的警告信息为:
尽管可以省略混合返回类型,但方法签名也必须相同。例如,在下面的脚本中,类 A 声明了 __callStatic 而没有指定其返回类型,而类 B 将其第一个参数定义为int :
输出的错误信息如下所示:
在 PHP 8.1 中,大多数内部方法,即内部类中的方法,都已经“试探性地”开始声明返回类型。试探性地暗示,虽然在 8.1 中只会引发不推荐(Deprecation)通知,但在 9.0 版中,则会输出错误条件信息。因此,任何扩展类都必须声明与内部类相兼容的返回类型,否则将会引发已弃用(不推荐)通知。为了演示这一点,扩展内部类 Directory 并重新声明没有返回类型的函数 read() :
该脚本将生成已弃用(不推荐)通知:
但是,以下脚本是可以的:
添加 #[\\ReturnTypeWillChange] 属性能抑制已弃用(不推荐)通知:
虽然包含有关方法参数的详细信息的异常堆栈跟踪对调试非常有用,但你可能不希望输出某些敏感参数的参数值,例如与密码和凭据关联的参数值。PHP 8.2 引入了一个名为 \\SensitiveParameter 的新属性,这样,如果使用 \\SensitivyParameter 属性注解方法的参数,则该参数的值不会在异常堆栈跟踪中输出。
为了演示这一点,考虑下面的脚本,其中函数 f1 具有与 \\SensitiveParameter 属性关联的参数 $password 。
为了演示 \\SensitiveParameter 特性,该函数只是抛出一个异常。调用函数:
请注意,异常堆栈跟踪不包含 $password 参数的值,而是列出了 Object(SensitiveParameterValue) 。
内置函数 utf8_encode() 和 utf8_decode() 经常被误解,因为它们的名称意味着对任何字符串进行编码/解码。实际上,这些函数仅用于编码/解码 ISO8859-1,即“Latin-1”字符串。此外,它们生成的错误信息对于调试来说描述性不够。PHP 8.2 已经弃用了这些函数。下面的脚本使用了它们:
对于 PHP 8.2,会输出已弃用(不推荐)通知:
在 PHP 8.2 中,函数 iterator_count 和 iterator_to_array 接受所有可迭代的对象。 iterator_to_array() 函数将迭代器的元素复制到数组中。 iterator_count() 函数对数组的元素进行计数。这些函数接受一个 $iterator 作为第一个参数。在 PHP 8.2 中,$iterator 参数的类型已从 Traversable 扩展为 Traversable|array ,以便接受任意的可迭代值。
下面的脚本演示了它们在 arrays 和 Traversables 中的使用。
输出如下所示:
在这篇 PHP 8 系列文章中,我们讨论了与函数和方法相关的新特性,其中最突出的是命名函数的形参/实参、简化的可调用语法和被称为纤程(Fiber)的可中断函数。
在本系列的下一篇文章中,我们将介绍 PHP 类型系统的新特性。
原文链接:
https://www.infoq.com/articles/php8-functions-methods/
相关阅读:
PHP 8:注解、match 表达式及其他改进
PHP 8:类和枚
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。