[Related learning recommendations: php programming (video)]
You already know How to compile PHP itself, next we will compile external extensions. We will discuss the extended build process and available compilation options.
As you already know in the previous chapter, PHP extensions can be built into either static libraries or dynamic libraries (.so
). Most static libraries are compiled bundled with PHP, and dynamic libraries can explicitly pass the parameter --enable-EXTNAME=shared
or --with-EXTNAME=shared
to ./configure
.
Static extensions are available by default, and dynamic libraries need to add extension or zend_extension ini configuration. Both can be absolute paths or relative paths.
For example, the configuration items for compiling a PHP extension project:
~/php-src> ./configure --prefix=$HOME/myphp --enable-debug --enable-maintainer-zts --enable-opcache --with-gmp=shared
In this example, both the opcache extension and the GMP extension are compiled into shared objects located in the modules/
directory. You can load it by changing extension_dir
or by passing the absolute path:
~/php-src> sapi/cli/php -dzend_extension=`pwd`/modules/opcache.so -dextension=`pwd`/modules/gmp.so # or ~/php-src> sapi/cli/php -dextension_dir=`pwd`/modules -dzend_extension=opcache.so -dextension=gmp.so
In the make install
step, these two .so
files It will be moved into the extension directory of the PHP installation. You may be able to find it using the php-config --extension-dir
command. For the build options above, it would be /home/myuser/myphp/lib/php/extensions/no-debug-non-zts-MODULE_API
. This value is also the default value for the extension_dir
configuration option, so you can load the extension directly without specifying it explicitly:
~/myphp> bin/php -dzend_extension=opcache.so -dextension=gmp.so
This leaves us with a question: should you use Which mechanism? Shared objects allow you to have a base PHP binary and load additional extensions via php.ini. Distros take advantage of this functionality both through the original PHP package and by distributing extensions as separate packages. On the other hand, if you compile your own PHP binaries, you probably don't need this since you already know which extensions are required.
As a rule of thumb, you will use static linking for extensions bundled with PHP itself, and use shared extensions elsewhere. The reason is simple, as you'll see later, it's easier (or at least less intrusive) to build external extensions to shared objects. Another benefit is that you can update extensions without rebuilding PHP.
NOTE
If you need information about the differences between extensions and Zend extensions, you can consult the dedicated section.
PECL, PHP extension community library, provides a large number of PHP extensions. When extensions are removed from the main PHP distribution, they are usually still in PECL. Likewise, many of the extensions now bundled with PHP were formerly PECL extensions.
Unless you specify --without-pear
in the configuration step of the PHP build, make install
will download and install PECL as part of PEAR. You can find the pecl
script in the $PREFIX/bin
directory. Now installing an extension is as simple as running pecl install EXTNAME
, for example:
~/myphp> bin/pecl install apcu
This command will download, compile and install the APCu extension. The result will be a apcu.so
file in the extension directory, which can be loaded by passing the extension=apcu.so
configuration option.
Although pecl install
is very convenient for end users, it has little interest among extension developers. Below, we explain two ways to build an extension manually: by importing it into the main PHP source tree (allowing static linking) or by building it externally (sharing only).
There is no fundamental difference between third-party extensions and extensions bundled with PHP. So you can copy the external extension into the PHP source tree and build it as usual. We use APCu as an example to demonstrate.
First, you need to put the extension source code into the ext/EXTNAME
directory of the PHP source tree. If the extension is available via Git, it's as simple as cloning the repository from ext/
:
~/php-src/ext> git clone https://github.com/krakjoe/apcu.git
Alternatively you can download the source tarball and unzip it:
/tmp> wget http://pecl.php.net/get/apcu-4.0.2.tgz /tmp> tar xzf apcu-4.0.2.tgz /tmp> mkdir ~/php-src/ext/apcu /tmp> cp -r apcu-4.0.2/. ~/php-src/ext/apcu
The extension will contain a config.m4
file that specifies the specific extension build instructions used by the autoconf file. In order to include them in the /configure
script, you must run ./buildconf
again. To ensure that the configuration file has been regenerated, it is recommended to delete it beforehand:
~/php-src> rm configure && ./buildconf --force
Now you can use the ./config.nice
script to add APCu to your existing configuration, or from a fresh Configuration line starts:
~/php-src> ./config.nice --enable-apcu # or ~/php-src> ./configure --enable-apcu # --other-options
最后,运行 make -jN
执行实际的构建。由于我们没有使用 --enable-apcu=shared
,该扩展已经静态链接到 PHP 库,即不需要额外的操作即可使用它。显然,你也可以使用 make install
去安装最后的二进制文件。
phpize
构建扩展还可以通过使用构建 PHP章节提及到的 phpize
脚本与 PHP 分开构建。
phpize
的作用与 ./buildconf
用于 PHP 构建的脚本相似:第一,通过$PREFIX/lib/php/build
复制文件导入 PHP 构建系统到你的扩展中。这些文件是 acinclude.m4
(PHP 的 M4宏)、phpize.m4
(它会在你的扩展中重命名为 configure.in
并包含主要的构建说明)和 run-tests.php
。
然后 phpize
将调用 autoconf 生成 ./configure
文件,该文件可以自定义扩展构建。注意,没必要传递 --enable-apcu
给它,因为这是隐式假定的。相反,你应该使用 --with-php-config
指定你的 php-config
脚本路径:
/tmp/apcu-4.0.2> ~/myphp/bin/phpize Configuring for: PHP Api Version: 20121113 Zend Module Api No: 20121113 Zend Extension Api No: 220121113 /tmp/apcu-4.0.2> ./configure --with-php-config=$HOME/myphp/bin/php-config /tmp/apcu-4.0.2> make -jN && make install
当你构建扩展时,你应该总是指定 --with-php-config
选项(除非你只有一个全局的 PHP 安装),否则 ./configure
无法确定要构建的 PHP 版本和标志。指定 php-config
脚本也确保了 make install
将移动生成的 .so
文件(可以在 modules/
目录找到)到正确的扩展目录。
由于在 phpize
阶段还复制了 run-tests.php
文件,因此你可以使用 make test
(或显示调用 run-tests)运行扩展测试。
删除已编译对象的 make clean
也是可用的,并且允许你增量构建失败时强制重新构建扩展。 另外 phpize 提供了一个清理选项 phpize --clean
。该命令将删除所有 phpize
导入的文件和通过 /configure
脚本生成的文件。
PHP CLI 二进制文件提供了几个选项来显示关于扩展的信息。你已经知道 -m
,该命令会列出所有已经下载的扩展。你可以利用它来确定扩展是否正确下载了:
~/myphp/bin> ./php -dextension=apcu.so -m | grep apcu apcu
还有其他一些以 --r
开头的参数都是具有 Reflection 功能。例如,你可以使用 --ri
去显示扩展的配置:
~/myphp/bin> ./php -dextension=apcu.so --ri apcu apcu APCu Support => disabled Version => 4.0.2 APCu Debugging => Disabled MMAP Support => Enabled MMAP File Mask => Serialization Support => broken Revision => $Revision: 328290 $ Build Date => Jan 1 2014 16:40:00 Directive => Local Value => Master Value apc.enabled => On => On apc.shm_segments => 1 => 1 apc.shm_size => 32M => 32M apc.entries_hint => 4096 => 4096 apc.gc_ttl => 3600 => 3600 apc.ttl => 0 => 0 # ...
--re
参数列出扩展添加的所有初始设置、常数、函数和类:
~/myphp/bin> ./php -dextension=apcu.so --re apcu Extension [ <persistent> extension #27 apcu version 4.0.2 ] { - INI { Entry [ apc.enabled <SYSTEM> ] Current = '1' } Entry [ apc.shm_segments <SYSTEM> ] Current = '1' } # ... } - Constants [1] { Constant [ boolean APCU_APC_FULL_BC ] { 1 } } - Functions { Function [ <internal:apcu> function apcu_cache_info ] { - Parameters [2] { Parameter #0 [ <optional> $type ] Parameter #1 [ <optional> $limited ] } } # ... } }
--re
参数仅适用普通扩展,Zend 扩展使用 --rz
代替。 你可以在 opcache 上尝试:
~/myphp/bin> ./php -dzend_extension=opcache.so --rz "Zend OPcache" Zend Extension [ Zend OPcache 7.0.3-dev Copyright (c) 1999-2013 by Zend Technologies <http://www.zend.com/> ]
如你所见, 该命令没有显示有用的信息。因为 opcache 同时注册了普通扩展和 Zend 扩展, 前者包含所有初始配置、常量和函数。因此在这个特殊的案例中,你仍然需要使用 --re
。其他 Zend 扩展通过 --rz
可得到信息。
扩展对5个主要因素非常敏感。如果它们不合适,则该扩展将不会加载到 PHP中,并将无用:
- PHP Api 版本
- Zend 模块 Api 编号
- Zend 扩展 Api 编号
- 调试模式
- 线程安全
phpize 工具可让你回想它们的一些信息。所以,如果你在调试模式下构建 PHP,并试图加载和使用非调试模式构建的扩展,那它将无法工作。其他检查也一样。
PHP Api 版本 是内部 API 版本号,Zend 模块 Api 编号 和 Zend 扩展 Api 编号 分别与 PHP 扩展和 Zend 扩展 API 有关。
那些编号随后作为 C 宏传递给正在构建的扩展,以便它本身可以检查那些参数,并在 C 预处理器 #ifdef
的基础上采用不同的代码路径。当那些编号作为宏传给扩展代码,它们会被写在扩展结构中,以便你每次尝试在 PHP 二进制文件中加载该扩展时,都将对照 PHP 二进制文件本身的编号进行检查。如果不匹配,那么该扩展不会被加载,并显示一条错误信息。
如果我们看一下扩展的 C 结构,它看起来像这样:
zend_module_entry foo_module_entry = { STANDARD_MODULE_HEADER, "foo", foo_functions, PHP_MINIT(foo), PHP_MSHUTDOWN(foo), NULL, NULL, PHP_MINFO(foo), PHP_FOO_VERSION, STANDARD_MODULE_PROPERTIES };
至今,对我们来说有趣的是 STANDARD_MODULE_HEADER
宏。如果我们扩展它,我们可以看到:
#define STANDARD_MODULE_HEADER_EX sizeof(zend_module_entry), ZEND_MODULE_API_NO, ZEND_DEBUG, USING_ZTS #define STANDARD_MODULE_HEADER STANDARD_MODULE_HEADER_EX, NULL, NULL
注意 ZEND_MODULE_API_NO
、ZEND_DEBUG
、 USING_ZTS
是如何使用的。
如果查看 PHP 扩展的默认目录,它应该像 no-debug-non-zts-20090626
。如你所料,该目录由不同的部分组成:调试模式,其次是线程安全信息,然后是Zend 模块 Api 编号。所以默认情况下,PHP 试图帮你浏览扩展。
注意
通常,当你成为一位内部开发人员或扩展开发人员,必须使用调试参数,并且如果必须处理 Windows 平台,线程也会显示出来。你可以针对那些参数的多种情况多次编译同一扩展。
记住,每次新的 PHP 主要/次要版本都会更改参数,比如 PHP Api 版本,这就是为什么你需要针对新的 PHP 版本重新编译的原因。
> /path/to/php70/bin/phpize -v Configuring for: PHP Api Version: 20151012 Zend Module Api No: 20151012 Zend Extension Api No: 320151012 > /path/to/php71/bin/phpize -v Configuring for: PHP Api Version: 20160303 Zend Module Api No: 20160303 Zend Extension Api No: 320160303 > /path/to/php56/bin/phpize -v Configuring for: PHP Api Version: 20131106 Zend Module Api No: 20131226 Zend Extension Api No: 220131226
注意
Zend 模块 Api 编号 本身是使用 年 月 日 的日期格式构建。这是 API 更改和并被标记的日期。Zend 扩展 Api 编号 是 Zend 版本,其次是 Zend 模块 Api 编号。
注意
数字太多?是的,一个 API 编号绑定一个 PHP 版本,对任何人来说都足够了,并且可以简化对 PHP 的理解。不幸的是,除了 PHP 版本本身,还增加了3种不同的 API 编号。你应该找哪一个?答案是任何一个:当 PHP 版本演变时,它们三种同时演变。由于历史原因,我们有三种不同编号。
但是,你是一位 C开发人员,不是吗?为什么不根据这些数字构建一个“兼容的”头文件?我们在我们的扩展中使用了类似这些:
#include "php.h" #include "Zend/zend_extensions.h" #define PHP_5_5_X_API_NO 220121212 #define PHP_5_6_X_API_NO 220131226 #define PHP_7_0_X_API_NO 320151012 #define PHP_7_1_X_API_NO 320160303 #define PHP_7_2_X_API_NO 320160731 #define IS_PHP_72 ZEND_EXTENSION_API_NO == PHP_7_2_X_API_NO #define IS_AT_LEAST_PHP_72 ZEND_EXTENSION_API_NO >= PHP_7_2_X_API_NO #define IS_PHP_71 ZEND_EXTENSION_API_NO == PHP_7_1_X_API_NO #define IS_AT_LEAST_PHP_71 ZEND_EXTENSION_API_NO >= PHP_7_1_X_API_NO #define IS_PHP_70 ZEND_EXTENSION_API_NO == PHP_7_0_X_API_NO #define IS_AT_LEAST_PHP_70 ZEND_EXTENSION_API_NO >= PHP_7_0_X_API_NO #define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO #define IS_AT_LEAST_PHP_56 (ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO && ZEND_EXTENSION_API_NO < PHP_7_0_X_API_NO) #define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO #define IS_AT_LEAST_PHP_55 (ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO && ZEND_EXTENSION_API_NO < PHP_7_0_X_API_NO) #if ZEND_EXTENSION_API_NO >= PHP_7_0_X_API_NO #define IS_PHP_7 1 #define IS_PHP_5 0 #else #define IS_PHP_7 0 #define IS_PHP_5 1 #endif
看见了?
或者更简单(更好)的是使用 PHP_VERSION_ID
,这你可能更熟悉:
#if PHP_VERSION_ID >= 50600
想了解更多编程学习,敬请关注php培训栏目!
The above is the detailed content of How to compile PHP extensions. For more information, please follow other related articles on the PHP Chinese website!