PHP7扩展开发教程[11] – 如何引入php文件?

确保你已经阅读《PHP7扩展开发教程[10] – 如何使用资源类型?

这应该是本系列教程的最后一章,整个系列一共11章节,它们涉及了PHP7扩展开发的主要内容,我相信其意义于我与你都是一样的。

当然,在今后的工作中可能会遇到新的问题、新的知识点,这些潜在的新内容将会以扩展章节的方式补充上来,与本教程的11个章节区分开来。

在本章中,我将演示如何在PHP扩展中引入另外一个PHP文件,就像你在PHP中执行include或者include_once一样。

正式开始

本章代码:https://github.com/owenliang/php7-extension-explore/tree/master/course11-how-to-include-php-file

我增加了一个函数:

并在test.php中这样测试:

test_include方法模拟了include_once的实现,也就是只包含一次该文件,下面看一下实现过程。

test_include方法接收1个参数,是要引入的文件路径,可以是相对的或者绝对的文件路径。通过Zend的virtual_realpath方法,可以按照PHP的文件搜索规则得到绝对路径,如果路径不合法返回false。

接下来生成一个zend_string类型的文件路径,并在全局哈希表EG(included_files)中查找是否已经引入了该文件,如果引入我们就立即返回false。

接下来,我们创建一个zend_file_handle文件句柄,它的定义如下:

  • filename:const char *类型,文件名字符串。
  • free_filename:表示filename的内存是否需要帮助我们释放,这里不需要,因为我们直接引用了zend_string*里的内存,后面会主动释放它。
  • type:表示这个zend_file_handle仅仅填充了文件名,尚未真实打开文件。
  • opened_path:传NULL即可,是API内部使用的。
  • handle:保存底层文件指针的一个union,Zend内其实最终会用zend_stream stream,而zend_stream的第一个字段void *handle恰好与handle.fp共享内存。

准备好这个file handle后,传给zend_compile_file来编译这个文件,这个API内部会为zend_file_handle打开真实的文件,并编译PHP代码为opcode。第二个参数有几个可选值:

貌似ONCE并没有什么用,通常传ZEND_INCLUDE即可。

现在可以关闭文件句柄了:

接下来,我们在EG(included_files)中记录该文件已经加载:

这里value并不重要,只要文件路径作为key存在即可,因为zend_hash_add_xxx会给key增加1个引用计数,并且此后也不再使用filename,我们主动释放掉原先zend_string的1个引用计数。

现在,我们准备一个zval接收include的返回值,然后将之前zend_compile_file输出的opcode传入zend_execute执行代码:

opcode执行完成,可以释放其内部以及自身的内存:

最后,我们判定返回值是一个数组(test.php里return了1个array),并把这个返回值作为test_include的返回值:

注意,这里include_once的语义是我们自己实现的,我们完全可以不去查验EG(include_files),对同一个PHP文件进行多次的解释执行。

结语

通过本章,我们应该掌握:

  • 编译与执行php文件。
  • 确保php文件只引入一次。

 

 

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