构建PHP扩展

在了解了如何编译 PHP 本身之后,我们将开始编译其他扩展。我们将讨论编译过程是如何工作的,以及有哪些不同的选项。

加载共享扩展

如前所述,PHP 扩展既可以静态联入 PHP 二进制文件,也可以编译成共享对象(.so)。静态链接是大多数捆绑扩展的默认方式,而共享对象可以通过在 ./configure 中明确传递 --enable-EXTNAME=shared--with-EXTNAME=shared 来创建。

静态扩展总是可用的,而共享扩展则需要使用 extensionzend_extension ini 选项加载。这两个选项要么使用 .so 文件的绝对路径,要么使用 extension_dir 设置的相对路径。

举例来说,使用以下配置行编译 PHP 代码:

~/php-src> ./configure --prefix=$HOME/myphp \
                       --enable-debug --enable-maintainer-zts \
                       --enable-opcache --with-gmp=shared

在这种情况下,opcache 扩展和 GMP 扩展都被编译成共享对象,位于 modules/ 目录下。您可以通过更改 extension_dir 或传递绝对路径来加载这两个扩展:

~/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

# or (since PHP 7.2 the .so is optional)
~/php-src> sapi/cli/php -dextension_dir=`pwd`/modules \
                        -dzend_extension=opcache -dextension=gmp

make install 步骤中,两个 .so 文件都将被移入 PHP 安装的扩展目录,可以使用 php-config --extension-dir 命令找到该目录。在上述构建选项中,该目录将是 /home/myuser/myphp/lib/php/extensions/no-debug-non-zts-MODULE_API。该值也将是 extension_dir ini 选项的默认值,因此无需明确指定,可以直接加载扩展:

~/myphp> bin/php -dzend_extension=opcache -dextension=gmp

这就给我们留下了一个问题: 应该使用哪种机制?共享对象允许你拥有一个基本的 PHP 二进制文件,并通过 php.ini 加载额外的扩展。发行版就是利用了这一点,提供了一个基本的 PHP 包,并将扩展作为单独的包发行。另一方面,如果你正在编译自己的 PHP 二进制文件,你可能不需要这样做,因为你已经知道自己需要哪些扩展。

根据经验,PHP 本身捆绑的扩展会使用静态链接,而其他扩展则使用共享链接。原因很简单,将外部扩展构建为共享对象更容易(或至少不那么麻烦),稍后就会明白。另一个好处是可以更新扩展而无需重建 PHP。

如果你需要了解扩展和 Zend 扩展之间的区别, 可以参阅专门章节

从PECL安装扩展

PECL 是 PHP 扩展社区库,为 PHP 提供了大量扩展。当扩展从主要的 PHP 发行版中移除时,它们通常会继续存在于 PECL 中。同样,现在与 PHP 捆绑的许多扩展以前也是 PECL 扩展。

如果在构建 PHP 的配置阶段指定了 --with-pearmake install 将下载并安装作为 PEAR 一部分的 PECL。您可以在 $PREFIX/bin 目录中找到 pecl 脚本。现在,安装扩展只需运行 pecl install EXTNAME 即可,例如:

~/myphp> bin/pecl install apcu

该命令将下载、编译并安装 APCu 扩展。其结果是在扩展目录下生成 apcu.so 文件,然后通过 extension=apcu ini 选项加载该文件。

虽然 pecl install 对于最终用户来说非常方便,但对于扩展开发人员来说却并不重要。下面,我们将介绍两种手动构建扩展的方法: 一种是将其导入主 PHP 源代码树(允许静态链接),另一种是进行外部编译(只能共享)。

增加扩展到PHP源码树

第三方扩展与捆绑在 PHP 中的扩展没有本质区别。因此,只需将外部扩展复制到 PHP 源代码树中,然后使用通常的构建过程,就可以构建外部扩展。我们将以 APCu 为例进行演示。

首先,您需要将扩展的源代码放到 PHP 源代码树的 ext/EXTNAME 目录中。如果扩展是通过 git 提供的,则只需从 ext/ 目录中克隆源代码即可:

~/php-src/ext> git clone https://github.com/krakjoe/apcu.git

或者,您也可以下载源代码压缩包并解压缩:

/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

扩展将包含一个 config.m4 文件,其中指定了供 autoconf 使用的特定扩展编译指令。要将其纳入 ./configure 脚本,必须再次运行 ./buildconf。为确保 configure 文件真正重新生成,建议事先将其删除:

~/php-src> rm configure && ./buildconf

现在,您可以使用 ./config.nice 脚本将 APCu 添加到现有配置中,或使用全新的 configure 行重新开始:

~/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 的作用与用于 PHP 构建的 ./buildconf 脚本类似: 首先,它将从 $PREFIX/lib/php/build 中复制文件,将 PHP 编译系统导入到扩展中。这些文件包括 php.m4(PHP 的 M4 宏)、phpize.m4(在扩展中将更名为 configure.ac,包含主要的构建说明)和 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/ 目录中找到)移动到正确的扩展目录。

由于 run-tests.php 文件也在 phpize 阶段被复制,因此可以使用 make test(或明确调用 run-tests.php)运行扩展测试。

make clean 目标也可用于删除已编译的对象,如果增量构建在更改后失败,它允许你强制重建扩展。此外,phpize 还提供了 phpize --clean 清理选项。这将删除 phpize 导入的所有文件,以及 ./configure 脚本生成的文件。

显示关于扩展的信息

PHP CLI 二进制程序提供了几个选项来显示有关扩展的信息。您已经知道 -m,它会列出所有已加载的扩展。您可以用它来验证扩展是否已正确加载:

~/myphp/bin> ./php -dextension=apcu -m | grep apcu
apcu

还有几种以 --r 开头的开关可以显示 Reflection 功能。例如,你可以使用 --ri 来显示扩展的配置:

~/myphp/bin> ./php -dextension=apcu --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 会列出扩展添加的所有 ini 设置、常量、函数和类:

~/myphp/bin> ./php -dextension=apcu --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 --rz "Zend OPcache"
Zend Extension [ Zend OPcache 7.0.3-dev Copyright (c) 1999-2013 by Zend Technologies <http://www.zend.com/> ]

正如你所看到的,这不会显示任何有用的信息。原因是 opcache 注册了普通扩展和 Zend 扩展,前者包含所有 ini 设置、常量和函数。因此,在这种特殊情况下,你仍然需要使用 --re。 不过,其他 Zend 扩展可以通过 --rz 获取信息。