PHP Conference Japan 2024

識別參照

PHP 中的許多語法結構都是透過參照機制實作的,因此這裡所有關於參照繫結的內容也適用於這些結構。有些結構,例如以參照方式傳遞和返回,已在上面提及。其他使用參照的結構有

全域參照

當您宣告一個變數為 global $var 時,實際上是建立一個全域變數的參照。這表示,這與下列程式碼相同:

<?php

$var
=& $GLOBALS["var"];

?>

這也表示取消設定 $var 不會取消設定全域變數。

新增註解

使用者貢獻的註解 8 個註解

21
BenBE at omorphia dot de
17 年前
您好,

如果您想檢查兩個變數是否彼此參照(即指向相同的記憶體),您可以使用像這樣的函數:

<?php

function same_type(&$var1, &$var2){
return
gettype($var1) === gettype($var2);
}

function
is_ref(&$var1, &$var2) {
//如果存在參照,則型別相同
if(!same_type($var1, $var2)) {
return
false;
}

$same = false;

//我們現在只需要要求 $var1 是一個陣列 ;-)
if(is_array($var1)) {
//在 $var1 中尋找未使用的索引
do {
$key = uniqid("is_ref_", true);
} while(
array_key_exists($key, $var1));

//兩個變數的內容不同 ... 它們不可能相同
if(array_key_exists($key, $var2)) {
return
false;
}

//如果變更反映在 $var2 中,則陣列指向相同的資料
$data = uniqid("is_ref_data_", true);
$var1[$key] =& $data;
//似乎有修改 ...
if(array_key_exists($key, $var2)) {
if(
$var2[$key] === $data) {
$same = true;
}
}

//復原我們的變更 ...
unset($var1[$key]);
} elseif(
is_object($var1)) {
//相同的物件必須具有相同的類別名稱 ;-)
if(get_class($var1) !== get_class($var2)) {
return
false;
}

$obj1 = array_keys(get_object_vars($var1));
$obj2 = array_keys(get_object_vars($var2));

//在 $var1 中尋找未使用的索引
do {
$key = uniqid("is_ref_", true);
} while(
in_array($key, $obj1));

//兩個變數的內容不同 ... 它們不可能相同
if(in_array($key, $obj2)) {
return
false;
}

//如果變更反映在 $var2 中,則陣列指向相同的資料
$data = uniqid("is_ref_data_", true);
$var1->$key =& $data;
//似乎有修改 ...
if(isset($var2->$key)) {
if(
$var2[$key] === $data) {
$same = true;
}
}

//復原我們的變更 ...
unset($var1->$key);
} elseif (
is_resource($var1)) {
if(
get_resource_type($var1) !== get_resource_type($var2)) {
return
false;
}

return ((string)
$var1) === ((string) $var2);
} else {
//簡單變數 ...
if($var1!==$var2) {
//資料不符 ... 它們不可能相同 ...
return false;
}

//為了檢查具有簡單型別的變數的參照
//只需儲存其舊值,並針對第二個變數的修改進行檢查 ;-)

do {
$key = uniqid("is_ref_", true);
} while(
$key === $var1);

$tmp = $var1; //這裡我們需要一份副本!!!
$var1 = $key; //將 var1 設定為 $key 的值(副本)
$same = $var1 === $var2; //檢查 $var2 是否也被修改 ...
$var1 = $tmp; //復原我們的變更 ...
}

return
$same;
}

?>

雖然此實作相當完整,但它目前無法處理函數參照和其他一些小東西。
如果您想手動序列化遞迴陣列,此函數特別有用。

用法類似這樣:
<?php
$a
= 5;
$b = 5;
var_dump(is_ref($a, $b)); //false

$a = 5;
$b = $a;
var_dump(is_ref($a, $b)); //false

$a = 5;
$b =& $a;
var_dump(is_ref($a, $b)); //true
echo "---\n";

$a = array();
var_dump(is_ref($a, $a)); //true

$a[] =& $a;
var_dump(is_ref($a, $a[0])); // true
echo "---\n";

$b = array(array());
var_dump(is_ref($b, $b)); //true
var_dump(is_ref($b, $b[0])); //false
echo "---\n";

$b = array();
$b[] = $b;
var_dump(is_ref($b, $b)); //true
var_dump(is_ref($b, $b[0])); //false
var_dump(is_ref($b[0], $b[0][0])); //true*
echo "---\n";

var_dump($a);
var_dump($b);

?>

* 請注意 PHP 的內部行為,它似乎在實際複製變數 *之前* 就進行了參考賦值!!!因此,對於最後一個測試案例,你會得到一個包含(不同的)遞迴陣列的陣列,而不是你可能預期的包含一個空陣列的陣列。

