2024年PHP开发者大会(日本)

extract

(PHP 4, PHP 5, PHP 7, PHP 8)

extract从数组导入变量到当前符号表

描述

extract(数组 &$array, 整数 $flags = EXTR_OVERWRITE, 字符串 $prefix = ""): 整数

将变量从数组导入到当前符号表

检查每个键是否具有有效的变量名。它还会检查与符号表中现有变量的冲突。

警告

不要对不可信数据(例如用户输入(例如 $_GET$_FILES))使用extract()

参数

array

一个关联数组。此函数将键视为变量名,将值视为变量值。对于每个键/值对,它将在当前符号表中创建一个变量,这取决于flagsprefix参数。

必须使用关联数组;除非使用EXTR_PREFIX_ALLEXTR_PREFIX_INVALID,否则数值索引数组不会产生结果。

flags

无效/数字键和冲突的处理方式由提取flags确定。它可以是以下值之一

EXTR_OVERWRITE
如果发生冲突,则覆盖现有变量。
EXTR_SKIP
如果发生冲突,则不覆盖现有变量。
EXTR_PREFIX_SAME
如果发生冲突,则在变量名前添加prefix
EXTR_PREFIX_ALL
为所有变量名添加prefix
EXTR_PREFIX_INVALID
仅为无效/数字变量名添加prefix
EXTR_IF_EXISTS
只有当变量已存在于当前符号表中时才覆盖该变量,否则什么也不做。例如,这对于定义有效变量列表,然后仅提取您已在$_REQUEST中定义的那些变量非常有用。
EXTR_PREFIX_IF_EXISTS
只有当同一变量的非前缀版本存在于当前符号表中时,才创建前缀变量名。
EXTR_REFS
将变量作为引用提取。这实际上意味着导入变量的值仍然引用array参数的值。您可以单独使用此标志,也可以通过对flags进行OR运算将其与任何其他标志组合。

如果未指定flags,则假定为EXTR_OVERWRITE

prefix

请注意,只有当flagsEXTR_PREFIX_SAMEEXTR_PREFIX_ALLEXTR_PREFIX_INVALIDEXTR_PREFIX_IF_EXISTS时,才需要prefix。如果前缀结果不是有效的变量名,则不会将其导入到符号表中。前缀会自动用下划线与数组键分隔。

返回值

返回成功导入到符号表中的变量数。

示例

示例 #1 extract() 示例

extract() 的一个可能用途是将wddx_deserialize()返回的关联数组中包含的变量导入到符号表中。

<?php

/* 假设 $var_array 是从 wddx_deserialize 返回的数组 */

$size = "large";
$var_array = array(
"color" => "blue",
"size" => "medium",
"shape" => "sphere"
);

extract($var_array, EXTR_PREFIX_SAME, "wddx");

echo
"$color, $size, $shape, $wddx_size\n";

?>

以上示例将输出

blue, large, sphere, medium

$size 没有被覆盖,因为我们指定了EXTR_PREFIX_SAME,这导致创建了$wddx_size。如果指定了EXTR_SKIP,则甚至不会创建$wddx_sizeEXTR_OVERWRITE 将导致$size 的值为“medium”,而EXTR_PREFIX_ALL 将导致新变量被命名为$wddx_color$wddx_size$wddx_shape

注释

警告

不要对不可信数据(即 $_GET$_FILES 等)使用extract()。如果这样做,请确保使用非覆盖flags值(例如EXTR_SKIP),并注意应按照variables_order(在php.ini中定义)中定义的顺序进行提取。

参见

  • compact() - 创建包含变量及其值的数组
  • list() - 将变量赋值为数组

添加注释

用户贡献的注释 28 条注释

Michael Newton
19年前
他们说“如果结果不是有效的变量名,则不会将其导入到符号表中”。

他们应该说的是,如果_任何_结果的名称无效,则_不会_提取任何变量。

