Introduce to php magic method: __get & __set
本篇為PHP 系列
接續PHP魔術方法系列,
此次要講的是,可以讓操作資料更彈性的 __get
及 __set
,
先來看看官網定義:
__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.
簡單來說,當存取到不存在(non-existing)、或沒有讀取權限(protected or private)的屬性(property)時,就會自動觸發__get
或__set
。
先看__get
先來看一個使用__get的範例·:
class Fruit
{
public $lemon = 'goodLemon';
private $cherry = 'goodCherry';
protected $orange = 'goodOrange';
public function __get($name)
{
return "[__get] $name";
}
}
$fruit = new Fruit();
// 1. public
// → string(5) "goodLemon"
var_dump($fruit->lemon);
// 2. inaccessible(pivate)
// → string(14) "[__get] cherry"
var_dump($fruit->cherry);
// 3. inaccessible(protected)
// → string(14) "[__get] orange"
var_dump($fruit->orange);
// 4. non-existing
// → string(13) "[__get] apple"
var_dump($fruit->apple);
如範例所見,
lemno
是public的屬性,所以可以正常讀取。cherry
是private,外界基本上是沒辦法讀取的,因此觸發了__get
魔術方法。orange
是protected,外f界基本上是沒辦法讀取的,因此觸發了__get
魔術方法。- 第四個範例的apple,基本上根本沒有宣告過,所以是不存在的,也觸發了
__get
魔術方法。
補充:
__get
的第一個參數$name
就是使用者所呼叫的屬性(property)名稱,例如,當使用者呼叫$fruit->cherry
時,__get
收到的第一個參數就是'cherry'
字串。
再看__set
一樣直接來看__set的範例:
class Fruit
{
public $lemon = '';
private $cherry = '';
protected $orange = '';
public function __set($name, $value)
{
echo "[__set] $name, $value".PHP_EOL;
}
}
$fruit = new Fruit();
// 1. public
// → 正常
$fruit->lemon = 'goodLemon';
// 2. pivate
// → [__set] cherry, goodCherry
$fruit->cherry = 'goodCherry';
// 3. protected
// → [__set] orange, goodOrange
$fruit->orange = 'goodOrange';
// 4. non-existing
// → [__set] apple, goodApple
$fruit->apple = 'goodApple';
如範例所見,
lemno
是public的屬性,所以可以正常寫入。cherry
是private,外界基本上是沒辦法寫入的,因此觸發了__set
魔術方法。orange
是protected,外界基本上是沒辦法寫入的,因此觸發了__set
魔術方法。- 第四個範例的apple,基本上根本沒有宣告過,所以是不存在的,也觸發了
__set
魔術方法。
補充:
__set
的第一個參數$name
一樣是屬性的名稱,第二個參數$value
則是想要設定的值,例如$fruit->cherry = 'goodCherry'
,$value
就是字串'goodCherry'
。
get、set放在一起看
大家都知道, 一般來說,是無法讀取private屬性的,所以像下面範例一樣,直接讀取private $apple,程式會直接噴錯。
class Fruit
{
private $apple;
}
$fruit = new Fruit();
// PHP Fatal error: Uncaught Error: Cannot access private property Fruit::$apple
// → 程式終止
var_dump($fruit->apple);
$fruit->apple = 'apple';
var_dump($fruit->apple);
因此,為了要完成這段程式,我們需要同時利用__get
及__set
。
class Fruit
{
private $apple;
public function __get($name)
{
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
}
$fruit = new Fruit();
// 1. NULL
var_dump($fruit->apple);
// 2. 成功
$fruit->apple = 'apple';
// 3. string(5) "apple"
var_dump($fruit->apple);
- 第一次呼叫
apple
的時候,雖然它仍然是private的屬性,但因為這次有__get
了,所以不會報錯,得到了NULL
。 - 我們assign一個值給
apple
,透過__set
,我們成功把值賦與了變數$apple
。 - 再一次呼叫apple,因為前面成功assign,這次可以成功拿到字串
apple
。
補充:
$this->$name
,這樣的用法,是因為php的語法特性,可以把變數中所包含的字串取出,作為變數名稱。所以當$name為'apple'時,$this->$name
等同於$this->apple
。
Laravel中的應用
其實__get
、__set
在Laravel中,有相當廣泛的用運用,而其中有一個應用是,大家基本上都會用到的,那就是Model。
不知道大家有沒有好奇過,為什麼Laravel中的Eloquent Model,可以的取得db中任意欄位的值?為什麼不需要在Model中做事先的宣告?
$user = User::first();
$user->email;
像這個範例,你並不用手動去User Model
的類別新增email屬性,就可以直接透過user model物件取得email。
其實這就是__get
、__set
在其中搞怪。
如果我們把Laravel Model的程式找出來,會發現這段:
// 路徑:/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/**
* Dynamically retrieve attributes on the model.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->getAttribute($key);
}
/**
* Dynamically set attributes on the model.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
就會發現Laravel其實也是透過魔術方法,去幫你取得各種屬性的!
當我們要讀取email時,因為user model中並不存在這個屬性,因此會觸發__get
,再藉由getAttribute
方法,去找出真正的值。
什麼時候要使用get或set?
類別的是動態產生的,難以事前宣告,例如前面提到的Laravel的Eloquent Model。
需要透過一些特殊的方式來存取property,可藉由
__get
、__set
的來一致性地處理。function __get($eky) { return $this->doSomethingBeforeGet($this->$key); } function __set($eky, $value) { return $this->$key = $this->doSomethingBeforeSet($value); }
property傳遞,一個主類別可以注入許多小類別,當想取得的property不屬於主類別時,可以藉由
__get
將請求傳遞到小類別,並取得小類別的的property。function __get($eky) { return $this->dataStoreManager->get($key); } function __set($eky, $value) {fs return $this->dataStoreManager->set($eky, $value); }
小結
__get
及__set
這兩個魔術方法,可以讓開發者更彈性地的操作資料,小缺點就是debug的時候可能比較困難,因為有些事情被魔術般地完成了。
但如果可以克服這個缺點,就可以善用這些特性,設計出更有彈性跟變化的類別,來滿足實際的需求。
環境
PHP 7.4.12
延伸
認識PHP魔術方法: __call看更多PHP系列