解析PHP原生扩展开发

一、PHP扩展是什么

PHP 原生扩展(PHP Native Extension) ,在Web开发中,我们基于C/C++语言开发的对PHP语言的扩展,编译安装后供PHP使用。

在理解Extension扩展前,需要先对PHP运行机制,简单了解:

(示例图片来源)

  1. Zend引擎:纯C实现,它将PHP代码翻译(过滤符号、语法、词法)为可执行Opcode,并提供对应处理方法,实现了数据结构、内存管理,是PHP的核心

  2. Extensions:将功能组件与Zend引擎做解耦,开发人员以组件化方式编写扩展,丰富PHP使用场景,比如Curl、GD、Mysql等

  3. SAPI:服务应用编程接口,通过一系列钩子函数,使得PHP与外围交互数据(web、命令行)

  4. Application:平时编写的应用程式,比如web服务应用、命令行运行脚本

以Mysql扩展来讲:PHP本身是无法与Mysql(C/C++编写)通信,但建立一个PHP_Mysql扩展,可调用到Mysql的C函数,PHP应用层的开发者们,只需要借助简单的PHP函数,便可链接并操作Mysql数据库。

二、为什么要使用PHP扩展

场景一:借助第三方扩展PHP功能

如图所示,通过phpinfo()函数,可见当前环境下已安装的PHP扩展

可以把PHP扩展理解为bridge,桥接PHP和第三方程序。

注:PHP扩展无处不在:借助GD/imagick扩展对图片水印和裁剪,通过xdebug扩展跟踪、调试和分析PHP程序运行情况等。

场景二:编写高性能程式

面对复杂运算,PHP执行效率是很低的,因此我们可以将复杂逻辑放在扩展中处理。

口说无凭,实际测试一下,我们在环境中加入一个loop扩展,作用是接收两个参数,第一个参数是绘制符号,第二个参数是绘制符号的个数。

实测描述: 重复3000次绘制图,第一次输出o,第N次输出o*N,以此类推。

    /**
     * 测试c扩展和PHP性能,循环五千次,输出符号
     * loop扩展:两个参数,第一个为符号,第二个为个数
     * PHP原生:应用for循环实现符号输出
     */
    public function testExtensionPerforms(){
        //C扩展输出符号
        $loop_start_time = microtime(true);
        for ($i = 1; $i <= 3000; $i++){
            print loop("o", $i);
        }
        $loop_end_time = microtime(true);
        echo "loop扩展耗时:".($loop_end_time - $loop_start_time)."<br>";

        //PHP输出符号
        $php_start_time = microtime(true);
        for ($i = 1; $i <= 3000; $i++){
            for ($j = 1; $j <= $i; $j++){
                $k = $k ? $k.'o' : 'o';
            }
            print $k."\n";
            $k = '';
        }
        $php_end_time = microtime(true);
        echo " PHP执行耗时:".($php_end_time-$php_start_time);
    }
复制代码
纯PHP执行时间 扩展loop执行时间
1.952s 0.057s

在此场景下,扩展相对于PHP代码,有近35倍提升! 如果涉及递归逻辑,扩展会有数百倍提升。

值得一提的是,在IO瓶颈场景下,PHP扩展也束手无策,因为其耗时主要在于通信交互和等待(题外话:可以借助到协程解决此问题)

为什么PHP原生扩展,会比PHP实现的程序快呢?

PHP是弱类型编译型语言,在执行时需要经过一系列字符过滤、词法分析、语法分析,再转为Opcode,供机器逐条执行。PHP原生扩展是C语言实现,绕过了中间解析流程,因此效率有极大的提升。

场景三:基于安全考量,编写加密程式

常见密码加密场景中,我们会用到的是md5、sha等主流加密策略,但存在安全隐患(鸽巢理论),可以通过扩展实现特殊的加密逻辑。假使PHP和.so文件泄露,加密逻辑也不会被外人知晓。

除了代码本身的加密逻辑,我们也可以通过扩展对PHP文件做加密。范例

三、动手实现PHP扩展

通过上面介绍,相信大家已经对PHP原生扩展有了基本概念,接下来,我们亲自实现一则扩展。此扩展作用是封装了href标签,支持传入url。

3.1 配置好PHP开发环境

可用MAMP快速搭建环境,同时支持多版本PHP切换。

3.2 下载对应版本的PHP源码

由于MAMP的PHP源码,缺少扩展文件和C头文件,需要重新下载,我选用的是PHP-7.1.33版本。 传送门

