在foreach循環中,如果我們需要更改迭代的元素或是為了提高效率,運用引用是一個好辦法:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
這裡有個問題很多人會迷糊。循環結束後,$value並未銷毀,$value其實是數組中最後一個元素的引用,這樣在後續對$value的使用中,如果不知道這一點,會引發一些莫名奇妙的錯誤:)看看下面這段代碼:
$array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (i.e., copy) echo implode(',', $array), "\n";
上面代碼的運行結果如下:
1,2,3 1,2,3 1,2,2
你猜對了嗎?為什麼是這個結果呢?
我們來分析下。第一個循環過後,$value是數組中最後一個元素的引用。第二個循環開始:
綜上,最終結果就是1,2,2
避免這種錯誤最好的辦法就是在循環後立即用unset函數銷毀變量:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
對於isset()函數,變量不存在時會返回false,變量值為null時也會返回false。這種行為很容易把人弄迷糊。。。看下面的代碼:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
寫這段代碼的人本意可能是如果$data[‘keyShouldBeSet’]未設置,則執行對應邏輯。但問題在於即使$data[‘keyShouldBeSet’]已設置,但設置的值為null,還是會執行對應的邏輯,這就不符合代碼的本意了。
下面是另外一個例子:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
上 面的代碼假設$_POST[‘active’]為真,那麼$postData應該被設置,因此isset($postData)會返回true。反之,上 面代碼假設isset($postData)返回false的唯一途徑就是$_POST[‘active’]也返回false。
真是這樣嗎?當然不是!
即使$_POST[‘active’]返回true,$postData也有可能被設置為null,這時isset($postData)就會返回false。這就不符合代碼的本意了。
如果上面代碼的本意僅是檢測$_POST[‘active’]是否為真,下面這樣實現會更好:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
判斷一個變量是否真正被設置(區分未設置和設置值為null),array_key_exists()函數或許更好。重構上面的第一個例子,如下:
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
另外,結合get_defined_vars()函數,我們可以更加可靠的檢測變量在當前作用域內是否被設置:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
考慮下面的代碼:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
運行上面的代碼,將會輸出下面的內容:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
問題出在哪呢?問題就在於上面的代碼混淆了返回值和返回引用。在PHP中,除非你顯示的指定返回引用,否則對於數組PHP是值返回,也就是數組的拷貝。因此上面代碼對返回數組賦值,實際是對拷貝數組進行賦值,非原數組賦值。
// getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];
下面是一種可能的解決辦法,輸出拷貝的數組,而不是原數組:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
如果你就是想要改變原數組,也就是要反回數組引用,那應該如何處理呢?辦法就是顯示指定返回引用即可:
class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
經過改造後,上面代碼將會像你期望那樣會輸出test。
我們再來看一個例子會讓你更迷糊的例子:
class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
如果你想的是會和上面一樣輸出“ Undefined index”錯誤,那你就錯了。代碼會正常輸出“test”。原因在於PHP對於對象默認就是按引用返回的,而不是按值返回。
綜上所述,我們在使用函數返回值時,要弄清楚是值返回還是引用返回。PHP中對於對象,默認是引用返回,數組和內置基本類型默認均按值返回。這個要與其它語言區別開來(很多語言對於數組是引用傳遞)。
像其它語言,比如java或C#,利用getter或setter來訪問或設置類屬性是一種更好的方案,當然PHP默認不支持,需要自己實現:
class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'
上面的代碼給調用者可以訪問或設置數組中的任意值而不用給與數組public訪問權限。感覺怎麼樣:)
*