PHP Conference Japan 2024

extract

(PHP 4、PHP 5、PHP 7、PHP 8)

extract從陣列匯入變數到目前的符號表

描述

extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""): int

從陣列匯入變數到目前的符號表

檢查每個鍵,查看它是否具有有效的變數名稱。它也會檢查符號表中是否存在與現有變數的衝突。

警告

不要在不受信任的資料上使用 extract(),例如使用者輸入(例如 $_GET$_FILES)。

參數

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

由於我們指定了 EXTR_PREFIX_SAME,因此未覆寫 $size,這導致建立 $wddx_size。如果指定了 EXTR_SKIP,則甚至不會建立 $wddx_sizeEXTR_OVERWRITE 會導致 $size 的值為「medium」,而 EXTR_PREFIX_ALL 會導致新的變數被命名為 $wddx_color$wddx_size$wddx_shape

注意事項

警告

不要在不受信任的資料上使用 extract(),例如使用者輸入(即 $_GET$_FILES 等)。如果這樣做,請確保使用其中一個非覆寫 flags 值,例如 EXTR_SKIP,並注意您應該按照 variables_order 中定義的相同順序提取 php.ini 中的值。

另請參閱

  • compact() - 建立包含變數及其值的陣列
  • list() - 以就像它們是陣列的方式指派變數

新增註解

使用者貢獻的註解 28 則註解

20
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) 的變數提取到符號表中,因此它沒有執行這兩個操作。

(順帶一提,我通常不會像這樣堆疊函數!只是為了舉個簡短的例子!)
10
FredLawl
11 年前
可以將其用作建立類別的公用屬性的方法。

<?php
class Foo {

public function
__construct ($array) {

extract($array, EXTR_REFS);
foreach (
$array as $key => $value) {
$this->$key = $$key;
// 執行:如果 $key 不是字串,$this->key = $key;。
}

}

}

$array = array(
'valueOne' => '測試值 1',
'valueTwo' => '測試值 2',
'valueThree' => '測試值 3'
);

$foo = new Foo($array);

// 有效
echo $foo->valueOne; // 測試值 1
echo $foo->valueTwo; // 測試值 2

// 無效!
echo $foo::$valueOne; // 致命錯誤:存取未宣告的靜態屬性:Test::$valueOne
?>
6
Csaba at alum dot mit dot edu
19 年前
有時候您可能只想提取陣列中鍵/值對的特定子集。這樣可以讓事情更有條理,並防止不相關的變數因錯誤的鍵而遭到覆寫。例如:

$things = '未說出口的';
$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 = '未說出口的'

來自維也納的 Csaba Gabor
注意:當然,從網頁傳入的複合請求會放在 $_REQUEST 中。
10
dmikam
10 年前
我做了一些測試來比較以下結構的速度
<?php

extract
($ARRAY);

// vs.

foreach($ARRAY as $key=>$value)
$
$key = $value;
?>

令我驚訝的是,extract 比 foreach 結構慢 20%-80%。我不太明白為什麼,但事實就是如此。
8
Robc
13 年前
當從資料庫查詢後的資料列中提取資料時,例如使用

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

我發現結果變數可能與資料庫中的變數類型不符。特別是,我發現資料庫中的整數在提取的變數上使用 gettype() 時可能會變成字串。
6
Dan O'Donnell
17 年前
接續 ktwombley at gmail dot com 的貼文

據推測,處理此安全問題的一種簡單方法是使用 EXTR_IF_EXISTS 旗標並確保

a) 事先定義可接受的輸入變數(即作為空變數)
b) 清理任何使用者輸入,以避免不可接受的變數內容。

如果您執行這兩件事,那麼我不確定 extract($_REQUEST,EXTR_IF_EXISTS); 與手動指定每個變數之間有什麼區別。

我這裡不是在談論將變數儲存在資料庫中的想法,只是談論允許您相對安全地在 REQUEST 陣列上使用 extract 的直接必要步驟。
8
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) ""
}
}
2
amolocaleb at gmail dot com
6 年前
如果將物件強制轉換為陣列並「提取」,則只能存取公開屬性。方法當然會被省略。
<?php
class Test{
public
$name = '';

protected
$age = 10;

public
$status = '停用';

private
$isTrue = false;

public function
__construct()
{
$this->name = 'Amolo';
$this->status = '啟用';
}

public function
getName()
{
return
$this->name;
}

public function
getAge()
{
return
$this->age;
}

public function
getStatus()
{
return
$this->status;
}

}

