PHP 扩展——从入门到理解

从"为什么要装扩展"到"框架底层到底干了啥",一篇全讲清楚。


一、先把"扩展"和"服务"分清楚

这两个词长得像,但完全是两码事:

扩展(php_xxx.dll) 服务(xxx-server.exe)
是什么 C 代码编译好的二进制文件 一个独立运行的程序
能不能双击运行 ❌ 不能,是零件包 ✅ 能,是完整软件
角色 客户端桥梁,给 PHP 提供类/函数 服务端,在后台监听端口
例子 php_redis.dll redis-server.exe
装好后 PHP 里可以 new Redis() 命令行或服务面板里启动
类比 电话机(打电话的工具) 对方电话(接电话的人)

关键结论:扩展不包含服务端。 php_redis.dll 里没有 Redis 数据库,它只是一个"翻译官":

swift 复制代码
你写的 PHP 代码                .dll 扩展(翻译官)             服务端进程
─────────────────────────────────────────────────────────────────────────

$r = new Redis();     →    php_redis.dll             →    redis-server.exe
$r->get('key');            │                              │
                           │ 把 PHP 的 get()               │ 收到 GET key
                           │ 翻译成 Redis 协议:           │
                           │ *2\r\n$3\r\nGET\r\n           │ 返回 $5\r\nhello
                           │ $3\r\nkey\r\n           →    │
                           │                              │
                           │ 把 $5\r\nhello          ←────┘
                           │ 翻译成 PHP 字符串 "hello"
                           │
$r->get('key')  ←──────────┘ 返回 "hello"

扩展就是桥梁:连接 PHP 代码和外部服务,负责翻译指令和翻译返回值。


二、.dll 文件到底是个啥

它不是 C 语言的源代码,是编译好的机器码。你用记事本打开看到的是乱码(十六进制),因为它是给 CPU 读的,不是给人读的。

.c 源文件 .dll 文件
内容 人能读懂的 C 代码 CPU 能读懂的机器码
用途 开发、修改 直接用
依赖 需要 C 编译器 不需要,拿过来就能加载

.dll 不能被双击运行------它是个零件包,必须由 PHP/Apache 加载到内存里才能工作。

加载过程:

scss 复制代码
php.ini                    硬盘上的文件                  PHP 运行时内存
─────────────────────────────────────────────────────────────────────

extension=php_redis  ──→  PHP 去 ext/ 下找              ┌──────────────┐
                          php_redis.dll  ──→ 加载到内存  │ Zend 符号表   │
                          (二进制机器码)               │              │
                                                        │ Redis 类 ✓   │
                                                        │ connect()    │
                                                        │ get()        │
                                                        │ set()        │
                                                        │ hset()       │
                                                        │ ...          │
                                                        └──────────────┘
                                                              │
                                                              ▼
                                                      $r = new Redis();
                                                      // 可以用啦!

顺便说一句 :网上搜扩展的时候,搜"php redis"而不是"phpredis.dll"。因为 php.ini 里写 extension=php_redis,PHP 会在 Windows 下自动补 .dll,Linux 下自动补 .so


三、php.ini → 扩展 → 类,三步串起来

第 1 步:php.ini 里去掉分号

ini 复制代码
;extension=php_redis.dll    ← 分号 = 注释 = 不加载
extension=php_redis.dll     ← 去掉分号 = 告诉 PHP 去加载这个文件
extension_dir = "ext"       ← dll 文件在 ext 目录下

第 2 步:加载 .dll,向 Zend 引擎注册类

PHP 启动时读 php.ini,找到 extension=php_redis.dll,然后:

scss 复制代码
LoadLibrary("php_redis.dll")
    │
    └── dll 里有初始化代码(C 写的),执行时调用:
            zend_register_class("Redis")     ← 把 Redis 类写入全局符号表
            zend_register_class("RedisException")
            zend_register_function("...")
            ...

第 3 步:你的代码直接用

php 复制代码
$r = new Redis();           // 不需要 include,类已经在符号表里了
$r->connect('127.0.0.1', 6379);

