extract()这个函数从PHP 4.0就有了,它的作用是把关联数组的键名变成变量名,键值变成变量值。
这个函数在PHP开发社区里争议不小。很多编码规范直接禁止使用它,因为它会让代码变得难以追踪——变量到底从哪里来的,看一眼代码根本不知道。
不过了解它的机制还是有必要的,毕竟维护老项目时经常会碰到。
语法格式
int extract(array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]]): int
参数说明:
| 参数 | 说明 | 必填? |
|---|---|---|
| array | 要导入的关联数组 | 必填 |
| flags | 处理冲突和非法变量名的规则 | 可选 |
| prefix | 变量名前缀(某些flags下才需要) | 可选 |
返回值: 成功导入的变量数量。
最简单的用法
<?php
$user_data = [
"username" => "代码号001",
"email" => "user001@test.com",
"score" => 95
];
extract($user_data);
echo "用户名:{$username}\n";
echo "邮箱:{$email}\n";
echo "分数:{$score}\n";
?>
输出:
用户名:代码号001
邮箱:user001@test.com
分数:95
数组里的username变成了变量$username,email变成了$email,score变成了$score。省去了手动定义变量的麻烦。
flags参数的作用
这是extract()的核心控制部分,决定了遇到冲突时怎么办。
| flags值 | 行为 |
|---|---|
| EXTR_OVERWRITE | 默认值。如果变量已存在,覆盖它 |
| EXTR_SKIP | 如果变量已存在,跳过不覆盖 |
| EXTR_PREFIX_SAME | 如果变量已存在,加前缀 |
| EXTR_PREFIX_ALL | 所有变量都加前缀 |
| EXTR_PREFIX_INVALID | 非法变量名(如数字开头)加前缀 |
| EXTR_IF_EXISTS | 只覆盖已存在的变量 |
| EXTR_REFS | 以引用方式导入变量 |
示例1:处理变量名冲突(EXTR_SKIP)
<?php
$username = "原有用户"; // 已存在的变量
$incoming_data = [
"username" => "新用户",
"email" => "new@test.com",
"age" => 25
];
extract($incoming_data, EXTR_SKIP);
echo "username: {$username}\n"; // 还是"原有用户",没被覆盖
echo "email: {$email}\n";
echo "age: {$age}\n";
?>
输出:
username: 原有用户
email: new@test.com
age: 25
$username已存在,用EXTR_SKIP就跳过了,原来的值被保留。
示例2:所有变量加前缀(EXTR_PREFIX_ALL)
<?php
$config = [
"host" => "localhost",
"port" => 3306,
"username" => "root",
"password" => "123456"
];
extract($config, EXTR_PREFIX_ALL, "cfg");
echo "数据库主机:{$cfg_host}\n";
echo "端口:{$cfg_port}\n";
echo "用户名:{$cfg_username}\n";
echo "密码:{$cfg_password}\n";
?>
输出:
数据库主机:localhost
端口:3306
用户名:root
密码:123456
每个变量名前面都加了cfg_,避免和现有变量冲突。这是相对安全的用法。
示例3:非法变量名的处理
<?php
// 键名以数字开头,不能作为变量名
$weird_data = [
"123invalid" => "这个键名非法",
"valid_key" => "这个正常",
"user-name" => "带横杠也不行"
];
// 默认情况:非法键名直接被跳过
$count1 = extract($weird_data);
echo "导入数量:{$count1}\n";
// 加前缀处理非法键名
$count2 = extract($weird_data, EXTR_PREFIX_INVALID, "var");
echo "导入数量:{$count2}\n";
echo "变量值:{$var_123invalid}\n";
?>
输出:
导入数量:1
导入数量:3
变量值:这个键名非法
个人经验分享
-
为什么很多人不推荐用extract()?
-
来源不明:
$username这个变量到底从哪来的?是正常定义的还是extract()从数组里变的?看代码看不出来。 -
覆盖风险:不小心覆盖了重要的全局变量或函数内的已有变量。
-
调试困难:变量凭空出现,IDE也追踪不到。
-
安全问题:如果数组内容来自用户输入(比如
$_POST),extract()可能直接把用户提交的键值对变成变量,有变量覆盖的风险。
-
-
什么场景下extract()比较有用?
-
模板渲染:将变量传递给视图文件。很多老式模板引擎这样做。
-
配置加载:从配置数组中快速创建变量。
-
老项目维护:已经存在大量extract()调用的代码库。
-
-
更安全的替代方案
// 不推荐:extract() extract($data); echo $username; // 推荐:直接使用数组 echo $data['username']; // 或者显式赋值 $username = $data['username'] ?? ''; $email = $data['email'] ?? ''; -
如果非要用,记住几点
-
尽量用
EXTR_PREFIX_ALL加前缀,避免污染 -
不要对
$_GET、$_POST、$_REQUEST使用extract() -
限制作用域,在函数内部使用而不是全局
-
示例4:代码号学习编程场景(不推荐做法演示)
<?php
// 场景:配置文件加载(老式做法)
// config.php 返回一个配置数组
$app_config = [
"db_host" => "localhost",
"db_name" => "learning_db",
"db_user" => "root",
"debug_mode" => true,
"timezone" => "Asia/Shanghai"
];
// 老式做法:extract后直接用变量
extract($app_config, EXTR_PREFIX_ALL, "app");
echo "数据库:{$app_db_name}\n";
echo "时区:{$app_timezone}\n";
// 现在做法:直接使用数组(更清晰)
echo "数据库:{$app_config['db_name']}\n";
?>
示例5:EXTR_REFS引用模式
<?php
$source = ["count" => 10];
extract($source, EXTR_REFS);
$count = 20; // 修改变量
print_r($source); // 原数组也被修改了
?>
输出:
Array
(
[count] => 20
)
引用模式下,变量和原数组元素指向同一块内存。改变量就是改原数组,用时注意这个副作用。
本节课程知识要点
-
extract()将关联数组的键名转为变量名,键值转为变量值 -
返回成功导入的变量数量
-
默认行为
EXTR_OVERWRITE会覆盖已存在的同名变量 -
用
EXTR_SKIP可以跳过不覆盖 -
用
EXTR_PREFIX_ALL加前缀是相对安全的用法 -
不要对用户输入数据(如
$_POST)使用extract(),有安全风险 -
现在PHP开发中更推荐直接使用数组而非extract()
-
从PHP 4.0开始可用