在Windows 2000上的4.3.10下,我正在提取一些MySQL记录,但是需要将两个字段转换为IP地址
<?
extract(mysql_fetch_assoc(mysql_query('SELECT * FROM foo')));
extract(mysql_fetch_assoc(mysql_query('SELECT INET_NTOA(bar) AS bar, INET_NTOA(baz) FROM foo')));
?>

我在SQL查询中忘记了第二个AS修饰符。因为它无法将名为INET_NTOA(baz)的变量提取到符号表中,所以它两个都没有做。

(顺便说一句,我通常不会像那样堆叠函数!只是为了做一个简短的例子!)
FredLawl
11年前
可以使用它作为为类创建公共属性的一种方法。

<?php
Foo {

公共函数
__construct ($array) {

extract($array, EXTR_REFS);
循环 (
$array 作为 $key => $value) {
$this->$key = $$key;
// 执行: $this->key = $key; 如果 $key 不是字符串。
}

}

}

$array = 数组(
'valueOne' => '测试值 1',
'valueTwo' => '测试值 2',
'valueThree' => '测试值 3'
);

$foo = 新 Foo($array);

// 可行
echo $foo->valueOne; // 测试值 1
echo $foo->valueTwo; // 测试值 2

// 不可行!
echo $foo::$valueOne; // 致命错误:访问未声明的静态属性:Test::$valueOne
?>
Csaba (alum.mit.edu)
19年前
有时你可能只想提取数组中键值对的命名子集。这样可以使事情更有条理,并可以防止无关变量因错误的键而被覆盖。例如,

$things = 'unsaid';
$REQUEST = array(He=>This, said=>1, my=>is, info=>2, had=>a,
very=>3, important=>test, things=>4);
$aVarToExtract = array(my, important, info);
extract (array_intersect_key ($REQUEST, array_flip($aVarToExtract)));

将提取
$my = 'is';
$important = 'test';
$info = 2;

但将保留某些
$things = 'unsaid'

来自维也纳的Csaba Gabor
注意:当然,来自网页的组合请求位于$_REQUEST中。
dmikam
10年前
我做了一些测试来比较以下结构的速度
<?php

extract
($ARRAY);

// 与

foreach($ARRAY 作为 $key=>$value)
$
$key = $value;
?>

令我惊讶的是,extract 比 foreach 结构慢 20%-80%。我不太明白为什么,但事实就是这样。
Robc
13年前
例如,在数据库查询后从行中提取数据时

$row = mysql_fetch_array($result, MYSQL_ASSOC)
extract($row);

我发现结果变量可能与数据库中的变量类型不匹配。特别是,我发现数据库中的整数在提取的变量上 gettype() 为字符串。
Dan O'Donnell
17年前
跟进 [email protected] 的帖子

大概处理此安全问题的简单方法是使用 EXTR_IF_EXISTS 标志并确保

a) 你预先定义可接受的输入变量(即作为空变量)
b) 对任何用户输入进行消毒以避免不可接受的变量内容。

如果你做了这两件事,那么我不确定我看到 extract($_REQUEST,EXTR_IF_EXISTS); 和手动分配每个变量之间的区别。

我这里不是在讨论将变量存储在数据库中的想法,而只是允许你相对安全地对 REQUEST 数组使用 extract 的必要步骤。
CertaiN
10年前
[新版本]

使用方法示例
<?php
$_GET
['A']['a'] = ' 正确的(包括一些空格) ';
$_GET['A']['b'] = ' 正确的(包括一些空格) ';
$_GET['A']['c'] = "无效的 UTF-8 序列:\xe3\xe3\xe3";
$_GET['A']['d']['invalid_structure'] = '无效的';

$_GET['B']['a'] = ' 正确的(包括一些空格) ';
$_GET['B']['b'] = "无效的 UTF-8 序列:\xe3\xe3\xe3";
$_GET['B']['c']['invalid_structure'] = '无效的';
$_GET['B']["无效的 UTF-8 序列:\xe3\xe3\xe3"] = '无效的';