class_exists('Redis') 就是在问:全局符号表里有这个类吗?有 → 扩展加载成功,可以 new;没有 → 没装扩展或没在 php.ini 里打开。


四、扩展分两类:要不要服务端?

类型 扩展 做的事 需要额外服务?
纯本地运算 php_gd2.dll 图片缩放、加水印 ❌ 不
php_gettext.dll 国际化翻译,读 .mo 文件 ❌ 不
php_mbstring.dll 多字节字符串处理 ❌ 不
带外部服务 php_redis.dll 连 Redis 数据库 ✅ 要装 Redis
php_mysqli.dll 连 MySQL 数据库 ✅ 要装 MySQL
php_pdo_mysql.dll 连 MySQL(PDO 方式) ✅ 要装 MySQL
php_curl.dll 发 HTTP 请求 ✅ 要有目标网址
php_memcache.dll 连 Memcached 缓存 ✅ 要装 Memcached

一句话判断:它做的事需要连别的程序吗?不需要 → 纯本地,没服务端。需要连数据库/远程服务 → 先把那个服务跑起来。


五、扩展的类 vs 你自己写的类

php 复制代码
// 你自己的类 ------ 定义在 .php 文件里
require 'User.php';    // 必须先 include,不然找不到
$u = new User();

// 扩展的类 ------ 定义在 C 写的 .dll 里
// 不需要 include,PHP 启动时就已经在全局符号表里了
$r = new Redis();      // 直接 new
$m = new mysqli();     // 直接 new
$c = curl_init();      // 直接调函数
自己写的类 扩展的类
源码语言 PHP C
编译 不需要(PHP 解释执行) 编译成 .dll 二进制
加载方式 require/include php.iniextension=
存放位置 你的项目目录 ext/ 目录
能做什么 PHP 能做的事 PHP 做不到的底层操作(TCP 通信、内存操作等)

但用起来写法一样 ------这就是面向对象的好处,new 不关心里面的实现是什么语言。


六、框架(ECShop / ThinkPHP)到底多做了什么?

不管你用什么框架,底层都是同一个扩展在干活。

MySQL 为例

php 复制代码
// 写法1:原生 mysqli
$mysqli = new mysqli('127.0.0.1', 'root', '123456', 'test');
$result = $mysqli->query('SELECT * FROM users WHERE id = 1');
$row = $result->fetch_assoc();

// 写法2:原生 PDO
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', '123456');
$stmt = $pdo->query('SELECT * FROM users WHERE id = 1');
$row = $stmt->fetch();

// 写法3:ECShop(你当前项目)
$row = $db->getRow("SELECT * FROM users WHERE id = 1");
// getRow 内部调用:mysql_query() + mysql_fetch_assoc()
// 查多行用 getAll(),查单个值用 getOne()

// 写法4:ThinkPHP
$row = Db::table('users')->where('id', 1)->find();
// 内部最终调用:pdo->query() + pdo->fetch()

四条路走到同一个终点:php_mysql.dllphp_pdo_mysql.dll → TCP 3306 端口 → MySQL 服务。

css 复制代码
你写的代码                框架封装层               PHP 扩展               MySQL 服务
────────────────────────────────────────────────────────────────────────────

Db::table('users')   →   ThinkPHP ORM        →   php_pdo_mysql.dll  →  MySQL
->where('id',1)->find()   翻译成 SELECT...        │                    (3306)
                                                 │  TCP 协议
$db->getRow($sql)    →   ECShop cls_mysql     →   php_mysql.dll      →  MySQL
                         拼 LIMIT 1,调           │                    (3306)
                         mysql_fetch_assoc()     │

框架只是包了一层,让代码更安全(防 SQL 注入)、更省事(不用手写连接和关闭)、更直观(链式调用)。换框架不等于换底层

PDO vs MySQLi 怎么选?

MySQLi PDO
支持数据库 只支持 MySQL 支持 12 种(MySQL、SQLite、PostgreSQL...)
写法 面向过程 + 面向对象 只有面向对象
预处理占位符 只能用 ? 可以用 ?:name
换数据库代价 代码全改 只改连接字符串