BenBE.
7
ludvig dot ericson at gmail dot com
18 年前
為了清楚起見

$this 是一個虛擬變數 - 因此不是真正的變數。ZE 以不同於普通變數的方式處理它,這意味著一些進階的變數操作無法對其起作用(原因很明顯)

<?php
class Test {
var
$monkeys = 0;

function
doFoobar() {
$var = "this";
$
$var->monkeys++; // 將在這行失敗。
}
}

$obj = new Test;
$obj->doFoobar(); // 將在此呼叫中失敗。
var_dump($obj->monkeys); // 如果甚至到達這裡,將返回 int(0)。
?>
3
ksamvel at gmail dot com
18 年前
可以使用簡單的運算子 == (物件) 來檢查對任何物件的參考。範例:

<?php
class A {}

$oA1 = new A();

$roA = & $oA1;

echo
"roA 和 oA1 是 " . ( $roA == $oA1 ? "相同" : "不相同") . "<br>";

$oA2 = new A();
$roA = & $roA2;

echo
"roA 和 oA1 是 " . ( $roA == $oA1 ? "相同" : "不相同") . "<br>";
?>

輸出

roA 和 oA1 是 相同
roA 和 oA1 是 不相同

當使用繼承並且只需要複製擴展類別的基本部分時(類似於 C++:oB = oA),目前的技術可能對物件中的快取很有用。

<?php
class A {
/* 任何改變 A 狀態的函式都應將 $bChanged 設定為 true */
public function isChanged() { return $this->bChanged; }
private
$bChanged;
//...
}

class
B extends A {
// ...
public function set( &$roObj) {
if(
$roObj instanceof A) {
if(
$this->roAObj == $roObj &&
$roObj->isChanged()) {
/* 物件未變更,不需要複製 B 的 A 部分 */
} else {
/* 複製 B 的 A 部分 */
$this->roAObj = &$roObj;
}
}
}

private
$roAObj;
}
?>
3
Sergio Santana: ssantana at tlaloc dot imta dot mx
19 年前
有時需要物件的方法返回對其自身的參考。這是一種編寫方法的方式

<?php
class MyClass {
public
$datum;
public
$other;

function &
MyRef($d) { // 這個方法
$this->datum = $d;
return
$this; // 返回參考
}
}

$a = new MyClass;
$b = $a->MyRef(25); // 建立參考

echo "這是物件 \$a: \n";
print_r($a);
echo
"這是物件 \$b: \n";
print_r($b);

$b->other = 50;

echo
"這是物件 \$a,透過修改參考 \$b 間接修改: \n";
print_r($a);
?>

這段程式碼的輸出為
這是物件 $a
MyClass 物件
(
[datum] => 25
[other] =>
)
這是物件 $b
MyClass 物件
(
[datum] => 25
[other] =>
)
這是物件 $a,透過修改參考 $b 間接修改
MyClass 物件
(
[datum] => 25
[other] => 50
)
1
Sergio Santana: ssantana at tlaloc dot imta dot mx
18 年前
*** 關於物件的棘手參考的警告 ***
-----------------------------------------------
在類別的上下文中,參考的使用
和物件,雖然在文件中定義得很清楚,
但有些棘手,因此在使用時必須非常小心
使用物件。讓我們檢視以下兩個
範例

<?php
class y {
public
$d;
}

$A = new y;
$A->d = 18;
echo
"操作前的 \$A 物件:\n";
var_dump($A);

$B = $A; // 這不是明確的 (=&) 參考賦值,
// 然而,$A 和 $B 參考到相同的實例
// 雖然它們不是等效的名稱
$C =& $A; // 明確的參考賦值,$A 和 $C 參考到
// 相同的實例,並且它們已經變成同一個
// 實例的等效名稱

$B->d = 1234;

echo
"\n操作後的 \$B 物件:\n";
var_dump($B);
echo
"\n操作後隱式修改的 \$A 物件:\n";
var_dump($A);
echo
"\n操作後隱式修改的 \$C 物件:\n";
var_dump($C);

// 讓 $A 參考到另一個實例
$A = new y;
$A->d = 25200;
echo
"\$A 修改後的 \$B 物件:\n";
var_dump($B); // $B 沒有改變
echo "\$A 修改後的 \$A 物件:\n";
var_dump($A);
echo
"\$A 修改後隱式修改的 \$C 物件:\n";
var_dump($C); // $C 隨著 $A 的改變而改變
?>