$_GET['C']['a'] = ' 正确的(包括一些空格) ';
$_GET['C']['b'] = "无效的 UTF-8 序列:\xe3\xe3\xe3";
$_GET['C']['c']['invalid_structure'] = '无效的';
$_GET['C']["无效的 UTF-8 序列:\xe3\xe3\xe3"] = '无效的';

$_GET['unneeded_item'] = '不需要的';

var_dump(filter_struct_utf8(INPUT_GET, array(
'A' => array(
'a' => '',
'b' => FILTER_STRUCT_TRIM,
'c' => '',
'd' => '',
),
'B' => FILTER_STRUCT_FORCE_ARRAY,
'C' => FILTER_STRUCT_FORCE_ARRAY | FILTER_STRUCT_TRIM,
)));
?>

结果示例
array(3) {
["A"]=>
array(4) {
["a"]=>
string(36) " 正确的(包括一些空格) "
["b"]=>
string(30) "正确的(包括一些空格)"
["c"]=>
string(0) ""
["d"]=>
string(0) ""
}
["B"]=>
array(3) {
["a"]=>
string(36) " 正确的(包括一些空格) "
["b"]=>
string(0) ""
["c"]=>
string(0) ""
}
["C"]=>
array(3) {
["a"]=>
string(30) "正确的(包括一些空格)"
["b"]=>
string(0) ""
["c"]=>
string(0) ""
}
}
amolocaleb (gmail.com)
6年前
如果对象被类型转换为数组并“提取”,则只能访问公共属性。方法当然被省略。
<?php
Test{
公共
$name = '';

受保护的
$age = 10;

公共
$status = 'disabled';

私有
$isTrue = false;

公共函数
__construct()
{
$this->name = 'Amolo';
$this->status = 'active';
}

公共函数
getName()
{
返回
$this->name;
}

公共函数
getAge()
{
返回
$this->age;
}

公共函数
getStatus()
{
返回
$this->status;
}

}

$obj = (数组) 新 Test();
var_dump($obj);
/* array(4) { ["name"]=> string(5) "Amolo" ["*age"]=> int(10) ["status"]=> string(6) "active" ["TestisTrue"]=> bool(false) } */
extract((数组)新 Test());
echo
$name; //Amolo
echo $status; //active
echo $age;//注意:未定义变量:age
echo $isTrue;//注意:未定义变量:isTrue
phatsk+php at gmail dot com
6年前
使用extract的返回值可能会导致意外的结果,尤其是在使用EXTR_REFS时。

<?php

$my_data
= [
'count' => 15,
'name' => 'foo',
];

$count = extract( $my_data, EXTR_REFS );

echo
$my_data['count']; // 2, 不是15。
dotslash.lu at gmail.com
11年前
你无法提取数值索引数组(例如非关联数组)。
<?php
$a
= array(
1,
2
);
extract($a);
var_dump(${1});
?>

结果
PHP Notice: Undefined variable: 1 in /Users/Lutashi/t.php on line 7

提示: /Users/Lutashi/t.php 第 7 行中未定义变量: 1
NULL
mrkhoa99 at gmail dot com
6年前
我们可以使用extract()函数作为模板引擎。

<?php
#Template.php

class Template
{
protected
$viewVars;

public function
renderPage($tpl)
{
ob_start();
extract($this->viewVars, EXTR_SKIP);
include
$tpl;
return
ob_end_flush();
}

public function
assign($arr)
{
foreach (
$arr as $key => $value) {
$this->viewVars[$key] = $value;
}
return
$this;
}
}

$template = new Template();
$template->assign(
[
'pageHeader' => 'Page Header', 'content' => 'This is the content page']
);
$template->renderPage('tpl.php');

#tpl.php

<h1><?= $pageHeader; ?></h1>
<p><?= $content ;?></p>

输出

Page Header
This is the content page
CertaiN
10年前
[新版本]
此函数对于过滤复杂的数组结构非常有用。
此外,还提供一些整数位掩码和无效UTF-8序列检测。