$obj = (array) new Test();
var_dump($obj);
/* array(4) { ["name"]=> string(5) "Amolo" ["*age"]=> int(10) ["status"]=> string(6) "active" ["TestisTrue"]=> bool(false) } */
extract((array)new Test());
echo
$name; //Amolo
echo $status; //active
echo $age;//Notice: Undefined variable: age
echo $isTrue;//Notice: Undefined variable: isTrue
3
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。
7
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

Notice: Undefined variable: 1 in /Users/Lutashi/t.php on line 7
NULL
4
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
6
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;
}
?>
4
nicolas zeh
18 年前
這個函式提供的功能與 extract 完全相同,只是新增了一個參數來定義 extract 的目標。
如果您的 PHP 安裝不支援所需的標誌,或者更重要的是,如果您想將陣列提取到 $GLOBALS 之外的其他目的地(例如其他陣列或物件),則可以使用此函式。
與 extract 唯一的區別在於,extract_to 會將 $arr 的陣列指標移動到結尾,因為 $arr 是以傳參考方式傳遞以支援 EXTR_REFS 標誌。

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


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

if( !
is_array( $arr ) ) return trigger_error("extract_to(): 第一個參數應該是陣列", E_USER_WARNING );

if(
is_array( $to ) ) $t=0;
else if(
is_object( $to ) ) $t=1;
else return
trigger_error("extract_to(): 第二個參數應該是陣列或物件", E_USER_WARNING );

if(
$type==EXTR_PREFIX_SAME || $type==EXTR_PREFIX_ALL || $type==EXTR_PREFIX_INVALID || $type==EXTR_PREFIX_IF_EXISTS )
if(
$prefix===false ) return trigger_error("extract_to(): 預期指定前綴", E_USER_WARNING );
else
$prefix .= '_';

$i=0;
foreach(
$arr as $key=>$val ){

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

if( (
$type==EXTR_SKIP && $isset )
|| (
$type==EXTR_IF_EXISTS && !$isset ) )
continue;

else if( (
$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;

else if(
$type==EXTR_PREFIX_IF_EXISTS )
if(
$isset ) $nkey = $prefix.$key;
else continue;

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

if(
$t==1 )
if(
$type & EXTR_REFS ) $to->$nkey = &$arr[$key];
else
$to->$nkey = $val;
else
if(
$type & EXTR_REFS ) $to[$nkey] = &$arr[$key];
else
$to[$nkey] = $val;

$i++;
}

return
$i;
}

// e.g.:
extract_to( $myarray, $myobject, EXTR_IF_EXISTS );
?>
3
benjaminATwebbutvecklarnaDOTse
16 年前
回覆:anon at anon dot org,關於 extract() 和 null 值

我個人發現,從資料庫提取多個結果集時,如果變數不是 null(並且可選地如果它不 > 0),後面的結果集會覆蓋前面的結果集,這很有用。

如果 $extract_type 可以擴展到這兩種情況之上,那就很有用了

EXTR_OVERWRITE
EXTR_SKIP

像這樣

EXTR_OVERWRITE_NULL
- 如果發生衝突,則覆寫現有的變數(如果它是 null)

EXTR_OVERWRITE_0
- 相同的情況,但 == 0 或 null

EXTR_SKIP_NULL
- 如果發生衝突,則跳過新的變數(如果現有的變數不是 null)

EXTR_SKIP_0
- 相同的情況,但 == 0 或 null

這些應該涵蓋一些現在沒有涵蓋的良好案例。
4
bob
9 年前
請注意,extract() 只會在目前範圍內建立或覆寫變數,所以
<?
function test(){
$a=Array('b'=>1,'c'=>2);
extract($a);
}
test();
exit("$b");
?>
將不會產生輸出,而
<?
function test(){
global $b;
$a=Array('b'=>1,'c'=>2);
extract($a);
}
test();
exit("$b");
?>
將會輸出 1。
1
Hayley Watson
16 年前
Dan O'Donnell 的建議需要第三個要求才能如所述般運作

c) 沒有定義其他變數 - 特別是包含潛在敏感資訊的變數。

如果沒有這個條件,extract() 和手動指定變數之間的差異(以及由此產生的安全隱憂)應該是很明顯的。