因此,請注意 $X = $Y 和 $X =& $Y 賦值之間的差異。
當 $Y 不是物件實例時,第一個賦值表示
$X 將會持有 $Y 的獨立副本,而第二個賦值表示
$X 和 $Y 將會參考到相同的東西,所以它們會緊密相連直到
其中 $X 或 $Y 被強制參考到另一個東西。然而,當 $Y
剛好是一個物件實例時,$X = $Y 的語義會改變並且
只與 $X =& $Y 的語義稍微不同,因為在這兩種
情況下,$X 和 $Y 都會變成參考到同一個物件。看看這個
範例的輸出結果

操作前的 $A 物件
object(y)#1 (1) {
["d"]=>
int(18)
}

操作後的 $B 物件
object(y)#1 (1) {
["d"]=>
int(1234)
}

操作後隱式修改的 $A 物件
object(y)#1 (1) {
["d"]=>
int(1234)
}

操作後隱式修改的 $C 物件
object(y)#1 (1) {
["d"]=>
int(1234)
}

$A 修改後的 $B 物件
object(y)#1 (1) {
["d"]=>
int(1234)
}

$A 修改後的 $A 物件
object(y)#2 (1) {
["d"]=>
int(25200)
}

$A 修改後隱式修改的 $C 物件
object(y)#2 (1) {
["d"]=>
int(25200)
}

讓我們回顧第二個範例
<?php
class yy {
public
$d;
function
yy($x) {
$this->d = $x;
}
}

function
modify($v)
{
$v->d = 1225;
}

$A = new yy(3);
var_dump($A);
modify($A);
var_dump($A);
?>

雖然,一般來說,在上面顯示的 'modify' 函式中宣告為
的 $v,意味著在呼叫函式時傳遞的
實際參數 $A 不會被修改,當 $A 是物件
實例時,情況並非如此。看看執行時
範例程式碼的輸出結果
example code outputs when executed

object(yy)#3 (1) {
["d"]=>
int(3)
}
object(yy)#3 (1) {
["d"]=>
int(1225)
}
1
CodeWorX.ch
13 年前
這是一種非傳統(而且不是很快速)的方法來檢測陣列中的參考

<?php

function is_array_reference ($arr, $key) {
$isRef = false;
ob_start();
var_dump($arr);
if (
strpos(preg_replace("/[ \n\r]*/i", "", preg_replace("/( ){4,}.*(\n\r)*/i", "", ob_get_contents())), "[" . $key . "]=>&") !== false)
$isRef = true;
ob_end_clean();
return
$isRef;
}

?>
1
biziclop
7 個月前
這是另一個用於檢查兩個變數是否彼此參考的函式。
BenBE 的函式運作正常,但它非常長而且可能很慢。
這是一個更短、更快且希望沒有錯誤的替代方案。
至少適用於 PHP 4.3.0 到 8.3.4:https://3v4l.org/5JNSl

<?php

function is_same_ref( & $a, & $b ){
// 儲存原始值
$ori_a = $a;
$ori_b = $b;
// 給它們不同的值
$a = 1;
$b = 2;
// 比較它們,如果它們相等,則它們彼此參考
$eq = $a === $b;
// 還原原始值
$a = $ori_a;
$b = $ori_b;
return
$eq;
}

$a = array(1,2);
$b =& $a;
$c =& $a; // 用於檢查副作用
var_dump( is_same_ref( $a, $b ));
// bool(true)

$a[] = 3;
$b[] = 4;
// 在 is_same_ref() 內部以各種方式操作我們的陣列後,
// 所有三個變數仍然參考到同一個陣列:
var_dump( implode( $a ), implode( $b ), implode( $c ));
// string(4) "3456"
// string(4) "3456"
// string(4) "3456"
?>
-1
Abimael Rodrguez Coln
13 年前
這是一種檢查是否為參考的方法
<?php
$a
= 1;
$b =& $a;
$c = 2;
$d = 3;
$e = array($a);
function
is_reference($var){
$val = $GLOBALS[$var];
$tmpArray = array();
/**
* 不使用參考的方式加入鍵/值
*/
foreach($GLOBALS as $k => $v){
if(!
is_array($v)){
$tmpArray[$k] = $v;
}
}

/**
* 變更其他變數的值
*/
foreach($GLOBALS as $k => $v){
if(
$k != 'GLOBALS'
&& $k != '_POST'
&& $k != '_GET'
&& $k != '_COOKIE'
&& $k != '_FILES'
&& $k != $var
&& !is_array($v)
){
usleep(1);
$GLOBALS[$k] = md5(microtime());
}
}

$bool = $val != $GLOBALS[$var];

/**
* 還原預設值
*/
foreach($tmpArray as $k => $v){
$GLOBALS[$k] = $v;
}

return
$bool;
}
var_dump(is_reference('a'));
var_dump(is_reference('b'));
var_dump(is_reference('c'));
var_dump(is_reference('d'));
?>

這不會檢查參考是否在陣列內部。
To Top