代码
<?php
/**
* @param integer $type 常量,例如 INPUT_XXX。
* @param array $default 指定的超全局变量的默认结构。
* 可用的位掩码:
* + FILTER_STRUCT_FORCE_ARRAY - 强制转换为一维数组。
* + FILTER_STRUCT_TRIM - 使用 ASCII 控制字符修剪。
* + FILTER_STRUCT_FULL_TRIM - 使用 ASCII 控制字符、
* 全角空格和不换行空格修剪。
* @return array 过滤后的超全局变量的值。
*/
define('FILTER_STRUCT_FORCE_ARRAY', 1);
define('FILTER_STRUCT_TRIM', 2);
define('FILTER_STRUCT_FULL_TRIM', 4);
function
filter_struct_utf8($type, array $default) {
static
$func = __FUNCTION__;
static
$trim = "[\\x0-\x20\x7f]";
static
$ftrim = "[\\x0-\x20\x7f\xc2\xa0\xe3\x80\x80]";
static
$recursive_static = false;
if (!
$recursive = $recursive_static) {
$types = array(
INPUT_GET => $_GET,
INPUT_POST => $_POST,
INPUT_COOKIE => $_COOKIE,
INPUT_REQUEST => $_REQUEST,
);
if (!isset(
$types[(int)$type])) {
throw new
LogicException('未知的超全局变量类型');
}
$var = $types[(int)$type];
$recursive_static = true;
} else {
$var = $type;
}
$ret = array();
foreach (
$default as $key => $value) {
if (
$is_int = is_int($value)) {
if (!(
$value | (
FILTER_STRUCT_FORCE_ARRAY |
FILTER_STRUCT_FULL_TRIM |
FILTER_STRUCT_TRIM
))) {
$recursive_static = false;
throw new
LogicException('未知的位掩码');
}
if (
$value & FILTER_STRUCT_FORCE_ARRAY) {
$tmp = array();
if (isset(
$var[$key])) {
foreach ((array)
$var[$key] as $k => $v) {
if (!
preg_match('//u', $k)){
continue;
}
$value &= FILTER_STRUCT_FULL_TRIM | FILTER_STRUCT_TRIM;
$tmp += array($k => $value ? $value : '');
}
}
$value = $tmp;
}
}
if (
$isset = isset($var[$key]) and is_array($value)) {
$ret[$key] = $func($var[$key], $value);
} elseif (!
$isset || is_array($var[$key])) {
$ret[$key] = null;
} elseif (
$is_int && $value & FILTER_STRUCT_FULL_TRIM) {
$ret[$key] = preg_replace("/\A{$ftrim}++|{$ftrim}++\z/u", '', $var[$key]);
} elseif (
$is_int && $value & FILTER_STRUCT_TRIM) {
$ret[$key] = preg_replace("/\A{$trim}++|{$trim}++\z/u", '', $var[$key]);
} else {
$ret[$key] = preg_replace('//u', '', $var[$key]);
}
if (
$ret[$key] === null) {
$ret[$key] = $is_int ? '' : $value;
}
}
if (!
$recursive) {
$recursive_static = false;
}
return
$ret;
}
?>
nicolas zeh
18年前
此函数提供与 extract 完全相同的的功能,只是添加了一个参数来定义提取目标。
如果您的 PHP 安装不支持所需的标志,或者更重要的是,如果您想将数组提取到 $GLOBALS 之外的其他目标(例如其他数组或对象),则可以使用此函数。
与 extract 的唯一区别在于,extract_to 会将 $arr 的数组指针移动到末尾,因为 $arr 是通过引用传递的,以支持 EXTR_REFS 标志。

<?php
如果 ( !defined('EXTR_PREFIX_ALL') ) define('EXTR_PREFIX_ALL', 3);
如果 ( !
defined('EXTR_PREFIX_INVALID') ) define('EXTR_PREFIX_INVALID', 4);
如果 ( !
defined('EXTR_IF_EXISTS') ) define('EXTR_IF_EXISTS', 5);
如果 ( !
defined('EXTR_PREFIX_IF_EXISTS') ) define('EXTR_PREFIX_IF_EXISTS', 6);
如果 ( !
defined('EXTR_REFS') ) define('EXTR_REFS', 256);