新项目优先选 PDO ,换数据库几乎不改代码。你的 ECShop 项目用的是更老的 mysql_* 扩展(PHP 7.0 已移除),所以必须在 PHP 5.x 上跑。


七、从浏览器到 PHP 执行,全链路

scss 复制代码
浏览器  ① GET /index.php
    │
    ▼
Apache   ② 收到请求,看后缀 .php → 交给 PHP 模块处理
    │     ③ 加载 php5_module.dll(Apache 和 PHP 的转接头)
    │
    ▼
PHP 引擎 ④ 启动(FastCGI 常驻则跳过)
    │     ⑤ 读 php.ini(路径在 httpd.conf 里配的 PHPIniDir)
    │     ⑥ 解析 extension=xxx 行
    │     ⑦ LoadLibrary("php_redis.dll")  → 注册 Redis 类
    │     ⑧ LoadLibrary("php_mysql.dll")  → 注册 mysql_* 函数
    │     ⑨ ...
    │     ⑩ 执行 index.php → $db->getRow(...)
    │              → cls_mysql::query()
    │              → mysql_query() → TCP 3306 → MySQL 服务
    │              → mysql_fetch_assoc() → 返回关联数组
    │
    ▼
 返回 HTML 给浏览器

关键配置文件:

ini 复制代码
httpd.conf
    ├── LoadModule php5_module "php5apache2_4.dll"    ← Apache 怎么认 .php
    └── PHPIniDir "G:/phpstudy_pro/.../php5.4.45nts"  ← php.ini 在哪

php.ini
    ├── extension_dir = "ext"                          ← .dll 文件放哪
    └── extension=php_redis.dll                        ← 加载哪个扩展

八、php.ini 常用配置速查

扩展相关

ini 复制代码
extension_dir = "ext"              ; 扩展文件目录
extension=php_redis.dll            ; 加载 Redis 扩展
extension=php_gd2.dll              ; 加载图片扩展
;extension=php_xdebug.dll          ; 分号开头 = 不加载

错误显示

ini 复制代码
; 开发环境
display_errors = On
error_reporting = E_ALL

; 生产环境
display_errors = Off
log_errors = On
error_log = "路径/php_error.log"

上传 & 内存

ini 复制代码
upload_max_filesize = 20M          ; 单个文件最大
post_max_size = 25M                ; POST 总大小
memory_limit = 128M                ; 脚本内存上限
max_execution_time = 30            ; 最长执行秒数(0 = 不限)

Session

ini 复制代码
session.save_handler = files       ; 存文件(默认),可改成 redis/memcached
session.gc_maxlifetime = 1440      ; 存活 1440 秒
session.cookie_lifetime = 0        ; 0 = 浏览器关闭即过期

其他

ini 复制代码
date.timezone = "Asia/Shanghai"    ; 时区(必配!)
default_charset = "UTF-8"          ; 字符集
expose_php = Off                   ; 隐藏 PHP 版本(生产环境安全)

改完配置后必须重启 Apache / PHP-FPM 才生效!


九、怎么装一个扩展(完整流程)

scss 复制代码
① 下载  →  找对的 .dll 文件(要匹配 PHP 版本 + nts/ts + x86/x64)
② 放置  →  放到 ext/ 目录下
③ 配置  →  php.ini 里加上 extension=php_xxx.dll(去分号)
④ 重启  →  重启 Apache 或 PHP-FPM
⑤ 验证  →  phpinfo() 或 class_exists('Redis') 确认加载成功

不会自动安装,每一步都要手动做。 php.ini 里的分号只是"开关",不是安装程序。


十、常见问题 FAQ

Q1:为什么 new Redis() 不需要 include?

扩展的类注册在 Zend 引擎的全局符号表里,整个 PHP 进程随时可见。就像 strlen() 不用 include 一样------它是 PHP 内置的。

Q2:装了扩展但连不上怎么办?

ini 复制代码
1. extension=php_redis.dll 在 php.ini 里去分号了没有?
2. Redis 服务端 redis-server.exe 启动了没有?
3. 端口 6379 在监听吗?  netstat -ano | findstr 6379
4. phpinfo() 里搜 redis,确认扩展加载成功

