【PHP】isset __get always return false
問題:
這篇文章要講的是 PHP 中常見的誤用,主要觀念上沒有釐清所導致的錯誤。我們先看看下面一段 PHP (ver 7.2.34) 程式碼:
class PropertyTest
{
private $data = array();
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
}
$obj = new PropertyTest;
$obj->propertyA = true;
var_dump($obj->propertyA);
var_dump(isset($obj->propertyA));
unset($obj->propertyA);
var_dump($obj->propertyA);
var_dump(isset($obj->propertyA));
$obj->propertyA = [true];
var_dump($obj->propertyA[0]);
var_dump(isset($obj->propertyA[0]));
|
程式碼中的六個 var_dump 結果是多少呢?可以思考過後再看解答。
.
.
.
.
.
.
.
.
.
.
$obj->propertyA = true;
var_dump($obj->propertyA); // bool(true)
var_dump(isset($obj->propertyA)); // bool(false)
unset($obj->propertyA);
var_dump($obj->propertyA); // bool(true)
var_dump(isset($obj->propertyA)); // bool(false)
$obj->propertyA = [true];
var_dump($obj->propertyA[0]); // bool(true)
var_dump(isset($obj->propertyA[0])); // bool(true)
|
不知道大家是否有答對。我自己是答錯後去查看官方文件才搞清楚的,也就是這樣才會有這篇文章的誕生,接下來會解釋這六個輸出是怎麼計算出來的。
解答:
在 PHP 語言中,class 可以宣告有一組特別的 methods 稱作 magic methods,magic method 會在特定情況觸發自動呼叫,並改變 (複寫) 該情況的預設行為。其中最常見的就是 __construct(),其作用就是建構子。
而今天我們探討的是 __get()、__set()、__isset()、__unset() 這四個 magic methods。我們來看一下他們的官方介紹。
__set() is run when writing data to inaccessible (protected or private) or non-existing properties. __get() is utilized for reading data from inaccessible (protected or private) or non-existing properties. __isset() is triggered by calling isset() or empty() on inaccessible (protected or private) or non-existing properties. __unset() is invoked when unset() is used on inaccessible (protected or private) or non-existing properties. |
整理成表格後:
magic method |
觸發情況 |
__set() |
寫入無法存取或不存在的 property 時 |
__get() |
讀取無法存取或不存在的 property 時 |
__isset() |
將無法存取或不存在的 property 傳入 isset() 或 empty() |
__unset() |
將無法存取或不存在的 property 傳入 unset() |
清楚知道正確定義後就可以來解釋輸出了
$obj->propertyA = true;
var_dump($obj->propertyA); // bool(true)
var_dump(isset($obj->propertyA)); // bool(false)
|
- 寫入無法存取或不存在的 property 後呼叫 __set('propertyA', true)。
- 第一個輸出:讀取無法存取或不存在的 property 後呼叫 __get('propertyA') 取得上一行 __set() 設定的 true。
- 第二個輸出:將無法存取或不存在的 property 傳入 isset() 後嘗試呼叫 __isset('propertyA'),但是範例中並沒有實作 __isset(),故回傳 false 表示 $obj 中並沒有設定 propertyA 這個 property。
所以如果希望 isset() 可以判斷 __get()、__set() 操作的動態 property 是否被設置,需要自己實作 __isset()。
unset($obj->propertyA);
var_dump($obj->propertyA); // bool(true)
var_dump(isset($obj->propertyA)); // bool(false)
|
將無法存取或不存在的 property 傳入 unset() 後嘗試呼叫 __unset('propertyA'),但是範例中並沒有實作 __unset() 且 $obj 中也沒有 propertyA 這個 property,所以沒有真的移除被 __set() 設定的 true,故第三第四輸出結果分別與第一第二輸出結果相同。
所以如果希望 unset() 可以移除 __get()、__ser() 操作的動態 property ,需要自己實作 __unset()。
$obj->propertyA = [true];
var_dump($obj->propertyA[0]); // bool(true)
var_dump(isset($obj->propertyA[0])); // bool(true)
|
- 寫入無法存取或不存在的 property 後呼叫 __set('propertyA', [true])。
- 第五個輸出:讀取無法存取或不存在的 property 後呼叫 __get('propertyA') 取得上一行 __set() 設定的 [true],再取其 0 offset 得 true。
- 第六個輸出:讀取無法存取或不存在的 property 後呼叫 __get('propertyA') 取得上一行 __set() 設定的 [true],isset() 再判斷 [true] 的 0 offset 有無被設定,得 true。
第六個輸出補充一下,他並不會呼叫 __isset() 是因為被傳入 isset() 判斷的東西已經跟 $obj 無關了,判斷的是 __get('propertyA') 回傳來的 [true] 的 0 offset。
實際範例:
class PropertyTest
{
private $data = array();
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __isset($name)
{
return isset($this->data[$name]);
}
public function __unset($name)
{
unset($this->data[$name]);
}
}
$obj = new PropertyTest;
$obj->propertyA = true;
var_dump($obj->propertyA); // bool(true)
var_dump(isset($obj->propertyA)); // bool(true)
unset($obj->propertyA);
var_dump($obj->propertyA); // NULL
var_dump(isset($obj->propertyA)); // bool(false)
$obj->propertyA = [true];
var_dump($obj->propertyA[0]); // bool(true)
var_dump(isset($obj->propertyA[0])); // bool(true)
|
這一次我們實作了 __isset() 與 __unset(),讓 __get()、__set() 操作的動態 property 可以被判斷是否設定與移除,輸出結果也與預期中相符。希望這篇文章可以讓大家更了解 magic methods "對應無法存取或不存在的 property" 時的使用方式。
參考資料:
[1] PHP: Magic Methods - Manual
https://www.php.net/manual/en/language.oop5.magic.php
[2] PHP: isset - Manual
https://www.php.net/manual/en/function.isset
[3] Weird PHP magic getter on an array - Stack Overflow
https://stackoverflow.com/questions/35032578/weird-php-magic-getter-on-an-array