PHP 扩展概览
概述
PHP 的强大生态很大程度上得益于其丰富的扩展体系。PHP 扩展(Extension)是用 C/C++ 编写的动态链接库,通过扩展 PHP 的核心功能来提供额外的能力。从数据库驱动到性能分析,从协程框架到加密算法,PHP 扩展覆盖了几乎所有的应用场景。了解 PHP 扩展的类型、加载机制和开发方法,有助于深入理解 PHP 的运行原理,以及在需要时开发自定义扩展来满足特定需求。
前置知识
阅读本节前,建议先了解 OPcache 配置详解 和 PECL 扩展管理。
基础概念
扩展的分类
PHP 扩展按其接入方式主要分为两类:
- PHP 扩展(Standard Extensions):通过
extension=指令加载,实现具体的 PHP 函数 - Zend 扩展(Zend Extensions):通过
zend_extension=指令加载,可以修改 Zend 引擎的核心行为
PHP 扩展加载层次:
┌─────────────────────────┐
│ 用户 PHP 代码 │
├─────────────────────────┤
│ PHP 函数/API 层 │ ← PHP 扩展注册函数
├─────────────────────────┤
│ Zend 引擎层 │ ← Zend 扩展修改引擎行为
├─────────────────────────┤
│ 操作系统层 │
└─────────────────────────┘扩展生命周期
PHP 扩展有四个生命周期阶段:
- MINIT(Module Init):PHP 进程启动时执行一次
- RINIT(Request Init):每个请求开始时执行
- RSHUTDOWN(Request Shutdown):每个请求结束时执行
- MSHUTDOWN(Module Shutdown):PHP 进程退出时执行
详细说明
PHP 扩展 vs Zend 扩展
| 特性 | PHP 扩展 | Zend 扩展 |
|---|---|---|
| 加载方式 | extension=xxx.so | zend_extension=xxx.so |
| 注册方式 | 注册 PHP 函数 | 注册 Zend Hook |
| 加载时机 | 在 Zend 引擎启动后 | 在 Zend 引擎启动前 |
| 功能范围 | 提供新函数 | 修改引擎行为 |
| 典型代表 | mysqli、gd、json | OPcache、Xdebug |
| 数量 | 数百个 | 极少数 |
常用扩展分类
核心扩展(内置)
bash
# 查看已加载的核心扩展
php -m| 分类 | 扩展 | 说明 |
|---|---|---|
| 字符串/数组 | SPL、JSON、CBOR | 数据处理 |
| 数学 | bcmath、GMP | 高精度计算 |
| 加密 | OpenSSL、Sodium | 安全相关 |
| 数据库 | PDO、SQLite3 | 数据存储 |
| 网络 | sockets、stream | 网络通信 |
| 进程 | PCNTL、POSIX | 进程控制 |
| 文件系统 | FileInfo、intl | 文件处理 |
| 日期时间 | DateTime | 时间处理 |
PECL 扩展
bash
# 列出可用的 PECL 扩展
pecl search <keyword>| 分类 | 扩展 | 说明 |
|---|---|---|
| 性能 | OPcache、APCu | 缓存加速 |
| 调试 | Xdebug | 开发调试 |
| 异步 | Swoole、ReactPHP | 高并发 |
| 并行 | parallel | 多线程 |
| 搜索 | redis、mongodb | NoSQL 驱动 |
| 监控 | tideways | 性能分析 |
扩展加载机制
加载顺序
ini
; php.ini 中的扩展加载顺序很重要
; 1. Zend 扩展必须先加载(修改引擎行为)
zend_extension=opcache.so
zend_extension=xdebug.so
; 2. PHP 扩展在之后加载
extension=mysqli.so
extension=pdo_mysql.so
extension=redis.so
extension=json.so加载顺序
Zend 扩展必须在 PHP 扩展之前加载。如果有多个 Zend 扩展,它们的加载顺序也很重要(如 OPcache 通常应在 Xdebug 之前)。
按目录加载
bash
# PHP 扩展目录
php-config --extension-dir
# 查看扩展搜索路径
php -i | grep "extension_dir"
# 扫描目录自动加载
# php.ini 中:
; extension_dir = "/usr/lib/php/20210902"
; 所有 .so 文件会被自动加载运行时加载
php
<?php
declare(strict_types=1);
// 使用 dl() 动态加载扩展(不推荐)
// dl('redis.so'); // PHP 7.0+ 已在 CLI 中移除
// 更好的方式:通过 php.ini 配置
// extension=redis.so
// 检查扩展是否已加载
if (!extension_loaded('redis')) {
throw new RuntimeException('Redis 扩展未安装');
}
// 获取扩展版本
echo phpversion('redis'); // e.g., 6.0.2
// 获取扩展函数列表
$functions = get_extension_funcs('redis');扩展开发工具
PHP-CPP
PHP-CPP 是一个 C++ 库,极大简化了 PHP 扩展的开发:
cpp
// my_extension.cpp
#include <phpcpp.h>
// 定义 PHP 函数
Php::Value hello_world(Php::Parameters ¶ms)
{
return "Hello, " + params[0].stringValue() + "!";
}
// 导出函数
extern "C" {
PHPCPP_EXPORT void *get_module()
{
static Php::Extension extension("my_extension", "1.0");
extension.add<hello_world>("hello_world", {
Php::ByVal("name", Php::Type::String, true)
});
return extension;
}
}bash
# 编译 PHP-CPP 扩展
g++ -std=c++11 -fPIC -I/usr/include/php/20210902 \
my_extension.cpp -o my_extension.so -lphpcpp
# 安装
sudo cp my_extension.so /usr/lib/php/20210902/
echo "extension=my_extension.so" >> /etc/php/8.1/mods-available/my_extension.ini
phpenmod my_extension纯 C 扩展开发
c
/* simple_ext.c */
#include "php.h"
/* 定义 PHP 函数 */
PHP_FUNCTION(simple_add)
{
long a, b;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &a, &b) == FAILURE) {
RETURN_NULL();
}
RETURN_LONG(a + b);
}
/* 函数参数信息 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_simple_add, 0, 0, 2)
ZEND_ARG_INFO(0, a)
ZEND_ARG_INFO(0, b)
ZEND_END_ARG_INFO()
/* 函数注册表 */
static const zend_function_entry simple_ext_functions[] = {
PHP_FE(simple_add, arginfo_simple_add)
PHP_FE_END
};
/* 模块信息 */
PHP_MINFO_FUNCTION(simple_ext)
{
php_info_print_table_start();
php_info_print_table_header(2, "Simple Extension", "enabled");
php_info_print_table_row(2, "Version", "1.0.0");
php_info_print_table_end();
}
/* 模块注册 */
zend_module_entry simple_ext_module_entry = {
STANDARD_MODULE_HEADER,
"simple_ext",
simple_ext_functions,
NULL, /* MINIT */
NULL, /* MSHUTDOWN */
NULL, /* RINIT */
NULL, /* RSHUTDOWN */
PHP_MINFO(simple_ext),
"1.0.0",
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_SIMPLE_EXT
ZEND_GET_MODULE(simple_ext)
#endifbash
# 编译配置
phpize
./configure --enable-simple-ext
make
sudo make installconfig.m4 模板
# config.m4
PHP_ARG_ENABLE(simple_ext, whether to enable simple_ext support,
[ --enable-simple_ext Enable simple_ext support])
if test "$PHP_SIMPLE_EXT" != "no"; then
PHP_NEW_EXTENSION(simple_ext, simple_ext.c, $ext_shared)
fi实战示例
扩展信息查询工具
php
<?php
declare(strict_types=1);
/**
* PHP 扩展信息查询工具
*/
class ExtensionInfoTool
{
/**
* 获取所有扩展信息
*/
public static function getAllExtensions(): array
{
$extensions = get_loaded_extensions();
$info = [];
foreach ($extensions as $ext) {
$info[$ext] = [
'version' => phpversion($ext) ?: 'unknown',
'type' => self::getExtensionType($ext),
'functions' => get_extension_funcs($ext) ?: [],
];
}
return $info;
}
/**
* 获取扩展类型
*/
private static function getExtensionType(string $name): string
{
$zendExtensions = ['opcache', 'xdebug', 'Zend OPcache'];
return in_array(strtolower($name), $zendExtensions) ? 'Zend' : 'PHP';
}
/**
* 检查扩展依赖
*/
public static function checkDependencies(array $required): array
{
$missing = [];
foreach ($required as $ext => $minVersion) {
if (!extension_loaded($ext)) {
$missing[$ext] = '未安装';
} elseif ($minVersion !== null) {
$currentVersion = phpversion($ext);
if (version_compare($currentVersion, $minVersion, '<')) {
$missing[$ext] = "版本过低 ({$currentVersion} < {$minVersion})";
}
}
}
return $missing;
}
}
// 使用示例
$info = ExtensionInfoTool::getAllExtensions();
echo "已加载扩展: " . count($info) . " 个" . PHP_EOL;
echo PHP_EOL;
// 检查常用扩展
$required = [
'PDO' => null,
'pdo_mysql' => null,
'json' => null,
'curl' => null,
'mbstring' => null,
'openssl' => null,
'redis' => null,
];
$missing = ExtensionInfoTool::checkDependencies($required);
if (empty($missing)) {
echo "所有必需扩展已安装" . PHP_EOL;
} else {
echo "缺失扩展:" . PHP_EOL;
foreach ($missing as $ext => $reason) {
echo " {$ext}: {$reason}" . PHP_EOL;
}
}扩展加载性能分析
php
<?php
declare(strict_types=1);
/**
* 扩展加载性能分析
* 对比不同扩展组合对 PHP 启动时间的影响
*/
class ExtensionLoadBenchmark
{
/**
* 测试 PHP 启动时间
*/
public static function benchmarkStartup(): array
{
// 记录脚本开始时间(越早越好)
$startTime = hrtime(true);
// ... PHP 已自动加载所有扩展
$endTime = hrtime(true);
$totalMs = ($endTime - $startTime) / 1_000_000;
return [
'total_ms' => round($totalMs, 3),
'extensions' => count(get_loaded_extensions()),
];
}
/**
* 生成扩展加载报告
*/
public static function report(): string
{
$extensions = get_loaded_extensions();
sort($extensions);
$lines = [];
$lines[] = "# PHP 扩展加载报告";
$lines[] = "";
$lines[] = "PHP 版本: " . PHP_VERSION;
$lines[] = "已加载扩展: " . count($extensions);
$lines[] = "";
$lines[] = "| 类型 | 扩展名 | 版本 |";
$lines[] = "|------|--------|------|";
foreach ($extensions as $ext) {
$type = in_array($ext, ['Zend OPcache', 'xdebug']) ? 'Zend' : 'PHP';
$version = phpversion($ext) ?: '-';
$lines[] = "| {$type} | {$ext} | {$version} |";
}
return implode("\n", $lines);
}
}
echo ExtensionLoadBenchmark::report() . PHP_EOL;注意事项
扩展安全
- 来源可信:仅安装来自 PECL、操作系统包管理器或知名开源项目的扩展
- 最小权限:只安装必需的扩展,减少攻击面
- 及时更新:保持扩展版本更新
- 禁用危险函数:
dl()、eval()等
版本兼容性
PHP 8.1 扩展 API 编号: 20210902
PHP 8.2 扩展 API 编号: 20220829
PHP 8.3 扩展 API 编号: 20230831扩展 API 版本
每个 PHP 大版本有不同的扩展 API(ZEND_EXTENSION_API_NO)。为 PHP 8.1 编译的扩展不能在 PHP 8.2 上使用。升级 PHP 版本时需要重新编译所有扩展。
最佳实践
1. 生产环境扩展管理
ini
; 生产环境 - 最小化扩展
extension=pdo_mysql
extension=mbstring
extension=openssl
extension=curl
extension=json
; 仅在需要的 FPM pool 中加载调试扩展
; /etc/php/8.1/fpm/pool.d/debug.conf
; zend_extension=xdebug.so2. 容器化部署
dockerfile
# Dockerfile
FROM php:8.1-fpm-alpine
# 仅安装必需扩展
RUN docker-php-ext-install pdo_mysql mbstring
RUN pecl install redis && docker-php-ext-enable redis
# 不安装调试扩展
# 不安装 Xdebug下一节
继续学习:FFI 外部函数接口