函数
extract_to( &$arr, &$to, $type=EXTR_OVERWRITE, $prefix=false ){

如果 ( !
is_array( $arr ) ) 返回 trigger_error("extract_to(): 第一个参数应为数组", E_USER_WARNING );

如果 (
is_array( $to ) ) $t=0;
否则如果 (
is_object( $to ) ) $t=1;
否则返回
trigger_error("extract_to(): 第二个参数应为数组或对象", E_USER_WARNING );

如果 (
$type==EXTR_PREFIX_SAME || $type==EXTR_PREFIX_ALL || $type==EXTR_PREFIX_INVALID || $type==EXTR_PREFIX_IF_EXISTS )
如果 (
$prefix===false ) 返回 trigger_error("extract_to(): 需要指定前缀", E_USER_WARNING );
否则
$prefix .= '_';

$i=0;
循环 (
$arr 作为 $key=>$val ){

$nkey = $key;
$isset = $t==1 ? isset( $to[$key] ) : isset( $to->$key );

如果 ( (
$type==EXTR_SKIP && $isset )
|| (
$type==EXTR_IF_EXISTS && !$isset ) )
继续;

否则如果 ( (
$type==EXTR_PREFIX_SAME && $isset )
|| (
$type==EXTR_PREFIX_ALL )
|| (
$type==EXTR_PREFIX_INVALID && !preg_match( '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#', $key ) ) )
$nkey = $prefix.$key;

否则如果 (
$type==EXTR_PREFIX_IF_EXISTS )
如果 (
$isset ) $nkey = $prefix.$key;
否则继续;

如果 ( !
preg_match( '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#', $nkey ) ) 继续;

如果 (
$t==1 )
如果 (
$type & EXTR_REFS ) $to->$nkey = &$arr[$key];
否则
$to->$nkey = $val;
否则
如果 (
$type & EXTR_REFS ) $to[$nkey] = &$arr[$key];
否则
$to[$nkey] = $val;

$i++;
}

返回
$i;
}

// 例如:
extract_to( $myarray, $myobject, EXTR_IF_EXISTS );
?>
benjaminATwebbutvecklarnaDOTse
16年前
回复:关于 extract() 和空值 的 anon at anon dot org

我个人发现它在从数据库中提取多个结果集时很有用,其中当变量不为空时(并且可选地,如果它不大于 0)后面的结果集会覆盖前面的结果集。

如果在现有两个的基础上扩展 `$extract_type` 将会非常有用。

EXTR_OVERWRITE
EXTR_SKIP

例如:

EXTR_OVERWRITE_NULL
- 如果发生冲突,则如果现有变量为空,则覆盖它。

EXTR_OVERWRITE_0
- 相同,但为 0 或 null。

EXTR_SKIP_NULL
- 如果发生冲突,则如果现有变量不为空,则跳过新变量。

EXTR_SKIP_0
- 相同,但为 0 或 null。

- 相同,但为 0 或 null。
这些应该可以涵盖一些现在没有涵盖的常见情况。
9年前
<?
注意,`extract()` 只会在当前作用域中创建或覆盖变量,因此
函数 test(){
$a=Array('b'=>1,'c'=>2);
}
extract($a);
test();
?>
exit("$b");
<?
注意,`extract()` 只会在当前作用域中创建或覆盖变量,因此
将不会产生任何输出,而
函数 test(){
$a=Array('b'=>1,'c'=>2);
}
extract($a);
test();
?>
global $b;
将输出 1。
16年前
Hayley Watson

Dan O'Donnell 的建议需要第三个条件才能按描述工作

c) 没有定义其他变量——尤其是不包含潜在敏感信息的变量。