Q3:Zend 引擎是什么?

PHP 的"大脑"------负责把你的 PHP 代码编译成 opcode 然后一条条执行,同时维护全局符号表(所有函数、类、常量都在这里)。类比 Java 的 JVM。

Web 开发不需要深入研究源码 ,知道 php.ini → 扩展 → 类 → 能用 这条链就够了。

Q4:getRow、getAll、getOne 是 PHP 原生的吗?

不是。这些是框架/项目自己封装的 。比如 ECShop 的 cls_mysql.php 里定义了:

php 复制代码
getRow($sql)   → 内部调用 mysql_query() + mysql_fetch_assoc()(查一行)
getAll($sql)   → 内部循环 mysql_fetch_assoc() 取所有行(查多行)
getOne($sql)   → 内部调用 mysql_fetch_row()[0](查单个值)

不同框架起不同名字:ECShop 叫 getRow,ThinkPHP 叫 find,Laravel 叫 first()底层都是同一个扩展

Q5:为什么 PHP 不装扩展也能有些方式连 MySQL / Redis?

  • Redis :可以用纯 PHP 库(Composer 装 predis/predis),因为它通信协议是简单文本,PHP 原生 socket 就能实现。
  • MySQL :没有纯 PHP 替代方案,因为 MySQL 通信协议是复杂二进制协议,PHP 原生 socket 解析不了。所以连 MySQL 必须装扩展

Q6:PDO 和 MySQLi 用哪个?

新项目用 PDO ,理由是:换数据库只需改一行连接字符串,预处理支持命名参数(:name),写法更统一。


十一、一张图全部串起来

ini 复制代码
httpd.conf                     php.ini                    Zend 引擎
┌───────────────┐          ┌──────────────┐          ┌──────────────┐
│ PHPIniDir     │─────────→│ extension=   │          │ 全局符号表    │
│ LoadMod php.. │  告诉PHP  │  php_redis   │──注册类─→│  ├ Redis     │
└───────────────┘  去哪找   │  php_mysql   │          │  ├ mysql_*  │
                   配置    │  php_curl    │          │  ├ curl     │
                           └──────────────┘          │  └ ...      │
                                                      │             │
  Apache                 PHP脚本                     │ 编译 & 执行   │
  ┌──────┐              ┌───────────┐                 │ ┌─────────┐ │
  │请求  │─────────────→│ $db->     │──查符号表────────→│ │ opcode  │ │
  │.php  │   转交PHP    │ getRow()  │                 │ │ 一条条跑 │ │
  └──────┘              │ $r=new    │←────────────────│ └─────────┘ │
                        │ Redis()   │    返回结果      └──────────────┘
                        └────┬──────┘
                             │
                    ┌────────┼────────┐
                    │        │        │
                    ▼        ▼        ▼
               ┌────────┐ ┌────────┐ ┌────────┐
               │ redis- │ │ MySQL  │ │ HTTP   │
               │ server │ │ 服务   │ │ 服务    │
               │ :6379  │ │ :3306  │ │ :80    │
               └────────┘ └────────┘ └────────┘
                独立进程    独立进程    远程服务
               (必须先启动)(必须先启动)(必须先存在)

最后记住三句话:

  1. 扩展是桥梁------连接 PHP 与外部服务,负责"翻译"指令和返回值。
  2. 框架是包装 ------无论 ECShop 的 getRow() 还是 ThinkPHP 的 find(),底层都是同一个扩展。
  3. 服务必须先跑起来 ------扩展只是客户端,redis-server.exe / mysqld 才是真正干活的那个。
相关推荐
鹏仔先生19 小时前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下1 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip1 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒1 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog2501 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis1 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel
Cheng小攸1 天前
渗透行为分析与检测
开发语言·php
云水一下1 天前
从零开始学 PHP 系列(六):MySQL 数据库与 PHP 交互——让数据真正“住”进服务器
数据库·mysql·php
qq_452396231 天前
第十四篇:《K8s 网络模型与 CNI 插件(Calico、Flannel、Cilium)》
网络·kubernetes·php