从"为什么要装扩展"到"框架底层到底干了啥",一篇全讲清楚。
一、先把"扩展"和"服务"分清楚
这两个词长得像,但完全是两码事:
| 扩展(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.ini → extension= |
| 存放位置 | 你的项目目录 | 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.dll 或 php_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 │
└────────┘ └────────┘ └────────┘
独立进程 独立进程 远程服务
(必须先启动)(必须先启动)(必须先存在)
最后记住三句话:
- 扩展是桥梁------连接 PHP 与外部服务,负责"翻译"指令和返回值。
- 框架是包装 ------无论 ECShop 的
getRow()还是 ThinkPHP 的find(),底层都是同一个扩展。- 服务必须先跑起来 ------扩展只是客户端,
redis-server.exe/mysqld才是真正干活的那个。