如果没有这个条件,`extract()` 和手动赋值变量(以及由此产生的安全隐患)之间的区别应该很明显。
唯一有效的安全步骤是 (b)——但无论如何你都应该这样做。
18年前
owk dot ch199_ph at gadz dot org
<?php
foreach ($V as $k => &$v) {
$
$k =& $v;
}
?>
这可以用来创建一种特殊的“自由参数”函数,让你在调用它们时选择发送变量的方式以及哪些变量。此外,由于使用了引用,它们的调用速度非常快。
<?php
function free_args (&$V) {
foreach (
$V as $k => &$v) {
$
$k =& $v;
}
unset (
$k); unset ($v); unset ($V);

// 注意,如果需要提取 $k、$v 或 $V 变量,则应为上述行中的变量赋予其他名称(例如,$__k、$__v 和 $__V)
}

$huge_text = '...';

$a = array ('arg1' => 'val1', 'arg2' => &$huge_text); // 在此调用中,只有 $arg2 将在函数中成为真正的引用
free_args ($a);
?>
警告:不能编写:“<?php free_args (array ('arg1' => 'val1')); ?>”,因为数组在函数开始时尚未创建,因此无法被函数引用。
[email protected]
18年前
使用 extract() 处理 $_REQUEST、$_GET 等非常容易造成严重的安全性漏洞。您必须非常确定自己在做什么,并使用 extract() 的正确标志来避免覆盖重要的变量。

例如,[email protected] 的提交不仅可以完美模拟 register_globals(这很糟糕),而且会将其存储在数据库中并在每次脚本运行时调用相同的变量(实际上允许攻击者每次脚本运行时通过一次攻击来攻击您的脚本)。糟糕!

要修复它,您需要巧妙地使用标志。例如,您可以使用 EXTR_PREFIX_ALL 而不是 EXTR_OVERWRITE。当然,您还应该清理表单元素以确保其中没有 php 代码,并确保表单数据中不包含任何非常重要的变量。(例如经典的 `$is_admin = true` 攻击)
Adam Monsen <[email protected]>
20 年前
如示例所示,如果使用了您的“前缀”,则会在提取变量的名称前添加一个下划线。这意味着,前缀“p”将成为“p_”的前缀,因此带有“blarg”前缀的变量将成为“p_blarg”。

如果您不确定通过提取创建了哪些变量,可以调用 get_defined_vars() 来查看当前作用域中所有已定义的变量。
匿名
19年前
为了使这一点(希望)完全清楚,在对字符串添加前缀时总是会添加下划线。
extract(array("color" => "blue"),EXTR_PREFIX_ALL,'');// 注意:前缀为空
等同于
$color='_blue';
Dutchdavey
17年前
我想提请您注意本页末尾关于前缀的用户说明。用户指出 php 会在您的前缀前添加一个“_”。
[email protected]
19年前
关于 extract() 和空值的警告。

这可能是一个 Zend2 引擎的实际错误,但这是一种糟糕的编程习惯,所以我在这里分享它。

我经常在没有 E_STRICT(这可以防止此类错误)的环境中工作,并且无法更改它。我还使用一个非常简单的模板类,简而言之,它的工作方式如下:

$t = new Template('somefile.php');
$t->title = $title;
$t->body = $body;
$t->display();

display() 差不多如下所示:

function display(){
extract(get_object_vars($this),EXTR_REFS);
ob_start(); include $this->templateFileName;
return ob_get_clean();
}

如果任何赋值的值为空(假设在这种情况下 $title 在上面没有初始化),它会导致引擎执行各种令人难以置信的奇怪操作,例如以极其不一致的方式完全丢失变量的跟踪。我追踪到问题在于它使用了 EXTR_REFS 标志。我认为在 PHP 的内部变量存储或引用计数机制中,尝试提取空引用会使其丢失某些内容的跟踪或计数。

