PHP7扩展开发教程[2] – 怎样定义依赖扩展与INI配置项

确认你已经读完上一章《PHP7扩展开发教程[1] – 怎样导出一个模块?》。

扩展依赖

假设我们的扩展需要用到curl扩展,那么这就产生了一个依赖关系,也就是myext扩展依赖了curl扩展。

如果php没有安装curl扩展,那么myext扩展就应该立即报错,php提供了这个检查能力。同时,依赖是传递的,所以如果curl扩展依赖了sockets扩展,那么myext扩展就间接依赖了sockets扩展。

这里存在一个误区,是myext扩展直接在C语言层调用了curl扩展的符号,还是myext扩展通过Zend解释器层调用了curl扩展的导出方法,是完全不同的2个概念,非常容易混淆,这里的”扩展依赖”,指的是后者。

下面的斜体内容是选读。

先说说C语言层的调用:我们知道curl.so和myext.so都是php扩展,php在运行时通过dlopen加载so,并通过dlsym获取与执行get_module方法,从而令扩展有机会将C函数注册到Zend引擎中成为一个PHP函数。因此,在curl.so中的C语言符号(函数,全局变量等)如果没有通过显式的向Zend注册,是不可能在myext.so中可见的。产生误导的地方是,PHP支持将扩展代码随PHP源码一起编译到php二进制中,这就导致一些扩展(例如curl)的C语言符号(函数之类)在php进程中全局可见,因而导致myext.so扩展在被php通过dlopen加载后可以访问到curl扩展中的函数符号。

当然,如果curl扩展是独立编译成so,我们的myext可以在编译时直接链接curl.so,同样可以访问到curl.so中的符号,只是多了一个运行时动态库依赖,通常我们不会这么做。

所以,正确的做法要么是将依赖的扩展随PHP一起编译到二进制中,从而可以调用依赖扩展内的任意全局符号;要么通过Zend引擎层调用依赖扩展导出的PHP符号。

这里定义了一个扩展依赖数组,standard模块是我们依赖的一个必须(MODULE_DEP_REQUIRED)存在的扩展,它要么随PHP源码一起编译在了二进制中,要么作为独立的standard.so并配置在php.ini中,否则php启动时会报错,因为缺少myext扩展的依赖扩展。

最后一个字段有一些可选值(必须存在,必须不存在否则冲突,可选):

zend在传递数组时通常不指定长度,而是通过一个全空的元素作为结尾标识。为了导出这个依赖,我们在get_module返回module定义之前将其填充到deps字段里即可。

具体你可以探索一下Zend/zend_modules.h文件。

INI配置项

通常扩展都支持ini配置,为了在扩展中使用必须提前向Zend注册。下面我希望可以支持在php.ini中配置一个ini项如下:

myext.github = “https://github.com/owenliang/php7-extension-explore”

一个ini项由name和default value组成,我宏定义如下:

接着,需要定义一个zend_ini_entry_def数组来列举我有哪些ini项目:

和扩展依赖deps一样,也是空元素结尾。第一项是我要定义的ini项目,通常我们只需要填写name、value、modifiable,name_length,value_length字段。

name是ini配置项的名称,value是其默认值(可以传NULL)。

modifiable控制谁有资格修改这个ini配置项,它的枚举值是:

USER是允许用户通过ini_set修改,PERDIR和SYSTEM可以理解为允许php.ini覆盖,所以无特殊需求用ZEND_INI_ALL即可。

其他字段一般是用不到的,比如on_modify是ini修改的时候回调(比如用户ini_set修改其值)、displayer是展示的时候回调处理。

仅仅定义ini还不够,最后要注册到Zend中去。注册环节需要用到module_number,也就是模块的ID,所以我们最早注册ini的时机只能在module_startup的时候:

只需将ini配置数组和module_number传入即可。

当你这样批量注册ini时,如果某个ini项已经存在会导致myext模块注册的所有ini被卸载,所以稳妥的话还是建议判断一下返回值是否为SUCCESS。

一个新注册的INI项的value是我们传入的default value,当php从php.ini中加载或者用户调用ini_set覆盖这个全新的INI项时,我们的default_value将会保存到ini_entry的一个备份字段中。zend_ini_string函数的最后一个参数比较重要,如果为0则表示总是读取ini的最新value,而如果为1则读取ini的默认值,也就是我们定义ini时传入的default_value。

接下来,我们需要获取最新ini数据的时候可以这样操作(以extension_before_request函数为例):

给zend_ini_string传入了ini的名称(还记得之前的那个宏定义吧)与长度,即可获得一个value字符串指针,如果没有对应的ini配置则返回NULL。返回值的有效期并不好控制,因为稍后回到php脚本执行时用户可能通过ini_set修改其value,所以如果你希望保存返回值,应该保留其内容副本。

ini相关的函数定义在Zend/zend_ini.h中。

结语

本章你应该掌握:

  1. 定义扩展依赖,以便帮用户快速找到错误。
  2. 定义INI,以便可以通过php.ini文件控制扩展行为。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~