进入到PHP源码ext目录下,每个文件夹对应一个扩展,其中有一个ext_skel文件,是PHP官方提供的快速构建扩展的工具。

3.3 一键生成扩展基础文件

执行:./ext_skel --extname=lovehk

  • config.m4、config.w32 配置文件
  • php_lovehk.h 声明一个函数供PHP调用
  • lovehk.c 用C实现扩展函数
  • lovehk.php 扩展测试文件
  • test/ 扩展测试文件夹
3.4 修改扩展配置config.m4

进入ext/lovehk目录,去掉10、11、12行,作用是标识此扩展没有用到其他扩展或lib库

3.5 修改php_lovehk.h,声明一个函数

在文件中加入一行:PHP_FUNCTION(lovehk)

3.6 修改lovehk.c,声明并实现一个函数lovehk

找到PHP_MINFO_FUNCTION方法,加入署名扩展:
php_info_print_table_row(3, "author", "DJ")

PHP_MINFO_FUNCTION(lovehk)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "lovehk support", "enabled");
	php_info_print_table_row(2, "author", "DJ"); /* Replace with your name */
	php_info_print_table_end();
}
复制代码

找到lovehk_functions结构体加入函数声明:PHP_FE(lovehk, NULL)

const zend_function_entry lovehk_functions[] = {
	PHP_FE(confirm_lovehk_compiled,	NULL)		/* For testing, remove later. */
	PHP_FE(lovehk, NULL) 
	PHP_FE_END	/* Must be the last line in lovehk_functions[] */
};
复制代码

找到PHP_FUNCTION(confirm_lovehk_compiled)函数,在下面实现lovehk我们的扩展方法

PHP_FUNCTION(lovehk)
{
	char *str = NULL;
	int argc = ZEND_NUM_ARGS();
	int str_len;
	char *result;

	if (zend_parse_parameters(argc TSRMLS_CC, "s", &str, &str_len) == FAILURE) 
		return;

	str_len = spprintf(&result, 0, "<a href="%.78s"></a>", str);
	RETURN_STRINGL(result, str_len);
}
复制代码

注:PHP7.0+,RETURN_STRINGL只返回两个参数,否则会编译报错

3.7 编译安装

在lovehk目录下,依次执行:

  • phpize
  • ./configure
  • sudo make && sudo make install

注:多环境下,需要执行编译环境 ./configure -with-php-config=/usr/local/php5/bin/php-config

如果一切顺利,会生成lovehk.so动态库,mac会自动迁移到MAMP对应版本PHP目录下!

3.8 修改php.ini,完成新增扩展

打开对应版本php.ini文件,增加extension=lovehk.so 打印phpinfo()函数,一切正常~

32.gif

四、借助Zephir自动生成扩展

Zephir:是一种中间语言,以接近PHP的语法来编写代码,然后转换编译成 PHP扩展,旨在简化PHP扩展的创建和可维护性。利用编译来提高性能和资源消耗,又不需要关注内存管理等复杂操作。

简单来说,就是借助这款工具,我们可以用PHP语法实现功能,再由Zephir转为.so动态库,作为扩展供PHP调用。

那么,你肯定会疑惑:既然这么方便,直接把PHP全转为扩展不就好了? 答案是否定的,原因如下:

  1. 鉴于PHP扩展的场景,我们前面有提到运算复杂、桥接第三方、以及加密安全领域可使用。
  2. 通过Zephir转为的扩展,对于复杂的业务逻辑,或者多个扩展协同场景,会有很多冗余代码,从业务发展和迭代考量,PHP优势明显
  3. PHP优势在于易于实现业务,以及丰富的Composer扩展可用,同时借助新版PHP的JIT即时编译和协程,让PHP在效率方面也有质的提升。

关于Zephir介绍,可参考这篇,就不展开了。

五、总结

文本从PHP运行机制切入,介绍了PHP扩展的价值和应用场景,并通过实例,为大家演示如何做扩展开发。同时简要分析了PHP扩展的优劣势,为大家在技术选择上提供些许思路,适合的才是最好的。

参考资料:

PHP内核与原生扩展开发 中文翻译

PHP扩展开发指南 Leon2021

PHP运行机制和原理 传送门

PHP扩展开发Zephir简介 乌啦啦

PHP文件加密扩展 lihancong

猜你喜欢

转载自juejin.im/post/7036991318991749128