PHP 的引用傳遞與多階層分類
在 PHP 中,我們可以使用等號 ( =
) 將一個值賦予給一個新的變數(應該很多程式語言都是這樣)。
$a = 'Hello';
$b = $a;
這時候 $b
會新增一個記憶體位址來存放值,如果對 $b
進行修改,並不會影響到 $a
的值。
$a = 'foo';
$b = $a;
$a = 'bar';
// 'bar'
echo $a;
// 'foo'
echo $b
在 PHP 中,我們可以使用 &
,將不同的變數指向同一個內容。
例如下方使用 &
將 $a
的記憶體位址賦值給 $b
。這時候,如果對 $b
進行修改,$a
的值也會跟著一起變更。
$a = 'foo';
$b = &$a;
$a = 'bar';
// 'bar'
echo $a;
// 'bar'
echo $b
在 PHP 官方文件中介紹引用的部分,底下有一位大大的留言,對於引用的解釋非常到位。
It just likes a person who has two different names.
就像一個人 (值) 有兩個不一樣的名字 (變數名稱)。
函式中參數的引用
引用還可以使用在函式的參數中,呼叫函式並傳入變數時,就會傳入變數的引用給函式內的參數。
在函式內對參數所做的任何更動,都會影響到函式外部的變數。
function foo(&$var)
{
$var++;
}
$a = 5;
foo($a);
// $a is 6 here
echo $a;
只有兩種內容可以被引用。
- 變數
- 可以返回引用的函式,如
function &foo()
一般的常數如數字與字串是不能被引用的。
// error : const cannot be passed by reference
foo(1);
從下方的例子來看什麼是「可以返回引用的函式」,在函式名稱的前方加上 &
就代表此函式可以返回引用。
如果在呼叫 bar()
時,bar()
的前面沒有加上 &
,就代表函式返回的只是單純的值,那麼在 foo(bar())
就會發生錯誤,因為 foo()
的參數必須是可以被引用的內容。
function foo(&$var)
{
$var++;
}
function &bar()
{
$a = 5;
return $a;
}
// 如果 function &bar() 沒有加上 &,這裡就會拋出錯誤
foo(bar());
// 這樣才是正確的
foo(&bar());
「可以返回引用的函式」並不是代表一定會返回引用,而是根據是否使用 &
來判斷要不要返回引用。
function &test()
{
static $b = 0; // 宣告一個靜態變數
$b++;
echo $b;
return $b;
}
// $a 接收的是一般的返回值 $b
$a = test(); // 1
$a = 5;
$a = test(); // 2
// $a 接收的是返回值的引用,所以會與 $b 綁在一起
$a = &test(); // 3
$a = 5;
$a = test(); // 6
物件的引用
其實在 PHP 中,物件賦值給一個新的變數時,是透過引用的方式來賦值。
因此對新變數的任何變更,也會修改到原本的物件。
class a
{
public string $abc = "ABC";
}
$b = new a();
$c = $b;
echo $b->abc; // ABC
echo $c->abc; // ABC
$c->abc = "DEF";
echo $b->abc; // DEF
取消引用
當 unset 一個引用,只是斷開了變數名和變數內容之間的綁定。 這並不意味著變數內容被銷毀了。
$a = 1;
$b = &$a;
unset($a);
官方文件這裡用 unix 的 unlink 指令來類比。
使用引用將一維陣列轉換為多維陣列
舉一個日常生活中常見的例子,電商網站裡種類繁多的商品分類。
電商網站商品分類
│
├── 3C 產品
├── 奶粉
│ └── 進口奶粉
│ └── 澳洲進口奶粉
└── 水果
└── 蘋果
├── 紅蘋果
└── 青蘋果
通常在資料庫中儲存多階層的商品分類,我們會使用 parent_id 欄位,在 parent_id 中儲存父 id,以此知道該分類被分在哪個分類底下。
id | name | parent_id |
---|---|---|
1 | 水果 | 0 |
2 | 奶粉 | 0 |
3 | 蘋果 | 1 |
4 | 青蘋果 | 3 |
5 | 紅蘋果 | 3 |
6 | 進口奶粉 | 2 |
7 | 澳洲進口奶粉 | 6 |
8 | 3C 產品 | 0 |
因為資料表中只能儲存一維的資料,將資料撈取出來之後還需要將其整理為多維陣列,通常會使用遞迴來處理,但另外一種處理方式就是使用引用傳遞 。
例如我們可以在迴圈中使用引用傳遞,這樣在迴圈中對陣列元素的所有操作,都會影響到原本的陣列。
<?php
$array = [
'hello',
'world',
];
foreach ($array as &$item) {
if ($item === 'world') {
$item = 'foobar';
}
}
var_dump($array);
// array(2) {
// [0]=>
// string(5) "hello"
// [1]=>
// &string(6) "foobar"
// }
利用這個概念,我們就可以使用引用傳遞來將一維陣列轉換為多維陣列。
<?php
// 陣列的 key 值需要與 id 相等,方便比對
// parentId === 0 為第一層
$array = [
1 => ['id' => 1, 'parentId' => 0, 'name' => '水果'],
2 => ['id' => 2, 'parentId' => 0, 'name' => '奶粉'],
3 => ['id' => 3, 'parentId' => 1, 'name' => '蘋果'],
4 => ['id' => 4, 'parentId' => 3, 'name' => '青蘋果'],
5 => ['id' => 5, 'parentId' => 3, 'name' => '紅蘋果'],
6 => ['id' => 6, 'parentId' => 2, 'name' => '進口奶粉'],
7 => ['id' => 7, 'parentId' => 6, 'name' => '澳洲進口奶粉'],
8 => ['id' => 8, 'parentId' => 0, 'name' => '3C 產品'],
];
$tree = [];
// 這裡使用 &$item,當 $item 被加入子元素時,也會影響到原本 $array 中對應的 item
foreach ($array as &$item) {
// 判斷父元素是否存在
if (isset($array[$item['parentId']])) {
// 存在,將該元素的引用加入至父元素的 children 陣列
$array[$item['parentId']]['children'][] = &$item;
} else {
// 不存在,將該元素的引用加入至根陣列
$tree[] = &$item;
}
}
// 使用 JSON 查看轉換為多維陣列的結果
echo json_encode($tree, JSON_UNESCAPED_UNICODE);