简而言之,如果您在使用 extract() 时开始出现奇怪的行为,请确保您尝试从中提取变量的数组或对象不包含空键或值!
[email protected]
14 年前
我使用 XDebug 和 NetbeansIDE 来分析和开发 PHP 代码。在调试 extract 语句时,变量列表中没有出现新的变量。尽管可以通过显式监视项检查 extract 创建的所有变量,并且一旦 PHP 脚本使用它们,单个变量就会出现,但我仍然不确定这是否是错误的配置、功能还是 XDebug 中的错误。
Aaron Stone
20 年前
如果您正在移植旧应用程序并遵循上述建议,只提取 _SERVER、_SESSION、_COOKIE、_POST、_GET,则忘记了提取 _FILES。将 _FILES 放在最后并使用 EXTR_SKIP 不起作用,因为文件上传框的名称已经设置为仅包含从早期提取的上传文件的临时名称的变量(我没有测试具体是哪个,但是)。一种解决方法是将 _FILES 放在最后并使用 EXTR_OVERWRITE。这允许 extract 将该仅包含临时名称的变量替换为包含完整文件上传信息的数组。
[email protected]
3 年前
回复 Dan O'Donnell 的说明

“大概处理此安全问题的简便方法是使用 EXTR_IF_EXISTS 标志并确保”

不一定 - 即使使用 EXTR_IF_EXISTS 标志也可能非常危险 - 想象一下这段代码正在运行……

<?php

global $sql ;

function
runSql ()
{
$result = $conn->query($sql);
$conn-> close();
return
$result;
}

function
extractGet ()
{
$name = '' ;
$address = '' ;
foreach (
$_GET as $key => $value )
$_GET[$key] = urldecode ( $value ) ;
extract($_GET,EXTR_IF_EXISTS);
$sql = str_replace ( '{NAME}', $name, $sql ) ;
$sql = str_replace ( '{ADDRESS}', $address, $sql ) ;
}

function
outputResult ( $res )
{
echo
'<pre>'.print_r ( $res->fetch_array(MYSQLI_NUM) ).'</pre>' ;
}

$sql = 'SELECT postcode FROM Customers WHERE name={NAME} AND address={ADDRESS}';
extractGet () ;
$res = runSql () ;
outputResult ( $res ) ;
?>

现在您能看到这里存在一个巨大的安全问题……

如果我们有这样的 url,似乎一切都很好

mycode.php?name=joe%20bloggs&address=20%20Any%20Street

因为它会专门找到 20 Any Street 的 joe bloggs。

但是如果有人输入

mycode.php?sql=SELECT%20password%20FROM%20Customers

甚至更糟

mycode.php?sql=DELETE%20from%20Customers

这里的问题是,据您所知,您将 name 和 address 定义为该函数中仅有的两个空变量 - 您可能忘记了全局变量,而这些全局变量可能会导致重大的安全问题。

在上面的示例中,$sql 定义为“SELECT postcode FROM Customers WHERE name={NAME} AND address={ADDRESS}”,这看起来很好,很安全,然后在 extractGet 函数中的 str_replace 之后用来自 $_GET 的 name 和 address 变量替换 {NAME} 和 {ADDRESS} - 但如果 GET 包含 SQL 变量,则会在 str_replace 函数之前覆盖全局 $sql 变量 - 如果 str_replace 函数找不到匹配项,它只返回原始字符串 - 在以上两个示例中,SQL 字符串将是“SELECT password FROM Customers”,在示例 outputResult 中,它只打印从数据库检索的数据,因此在此阶段它可能会将所有客户密码(希望已加密!)打印到屏幕上(糟糕!)或者在第二个示例中,SQL 字符串将是“DELETE from Customers” - 没有 WHERE 子句,这将删除 Customers 表中的所有数据,当然,还有以下组合:

SELECT table_name FROM information_schema.tables



DROP TABLE <table_name>

可能是一场真正的灾难!

当然,这只是一个基本的示例,但很容易忘记全局变量,如果将 GET、REQUEST 或 POST 发送到 extract,这些全局变量很容易与 extract 一起使用来造成严重的安全风险!
[email protected]
19年前
这是一个关于提取方法在需要递归工作(也作用于嵌套数组)时的外观的小示例……

