Home >Backend Development >PHP Tutorial >Writing PHP Extension 1: Introduction to PHP and Zend
Translation: I lost it in the world
Original address:http://devzone.zend.com/303/extension-writing-part-i -introduction-to-php-and-zend/
Blog address: http://lg.uuhonghe.com/index/view?id=3
Introduction
What is extension
Life cycle
Hello World建 Create its own extension
Ini configuration
global variables
Set ini for global variables
Integral check
and then miles?
Introduction
If you are reading this tutorial, you may be interested in writing extensions in the PHP language. if not. . . Maybe after reading it you will find that you are interested in something you didn't know before.
This article assumes that readers have a basic understanding of the PHP language and the PHP interpreter written in C.
Let’s first confirm why you want to write a PHP extension:
1. Due to the depth of abstraction of the language core, you have some libraries and system calls that cannot be completed directly using PHP.
2. You want PHP to implement its own behavior in some unusual ways.
3. You’ve written a bunch of PHP code, but you know it can run faster.
4. You have code that implements a particularly clever idea that you want to sell, but more importantly, the code you want to sell must be able to run but cannot be seen in the source code.
These are all very valid reasons, but to create an extension, you first have to understand what an extension is.
What is extension?
If you have ever written PHP, then you must have used extensions. With just a few extensions, all user-space functionality in PHP is in the function group of this or that extension. A large number of these functions are part of the standard extensions - more than 400 in total. There are 86 extensions bundled in the PHP source code, each with an average of about 30 functions. By counting, there are about 2500 functions in total. If that's not enough, there are over 100 additional extensions available in the PECL repository
, and more can be found elsewhere online.“These functions are all in extensions, so what else?” You may ask, “What do they extend? What is the core of PHP?”.
The core of PHP consists of two parts. At the bottom you can find the Zend Engine (ZE for short). ZE parses human-recognizable scripts into machine-recognized symbols and runs these symbols in the process space. ZE handles memory management, variable fields and function calls simultaneously. Another part of this distinction is the PHP core. The PHP kernel handles communications, connections, and the SAPI layer (Server Application Programming Interface, often also used to refer to host environments such as Apache, IIS, CLI, CGI
etc.), and also provides unified detection of safe_mode and open_basedir in the control layer, as well as a streaming layer related to the user space functions fopen(), fread() and fwrite() for file and network I/O.
Life Cycle
When a SAPI is started, such as in the response of /usr/local/apache/bin/apachectl start
, PHP starts by initializing its kernel subsystem. At the end of this boot process, each extension's kernel is loaded and their module initialization routine (MINIT) is called. This gives each extension a chance to initialize internal variables, allocate resources, register resource handles, and register its functions with ZE so that when the script calls these functions, ZE knows which piece of code to run.
Next, PHP waits for the SAPI layer to request a page to process. In the case of CGI or CLI SAPI, this happens directly and only once. In Apache, IIS or some other mature web server SAPI, this happens when the remote user requests it, and may happen multiple times, possibly with concurrency. Regardless of how the request arrives, PHP starts by telling ZE to set up an environment for the script to run, and then calls each extension's request initialization (RINI
RINIGives extensions a chance to set up their own specific environment variables, allocate requests for specific resources, or perform other tasks such as auditing. RINIA prime example of functional behavior is in sessions In the extension, if the session.auto_start item is enabled, RINI will automatically trigger the user space session_start() function and preset the $_SESSION variable. Once the request is initialized, ZE takes over by translating the PHP script into tokens and finally into opcodes, which can be debugged and executed in a single step.
When an extension method contained in one of the opcodes is called, ZE will bundle the parameters of the method and temporarily hand over control to the direct method completion.
After the script is completed, PHP calls each extended request shutdown (RSHUTDOWN) function to do the final cleanup work (such as ensuring that session variables are saved to disk). Next, ZE runs a cleanup process (known as garbage collection), which effectively performs the unset() operation on every variable used in the previous request.
Operation complete Finally, PHP waits for SAPI to request another document or for the signal to close. In the case of CGI and CLI SAPI, there is no "next request", so the SAPI starts the shutdown process directly. During the shutdown process, PHP goes through each extension again, calls the module shutdown (MSHUTDOWN) function, and finally shuts down its own kernel subsystem.
The above may sound intimidating, but as you start to develop a working extension, some of it will gradually become clear.
Memory Management
To prevent poorly written extended memory loss, ZE implements its internal memory manager with an additional flag indicating persistence. Continuous allocation is important to ensure that memory allocations last longer than a page request. In contrast, a non-persistent allocation is released at the end of the request for which it was allocated, regardless of whether the release function is called. For example, user space variables are allocated non-persistently when they are no longer used after the request ends.
However, perhaps an extension would theoretically rely on ZE to automatically release non-persistent memory at the end of the page request, which is not recommended. Memory allocation will give a longer period of unrecycling, memory-related resources are unlikely to be closed at the right time, and no cleanup work will make this work a mess. As you will see later, it is a simple matter to ensure that the allocated data is cleaned up in time. Let's briefly compare traditional memory allocation (must be used when using external libraries) and persistence in PHP/ZE. and unpersistent memory allocation.
Traditional | Non-Persistent | Persistent |
---|---|---|
*
**
Building a Development Environment
Now that we have learned some of the theory behind how PHP and Zend Engine work, I guess you want to get started developing. Then before you start, you must gather some necessary work to set up a development environment that meets your needs.
First, you need PHP itself, because this series of development tools are inseparable from PHP. If you are not familiar with using source code to build PHP, I recommend you read this article first: http://www.php.net/install.unix.
(The article on developing PHP extensions using Windows will be given later) . Although it is very tempting to use the binary packages included in your Linux distribution to be safe, but these will miss two ./configure options that are very convenient during the development process. The first one is
Any program introduction that has not completed the Hello World application is incomplete. Therefore, below we will make an extension of only one function that returns the string "Hello World". In PHP code you may write like this: <?php
function hello_word(){
return 'Hello World';
}
?>
Now we use PHP extension to implement this code. First, we create the directory
in the ext/ directory of the PHP source code and enter the directory. In fact, this directory can be in or out of the PHP directory tree, but I let you put it here so that I will demonstrate an unrelated concept later. Three files need to be created in this directory: a source file that contains the
hello_world
method, a header file that contains references to let PHP load your extension, and a header file that lets phpize do the work for compiling the extension. Prepare the configuration file. PHP_ARG_ENABLE(hello, whether to enable Hello World support, [--enable-hello Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_FUNCTION(hello_world);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif
hello.c
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_hello.h" static function_entry hello_functions[] = { PHP_FE(hello_world, NULL) {NULL, NULL, NULL} }; zend_module_entry hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_HELLO_WORLD_EXTNAME, hello_functions, NULL, NULL, NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_HELLO_WORLD_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_HELLO ZEND_GET_MODULE(hello) #endif PHP_FUNCTION(hello_world) { RETURN_STRING("Hello World", 1); }It can be seen that most of the code in the above example extension is glue-protocol language. used to introduce extensions to PHP and establish a conversation for them to communicate. Only the last 4 lines of code can be called "real code" and are used to perform tasks that user space layer scripts can interact with. Indeed, the code at this level looks very similar to the PHP code we looked at before and is easy to understand:
1、声明一个方法
2、让这个方法返回一个字符串:"Hello World"
3、。。呃。。。1?这个1几个意思?
回忆一下,ZE包含了一套复杂的内存管理层,能确保分配的资源在脚本退出时被释放。然而在内存管理的掌控下,释放同样的块两次是非常大的禁忌。这种行为通常称为"double freeing",是一个常见的段错误的原因,涉及到一个正在调用的程序访问一个不再属于它的内存块。同样的,你不希望允许ZE去释放一个存活于程序空间并且其数据块被其他进程占用的静态字符串buffer(例如我们示例中的"Hello World")。
PHP_FUNCTION(hello_world) { char *str; str = estrup("hello World"); RETURN_STRING(str, 0); }
在此版本中,我们手动地分配内存给"Hello World"这个串,并且最终传回调用脚本,然后把内存传给
编译你的扩展
本练习的最后一步就是将你的扩展编译为一个动态可加载模块。如果你把上面的示例原封不动的抄下来,那么只需要在ext/hello/目录下运行下面三步命令就行:
phpize ./configure --enable-hello (译者注:如果编译PHP的时候使用了 --prefix 参数,此处要加上 --with-php-config 选项, 如笔者编译PHP时使用的是 ./configure --prefix=/use/local/phpdev/ 此处命令应使用 ./configure --enable-hello --with-php-c/local/phpdev/bin/php-config) make
运行完上述三个命令之后,你应该在ext/hello/modules/下找到一个 hello.so 文件。(译者注:如果在make时报错: error: unknown type name 'function_entry' ,可以把 'function_entry' 改为 'zend_function_entry',参见:https://bugs.php.net/bug.php?id=61479
)。现在,就像其他PHP扩展一样,你可以把你的扩展拷贝到扩展目录(默认为,/usr/local/lib/php/extensions/,可以通过php.ini确认)然后在php.ini里加上extension=hello.so一行可以在以触发它在程序启动时被加载到了。对于CGI/CLI SAPIs 来说,启动就指下一次运行PHP;而对我web server SAPIs如Apache来说,启动指下次web server重启。让我们试下运行下面命令:
$ php -r 'echo hello_world();'
如果一切顺利,你现在应该能看到这段代码输出"Hello World"了,因为你的扩展里的"hello_world()"返回了一个字符串"Hello World",而echo命令会原封不动地显示传递给他的参数(此处即为该函数的返回值)。
其他标量也可用类似的方式返回,使用
static function_entry hello_functions[] = { PHP_FE(hello_world, NULL) PHP_FE(hello_long, NULL) PHP_FE(hello_double, NULL) PHP_FE(hello_bool, NULL) PHP_FE(hello_null, NULL) {NULL, NULL, NULL} }; PHP_FUNCTION(hello_long) { RETURN_LONG(42); } PHP_FUNCTION(hello_double) { RETURN_DOUBLE(3.1415926535); } PHP_FUNCTION(hello_bool) { RETURN_BOOL(1); } PHP_FUNCTION(hello_null) { RETURN_NULL(); }
你同样需要在头文件php_hello.h里
PHP_FUNCTION(hello_world); PHP_FUNCTION(hello_long); PHP_FUNCTION(hello_double); PHP_FUNCTION(hello_bool); PHP_FUNCTION(hello_null);
因为你没有修改config.m4文件,所以技术上这次跳过phpize和./configure这两个步骤直接make是安全的。然后,在本游戏的这个阶段,我还是要求你从头把三个步骤都执行一遍以确认活干得漂亮。另外,最后一步的时候,你应该执行make clean all而不是简单的执行make,来确保所有源文件重建。再次声明,现在的改动上述这些步骤是非必须的,但是会更安全更清晰。模块建好后,再拷贝到你的扩展目录下,替换旧版本。
这个时候你可以再次调用你的PHP解析器,运行一段简单的脚本来试试你刚刚添加的方法。事实上,为何不现在不试呢?我等着呢。。。
试完了?很好。如果你使用
PHP_FUNCTION(hello_bool){ RETURN_TRUE; }
注意这里没有使用括号哦。
你也许注意到上面这些代码样品我们都没有传0和1什来表示这些值是否需要被拷贝。这是因为没有额外的内存(变量容器之外——我们将在第2部分深入)需要被分配或释放,因为这些标量都很简单很小。
还有另外三种返回类型:
INI Settings
Zend 引擎提供了两个方式处理
现在我们想在php.ini中定义一个变量,"hello.greeting", 用来处理你在"hello_function()"函数里用来打招呼的变量。你需要在hello.c和php_hello.h中添另一些东西,并且
PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_FUNCTION(hello_world); PHP_FUNCTION(hello_long); PHP_FUNCTION(hello_double); PHP_FUNCTION(hello_bool); PHP_FUNCTION(hello_null);
现在到hello.c中用下面这串代码覆盖当前版本的
zend_module_entry hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_HELLO_WORLD_EXTNAME, hello_functions, PHP_MINIT(hello), PHP_MSHUTDOWN(hello), NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_HELLO_WORLD_VERSION, #endif STANDARD_MODULE_PROPERTIES }; PHP_INI_BEGIN() PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL) PHP_INI_END() PHP_MINIT_FUNCTION(hello) { REGISTER_INI_ENTRIES(); return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(hello) { UNREGISTER_INI_ENTRIES(); return SUCCESS; }
现在,你只需要在hello.c顶部剩下的
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h "#include "php_hello.h"
最后,我们修改
PHP_FUNCTION(hello_world) { RETURN_STRING(INI_STR("hello.greeting"), 1); }
注意,你复制了来自
首次修改的部分包含了两个你需要熟悉的方法:
在你的方法hellow_world()中使用"INI_STR()"来取回当前"hello.greeting"项的值作为一个字符串。那些其他的已有方法取值作为长整型,浮点型和布尔型,如下表所示,"ORIG"补充了其他方法,能提供从
Current Value | Original Value | Type |
signed long | ||
signed double | ||
The first parameter passed into
The second parameter is the initial value, and is usually taken as a char* string whether it is a number or not. This is mainly because the values in the .ini file actually default to text - it is a text file. You can use
The third parameter passed in is an access mode modifier. This is a bitmask used to determine when and where
We will skip the fourth parameter and just click on this value to pass in a callback method to be called whenever the ini configuration is modified, such as when using
Global variables
通常,扩展需要在特定的请求里跟踪变量的值,使之独立于并发请求。在无线程的SAPI中这可能比较简单:只需要在源文件中声明一个全局变量,在需要时调用。然而麻烦在于PHP被设计运行于线程级的web服务器(如Apache 2 和 IIS),这就需要保证一个线程中的全局变量与其他线程中的分离。PHP通过使用TSRM(Thread Safe Resource Management)抽象层,大大地简化了这一操作,有时被称为ZTS(Zend Thread Safety)。实际 上,到现在你已经使用了一部分TSRM了,虽然你对其不甚了解。(不要急着去搜索,通过这一系列的进展,你会发现它无处不在。)
创建线程安全全局变量的第一步,跟创建其他全局变量一样,声明它。为了实现这个例子,你将声明一个以
#ifdef ZTS #include "TSRM.h" #endif ZEND_BEGIN_MODULE_GLOBALS(hello) login counter; ZEND_END_MODULE_GLOBALS(hello) #ifdef ZTS #define HELLO_G(v) TSRM(hello_globals_id, zend_hello_globals *, v) #else #define HELLO_G(v) (hello_globals.v) #endif
这次你还将用到
PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_RINIT_FUNCTION(hello);
现在 到hello.c中添加下面这段代码到include 模块后:
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "php_hello.h" ZEND_DECLARE_MODULE_GLOBALS(hello)
修改
zend_module_entry hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_HELLO_WORLD_EXTNAME, hello_functions, PHP_MINIT(hello), PHP_MSHUTDOWN(hello), PHP_RINIT(hello), NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_HELLO_WORLD_VERSION, #endif STANDARD_MODULE_PROPERTIES };
修改你的
static void php_hello_init_globals(zend_hello_globals *hello_globals) { } PHP_RINIT_FUNCTION(hello) { HELLO_G(counter) = 0; return SUCCESS; } PHP_MINIT_FUNCTION(hello) { ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL); REGISTER_INI_ENTRIES(); return SUCCESS; }
最后,修改你的
PHP_FUNCTION(hello_long) { HELLO_G(counter)++; RETURN_LONG(HELLO_G(counter)); }
在添加到php_hello.h的代码里,使用了一对宏
INI设置 vs 全局变量
如果你回观前文,一个在
ZEND_BEGIN_MODULE_GLOBAL(hello) login counter; zend_bool direction; ZEND_ENG_MODULE_GLOBALS(hello)
然后,通过修改
PHP_INI_BEGIN() PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL) STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals) PHP_INI_END()
现在在
static void php_hello_init_globals(zend_hello_globals *hello_globals) { hello_globals->direction = 1; }
最后,在hello_long()中使用这个配置的值来决定是自增还是自减:
PHP_FUNCTION(hello_long) { if (HELLO_G(direction)) { HELLO_G(counter)++; } else { HELLO_G(counter)--; } RETURN_LONG(HELLO_G(counter)); }
就是这样,我们在
完整性检查
到现在,我们的三个文件里的内容应该如下所列的(为了可阅读性,一些条目被移到了一起)。
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support, [ --enable-hello Enable Hello World support]) if test "$PHP_HELLO" = "yes"; then AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World]) PHP_NEW_EXTENSION(hello, hello.c, $ext_shared) fi
php_hello.h
#ifndef PHP_HELLO_H #define PHP_HELLO_H 1 #ifdef ZTS #include "TSRM.h" #endif ZEND_BEGIN_MODULE_GLOBALS(hello) long counter; zend_bool direction; ZEND_END_MODULE_GLOBALS(hello) #ifdef ZTS #define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v) #else #define HELLO_G(v) (hello_globals.v) #endif #define PHP_HELLO_WORLD_VERSION "1.0" #define PHP_HELLO_WORLD_EXTNAME "hello" PHP_MINIT_FUNCTION(hello); PHP_MSHUTDOWN_FUNCTION(hello); PHP_RINIT_FUNCTION(hello); PHP_FUNCTION(hello_world); PHP_FUNCTION(hello_long); PHP_FUNCTION(hello_double); PHP_FUNCTION(hello_bool); PHP_FUNCTION(hello_null); extern zend_module_entry hello_module_entry; #define phpext_hello_ptr &hello_module_entry #endif
hello.c
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "php_hello.h" ZEND_DECLARE_MODULE_GLOBALS(hello) static function_entry hello_functions[] = { PHP_FE(hello_world, NULL) PHP_FE(hello_long, NULL) PHP_FE(hello_double, NULL) PHP_FE(hello_bool, NULL) PHP_FE(hello_null, NULL) {NULL, NULL, NULL} }; zend_module_entry hello_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_HELLO_WORLD_EXTNAME, hello_functions, PHP_MINIT(hello), PHP_MSHUTDOWN(hello), PHP_RINIT(hello), NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_HELLO_WORLD_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_HELLO ZEND_GET_MODULE(hello) #endif PHP_INI_BEGIN() PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL) STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals) PHP_INI_END() static void php_hello_init_globals(zend_hello_globals *hello_globals) { hello_globals->direction = 1; } PHP_RINIT_FUNCTION(hello) { HELLO_G(counter) = 0; return SUCCESS; } PHP_MINIT_FUNCTION(hello) { ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL); REGISTER_INI_ENTRIES(); return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(hello) { UNREGISTER_INI_ENTRIES(); return SUCCESS; } PHP_FUNCTION(hello_world) { RETURN_STRING("Hello World", 1); } PHP_FUNCTION(hello_long) { if (HELLO_G(direction)) { HELLO_G(counter)++; } else { HELLO_G(counter)--; } RETURN_LONG(HELLO_G(counter)); } PHP_FUNCTION(hello_double) { RETURN_DOUBLE(3.1415926535); } PHP_FUNCTION(hello_bool) { RETURN_BOOL(1); } PHP_FUNCTION(hello_null) { RETURN_NULL(); }
接下来做什么?
在这教程中我们开发了一个简单的PHP扩展,导出方法,返回值,声明了
下一章我们研究PHP变量的内核结构,以及变量如何存储、跟踪和在脚本环境中修改。当一个函数被调用时我们使用