close

title.png

【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

 

arrow
arrow
    創作者介紹
    創作者 迷宮兔 的頭像
    迷宮兔

    兔窩

    迷宮兔 發表在 痞客邦 留言(0) 人氣()