请注意,这只是一个示例,它可以更容易地完成,也可以更高级。

<?php
/**
* extract() 函数的嵌套版本。
*
* @param array $array 要从中提取变量的数组
* @param int $type 用于覆盖的类型(遵循 PHP 5.0.3 中 extract() 的相同规则)
* @param string $prefix 必要时使用的变量前缀
*/
function extract_nested (&$array, $type = EXTR_OVERWRITE, $prefix = '')
{
/**
* 判断数组是否真的是数组?
*/
if (!is_array ($array))
{
return
trigger_error ('extract_nested (): 第一个参数应为数组', E_USER_WARNING);
}

/**
* 如果设置了前缀,则检查前缀是否与可接受的正则表达式模式匹配
* (用于变量的模式)
*/
if (!empty ($prefix) && !preg_match ('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#', $prefix))
{
return
trigger_error ('extract_nested (): 第三个参数应以字母或下划线开头', E_USER_WARNING);
}

/**
* 检查是否需要前缀。如果是,并且为空,则返回错误。
*/
if (($type == EXTR_PREFIX_SAME || $type == EXTR_PREFIX_ALL || $type == EXTR_PREFIX_IF_EXISTS) && empty ($prefix))
{
return
trigger_error ('extract_nested (): 预期指定前缀', E_USER_WARNING);
}

/**
* 确保前缀正确
*/
$prefix = $prefix . '_';

/**
* 遍历数组
*/
foreach ($array as $key => $val)
{
/**
* 如果键不是数组,则按需提取它
*/
if (!is_array ($array[$key]))
{
switch (
$type)
{
default:
case
EXTR_OVERWRITE:
$GLOBALS[$key] = $val;
break;
case
EXTR_SKIP:
$GLOBALS[$key] = isset ($GLOBALS[$key]) ? $GLOBALS[$key] : $val;
break;
case
EXTR_PREFIX_SAME:
if (isset (
$GLOBALS[$key]))
{
$GLOBALS[$prefix . $key] = $val;
}
else
{
$GLOBALS[$key] = $val;
}
break;
case
EXTR_PREFIX_ALL:
$GLOBALS[$prefix . $key] = $val;
break;
case
EXTR_PREFIX_INVALID:
if (!
preg_match ('#^[a-zA-Z_\x7f-\xff]$#', $key{0}))
{
$GLOBALS[$prefix . $key] = $val;
}
else
{
$GLOBALS[$key] = $val;
}
break;
case
EXTR_IF_EXISTS:
if (isset (
$GLOBALS[$key]))
{
$GLOBALS[$key] = $val;
}
break;
case
EXTR_PREFIX_IF_EXISTS:
if (isset (
$GLOBALS[$key]))
{
$GLOBALS[$prefix . $key] = $val;
}
break;
case
EXTR_REFS:
$GLOBALS[$key] =& $array[$key];
break;
}
}
/**
* 键是一个数组……在此索引上使用此函数
*/
else
{
extract_nested ($array[$key], $type, $prefix);
}
}
}
?>
danbettles at yahoo dot co dot uk
15年前
使用EXTR_PREFIX_ALL(以及可能所有其他EXTR_PREFIX_* 常量)和数字索引数组时,extract()将在前缀和索引之间添加下划线("_")。

<?php

extract
(array('foo', 'bar'), EXTR_PREFIX_ALL, 'var');

print_r(get_defined_vars()); // 显示 $var_0 = 'foo' 和 $var_1 = 'bar'
?>
moslehi<atsign>gmail<d0t>c0m
18年前
通过实验,我发现如果键已设置且不是数字,调用extract()也会显示键的数量!也许我的定义比这个更好。请查看这些脚本

<?PHP
$var
["i"] = "a";
$var["j"] = "b";
$var["k"] = 1;
echo
extract($var); // 返回 3
?>

<?PHP
$var2
["i"] = "a";
$var2[2] = "b";
$var2[] = 1;
echo
extract($var2); // 返回 1
?>

(Arash Moslehi)
To Top