那裡唯一有效的安全步驟是 (b) - 但您應該無論如何都這樣做。
1
owk dot ch199_ph at gadz dot org
18 年前
如果您想要在 PHP 5 中使用簡單的方式來通過參照提取 $V,請嘗試以下操作
<?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')); ?>」,因為當函式開始時,陣列尚未建立,因此無法被函式參照。
4
ktwombley at gmail dot com
18 年前
使用 extract() 對 $_REQUEST、$_GET 等開啟安全漏洞非常容易。您必須非常確定自己在做什麼,並在 extract() 上使用適當的旗標,以避免覆寫重要的變數。

例如,kake26 at gmail dot com 的提交不僅可以完美地模擬 register globals(這很糟糕),而且它會將其儲存在資料庫中,並在每次腳本執行時回憶相同的變數(基本上允許攻擊者通過一次攻擊在每次腳本執行時攻擊您的腳本)。糟糕!

若要修正此問題,您必須有創意地使用旗標。例如,您可以改用 EXTR_PREFIX_ALL 而不是 EXTR_OVERWRITE。當然,您也應該對表單元素進行消毒,以確保其中沒有 php 程式碼,並確保任何非常重要的變數不在表單資料中。(例如經典的 $is_admin = true 攻擊)
2
Adam Monsen <adamm at wazamatta dot com>
20 年前
如範例所示,如果使用了您的「前綴」,則會在提取的變數名稱中新增一個底線。也就是說,前綴為「p」會變成前綴「p_」,因此帶有前綴的「blarg」會變成「p_blarg」。

如果您不確定透過提取建立的變數,您可以呼叫 get_defined_vars() 來查看目前範圍內定義的所有變數。
2
Anonymous
19 年前
為了使其完全清楚(希望如此),當字串帶有前綴時,始終會新增一個底線。
extract(array("color" => "blue"),EXTR_PREFIX_ALL,'');// 注意:前綴為空

$color='_blue';
2
Dutchdavey
17 年前
請注意本頁最末端關於「前綴」的使用者注意事項。使用者指出 php 會在你的前綴中加上一個底線「_」。
1
anon at anon dot org
19 年前
關於 extract() 和 null 值的警告。

這可能是一個實際的 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();
}

如果任何已賦予的值為 null (假設在此情況下,$title 沒有在上面初始化),它會導致引擎執行各種極度怪異的事情,例如以難以置信且不一致的方式失去對變數的追蹤。我追蹤到問題的根源在於它使用了 EXTR_REFS 旗標。我猜想在 PHP 的內部變數儲存或參考計數機制中,嘗試擷取 null 參考會使其失去對某些東西的追蹤或計數。

簡而言之,如果您在使用 extract() 時開始出現奇怪的行為,請確保您嘗試从中獲取變數的陣列或物件不包含 null 的鍵或值!
0
auto493097 at hushmail dot com
14 年前
我使用 XDebug 和 NetbeansIDE 來分析和開發 PHP 程式碼。在偵錯 extract 語句時,變數列表中沒有出現任何新變數。雖然所有由 extract 建立的變數都可以透過明確的監看項目來檢查,而且一旦 PHP 腳本使用單一變數,該變數就會出現,我不確定這是一個錯誤的設定、一個功能還是 XDebug 中的一個錯誤。
0
Aaron Stone
20 年前
如果您正在移植較舊的應用程式,並採用上述建議,僅擷取 _SERVER、_SESSING、_COOKIE、_POST、_GET,您已經忘記擷取 _FILES 了。將 _FILES 放在最後並使用 EXTR_SKIP 無效,因為檔案上傳欄位的名稱已經設定為變數,該變數僅包含來自早期擷取的上傳檔案的暫時名稱 (我還沒有測試過具體是哪一個)。一個解決方法是將 _FILES 放在最後並使用 EXTR_OVERWRITE。這樣 extract 就可以將該僅含暫時名稱的變數替換為完整的檔案上傳資訊陣列。
-1
darrenforster99 at gmail dot com
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 ) ;
?>

現在您可以看到這裡存在一個巨大的安全問題嗎...?

如果我們有一個像這樣的網址,似乎一切都很好:

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

因為這樣會明確找到住在任何街道 20 號的 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 會將 {NAME} 和 {ADDRESS} 替換為來自 $_GET 的 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 一起使用,從而導致嚴重的安全風險!
-2
pg dot perfection at gmail dot com
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);
}
}
}
?>
-4
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'
?>
-5
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