關注這個專欄的其他相關筆記:[Web 安全] 反序列化漏洞 - 學習筆記-CSDN博客
PHP 魔術方法 - 簡介 - PHP 魔術方法 - 簡單教程,簡單編程PHP 中,以兩個下劃線 ( `__` ) 開頭方法稱之為 「 魔術方法 」 這些 「 魔術方法 」 在 [PHP](/l/yufei/php/php-basic-index.html) 中扮演這重要的角色,作為一名 PHP 開發人員,你必須知道它們,且會用它們 本專欄,我們就來看看和學習這些魔術方法,以及一些簡單的使用范例 ## PHP 魔術方法一覽 |方法名|說明| |:---|:---| |\__construct()類的構造函數 |\__destruct()| 類 - 簡單教程,簡單編程 https://twle.cn/c/yufei/phpmmethod/phpmmethod-basic-index.html
0x01:PHP 魔術方法簡介
在 PHP 中,以兩個下劃線(__
)開頭的方法就被稱為「 魔術方法 」。魔術方法是 PHP 中一個預定好的,在特定情況下會自動觸發的行為方法。 這些魔術方法在 PHP 中扮演著重要的角色,作為一名 PHP 開發人員,我們必須要掌握并且能熟練使用它們。下面,開始本章的學習,以下是常見的 PHP 魔術方法,及其作用簡介:
方法名 | 作用解析 |
---|---|
__construct() | 類的構造函數,創建對象時觸發 |
__destruct() | 類的析構函數,對象被銷毀時觸發 |
__call() | 當調用對象的一個不存在或不可訪問的方法時會自動調用 |
__callStatic() | 當調用對象或類的一個不存在或不可訪問的靜態方法時會自動調用 |
__get() | 調用不可訪問、不存在的對象成員屬性時觸發 |
__set() | 在給不可訪問、不存在的對象成員屬性賦值時觸發 |
__isset() | 當對不可訪問屬性調用 isset() 或 empty() 時觸發 |
__unset() | 當使用?reset() ?重制一個對象不存在的或不可訪問的屬性時會自動調用 |
__invoke() | 把對象當作函數調用時觸發 |
__sleep() | 執行 serialize() 函數前會先調用此方法。 |
__wakeup() | 執行 unserialize() 函數前會先調用此方法。 |
__toString() | 當把對象當成字符串調用時會觸發此方法 |
__clone() | 使用 clone 關鍵字拷貝完一個對象后觸發 |
__set_state() | 當使用 var_export() 將數組導出為變量時會自動調用 |
__autoload() | 嘗試自動加載一個未定義的類 |
__debugInfo() | 打印輸出調試信息,針對 var_dump() 函數 |
0x02:PHP 魔術方法 — __construct()
0x0201:方法簡介
PHP 構造函數 __construct()
是對象被創建后自動調用的第一個方法。
任何類都會有一個構造函數,當我們沒有顯示的聲明它時,系統其實已經為它創建了一個隱藏的默認的構造函數,這個默認的構造函數沒有任何參數,也不會執行任何代碼,等價于一個空函數。
一旦我們在類中顯式的聲明了一個構造函數,那么默認的構造函數就會消失,也可以說是我們創建的構造函數會覆蓋掉系統默認的構造函數。
0x0202:方法作用
構造函數通常用于執行一些初始化任務,例如在創建對象時設置成員變量的初始值。
0x0203:方法聲明
在類中聲明一個構造函數的語法格式一般如下:
?class ClassName {function __construct([parameter list]){// 函數主體,這里面通常用于初始化對象的一些屬性}}
注意:一個 PHP 類中只能有一個構造函數,因為 PHP 不允許進行函數重載!
0x0204:調用示例
下面的代碼聲明了一個 Dog
類,同時在該類中創建了一個構造函數,用于初始化對象的相應屬性:
?<?php?class Dog {public $name; // 姓名public $age; ?// 年齡?function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; ? // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}}?$dog = new Dog("旺財", "10"); // 實例化一只小狗
如上,可以看到,我們只是實例化了 Dog 類,并沒有主動調用類中的方法,__construct()
方法就自己調用了。
0x03:PHP 魔術方法 — __destruct()
0x0301:方法簡介
__destruct()
方法會在該類的一個對象被刪除時自動調用。一般情況下,該函數的觸發時機為:
-
主動調用
unset($obj)
。 -
主動調用
$obj = NULL
。 -
程序自動結束。
0x0302:方法作用
__destruct()
函數通常被用于對象執行完畢后進行釋放資源的操作,比如關閉文件、關閉數據庫鏈接、清空一個結果集等。
0x0303:方法聲明
在類中聲明 __destruct()
函數的語法格式如下,該函數沒有任何參數也沒有任何返回值:
?class ClassName {function __destruct() {// 其他代碼}}
0x0304:調用示例
在下面這個例子中,我們給 Dog
類添加上析構函數 __destruct()
,當對象走向消亡(生命周期結束)時,它會進行提示:
?<?php?class Dog {public $name; // 姓名public $age; ?// 年齡?function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; ? // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}?function __destruct() {echo "=============== destruct ===============\n";echo "快樂的時光總是短暫的,你的 🐕 " . $this -> name . "還是走向了它的終點\n";echo "請不要傷心,它的故事只是已另一種形式展開。。。。。";}}?$dog = new Dog("旺財", "10"); // 實例化一只小狗
0x04:PHP 魔術方法 — __call()
0x0401:方法簡介
__call()
方法只能被用于類中,當程序嘗試調用類對象的一個 不存在 的或者 不可訪問 的方法或屬性時會被自動調用。
0x0402:方法聲明
該方法有兩個參數,第一個參數是調用的那個不存在的 方法名,第二個參數是一個數組(array),是傳遞給不存在方法的所有參數組成的數組:
?class ClassName {function __call( string $func_name, array $args) {// 內部代碼}}
0x0403:調用示例
如下,我們給 Dog
類創建了一個 __call
方法,用于在程序調用其中不存在的方法時進行自動調用:
?<?php?class Dog {public $name; // 姓名public $age; ?// 年齡?function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; ? // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}?function __call($func_name, $args) {echo "================ Call Error ! ================\n";echo "Sorry, " . $this -> name . "不會" .$func_name . "\n";print_r($args);}}?$dog = new Dog("旺財", "1"); // 實例化一只小狗$dog -> fly("高高", $hight="100 米"); // 讓小狗飛高高,想飛 100 米那么高
0x05:PHP 魔術方法 — __callStatic()
0x0501:方法簡介
__callStatic()
會在程序調用一個不存在的靜態方法(該方法不存在或者不可訪問)時被自動調用。
0x0502:方法聲明
該方法接收兩個參數,第一個參數是調用的那個不存在的靜態方法名,第二個參數是一個數組(array),是傳遞給不存在的靜態方法的所有參數組成的數組:
?class ClassName {static function __callStatic( string $func_name, array $args) {// 內部代碼}}
0x0503:調用示例
如下,我們給 Dog
類創建了一個 __callStatic
方法,用于在程序調用其中不存在的靜態類時自動觸發:
?<?php?class Dog {public $name; // 姓名public $age; ?// 年齡?function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; ? // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}?static function __callStatic($name, $arguments) {echo "================ Call Error ! ================\n";echo "靜態方法:" . $name . "不存在!\n";print_r($arguments);}}?$dog = new Dog("旺財", "1"); // 實例化一只小狗?// 下面就是調用靜態方法的寫法$dog::fly("高高", $hight="100 米"); // 讓小狗飛高高,想飛 100 米那么高
0x06:PHP 魔術方法 — __get()
0x0601:方法簡介
當一個類定義了一個 __get()
魔術方法后,我們就可以獲取該類的實例的私有屬性或不存在的屬性而不犯錯,這里所說的獲取,是指獲取其值。
0x0602:方法聲明
該方法的原型如下:
class ClassName {public mixed function __get( string $propertyName ) {// 內部代碼}
}
0x0603:調用示例
在下面的示例中,我們創建了一個 Dog
類,并為其添加了 __get()
魔法方法,當程序調用類中不存在的屬性時,就會提示報錯:
<?phpclass Dog {public $name; // 姓名public $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}public function __get($propertyName) {echo "================ Get Error ! ================\n";echo "Sorry, The Dog Class Didn't have <" . $propertyName . "> attribute\n";}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
echo $dog -> type; // 想要知道 Dog 屬于哪類
0x07:PHP 魔術方法 — __set()
0x0701:方法簡介
魔術方法 __set()
可以用來給類的實例的不存在的屬性或不可訪問的屬性賦值。
0x0702:方法聲明
該方法有兩個參數,第一個參數 $property
是不存在的或不可訪問的實例屬性,第二個參數 $value
是實際要賦的值。
該方法可以有返回值,也可以沒有返回值,這取決于開發者的要求:
class ClassName {public function __set( $propertyName, $value ) {// 內部代碼}
}
0x0703:調用示例
在如下示例中,當我們為私有屬性 age
賦值時就會觸發類中的 __set
方法,做一個簡單的判斷,不讓這個年齡過大或者過小:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}public function __set($propertyName, $value) {print_r("===================== Set =====================\n");if ($propertyName == "age") {if ($value < 0 or $value > 35) {echo "Error! Your Dog Age IS Error !!!\n"; // 當設置的年齡超過了狗年齡的范圍時觸發} else {$this -> age = $value;echo "Now, Your Dog Age is " . $this -> age . "\n";}}}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
$dog -> age = 100; // 想讓狗的年齡變成 100 歲
$dog -> age = 18; // 想讓狗的年齡回到 18 歲
0x08:PHP 魔術方法 — __isset()
0x0801:方法簡介
在討論 __isset()
魔術方法之前,筆者先簡單介紹一下 isset()
方法,該方法主要用于判斷一個變量或一個實例的一個屬性是否被定義。
如果變量或實例的屬性不存在,或被賦值為 NULL,就會返回 false,其它情況下一律返回 true,哪怕目標被賦值為 false
,0
,''
。
isset()
通常用于判斷某個變量是否被設置,但它同時可以在外部實例中判斷實例的某個屬性值是否被設置,這通常有兩個常見:
-
如果屬性是公開(public)屬性,那么可以直接使用
isset()
來判斷該屬性是否設置。 -
如果屬性是一個私有(private)的屬性,那么
isset()
就無法正常工作了。
針對上述的第二種情況,我們就需要用到 __isset()
方法了。
0x0802:方法作用
通過在類中定義 __isset()
魔術方法,我們就可以使用 isset()
來判斷這個類的實例的某個私有屬性是否被 “設置”(只要 __isset()
返回 true
,那么 isset()
方法就會返回 true
,反之亦然)。
0x0803:方法聲明
該方法只接收一個參數,就是要進行判斷的屬性名,該方法的返回值為一個 Bool 類型:
class ClassName {public bool function __isset( $propertyName ) {// 內部代碼return [true or false];}
}
0x0804:調用示例
在下面的代碼中,類中的 age
屬性為私有的,要想判斷實例的 age
屬性是否被設置,我們就要借助 __isset()
方法:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}public function __isset($property) {print_r("WUHU, {$property} is a private attribute, __isset function is auto runs!!!\n");return isset($this -> $property);}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
var_dump(isset($dog -> age));
0x09:PHP 魔術方法 — __unset()
0x0901:方法簡介
如果一個類中定義了魔術方法 __unset()
,那么我們就可以使用 unset()
函數來銷毀類的私有屬性,或在銷毀一個不存在的屬性時得到通知。
然而實際上到底有沒有銷毀那個屬性,取決于 __unset()
的具體實現,假如我們定義了一個空的 __unset()
方法,emmmm,沒人這么閑吧。
0x0902:方法聲明
該方法的原型如下:
class ClassName {public function __unset( $propertyName ) {// 內部代碼}
}
0x0903:調用示例
在下面的示例中,我們在 Dog
類中定義了一個 __unset()
方法,并用它嘗試銷毀類中的一個私有屬性,與一個不存在的屬性:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}public function __unset( $property ) {if ($property != "age") {echo "啊哦, 你銷毀的東東不存在 !!!!\n";} else {echo "<$property> 已成功被銷毀 !!!\n";unset($this -> $property);}}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
unset($dog -> type); // 嘗試銷毀不存在的 Type 屬性
unset($dog -> age); // 嘗試銷毀類的私有屬性 age
0x10:PHP 魔術方法 — __sleep()
0x1001:方法簡介
當我們在 PHP 中調用 serialize()
函數嘗試序列化一個實例時,會首先檢查該實例中是否存在 __sleep()
方法,如果該方法存在,則自動調用,否則使用默認的序列化方式。
0x1003:方法聲明
我們可以在 __sleep()
方法中定制類的實例的序列化輸出結果,并剔除一些不需要被序列化的屬性,比如那些保存了超大數據的屬性。
該魔術方法沒有任何參數,單必須要有返回值,返回值的類型是 Array 類的,它包含了想要序列化的該實例的屬性名:
class ClassName {public array function __sleep() {// 內部代碼return array();}
}
0x1004:調用示例
比如下面這個例子,我們創建了一個 Dog
類,當程序序列化該類對象時,我們剔除了 $age
屬性,并對 $name
屬性進行了編碼操作:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}function __sleep() {print_r("============= Dog 類正在序列化 Ing =============");$this -> name = base64_encode($this -> name);$this -> type = "Dog"; // 臨時創建一個屬性return array("name", "type"); // 返回的時候排除了 $age 屬性}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
echo serialize($dog); // 對 dog 進行序列化
0x11:PHP 魔術方法 — __wakeup()
0x1101:方法簡介
當我們在 PHP 中使用 unserialize()
反序列化一個對象時,如果類中存在 __wakeup()
方法,那么該方法就會被自動調用。
0x1102:方法聲明
該魔術方法既沒有參數,也沒有返回值:
class ClassName {public function __wakeup() {// 內部代碼}
}
0x1103:調用示例
下面示例中,我們往 Dog
類中添加了反序列化方法,用來在反序列化時,對 $name
進行 Base64 解碼:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}function __sleep() {print_r("============= Dog 類正在序列化 Ing =============");$this -> name = base64_encode($this -> name);$this -> type = "Dog"; // 臨時創建一個屬性return array("name", "type"); // 返回的時候排除了 $age 屬性}function __wakeup() {print_r("============= Dog 類正在反序列化 Ing =============");$this -> name = base64_decode($this -> name); // 對 Dog 名稱進行 Base64 解碼}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗$serialize_dog = serialize($dog); // 對 dog 進行序列化
echo $serialize_dog . "\n";$new_dog = unserialize($serialize_dog);
echo "\nDog 的名稱: " . $new_dog -> name;
0x12:PHP 魔術方法 — __toString()
0x1201:方法簡介
當我們使用 echo
語句嘗試輸出一個對象時,就會自動檢查一個對象有沒有定義 __toString()
方法,如果定義了,就會輸出 __toString()
方法的返回值,如果沒有定義,那么就會直接拋出一個異常,表明該對象不能直接轉換為字符串。
0x1202:方法聲明
該方法沒有任何參數,也不會傳遞任何參數,但該方法必須有一個返回值,且返回值必須為字符串類型:
class ClassName {public string function __toString() {// 內部代碼}
}
0x1203:調用示例
在下面例子中,我們為 Dog 類新增添了一個 __toString()
方法,并通過 echo
輸出了該類:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}public function __toString() {return sprintf("Dog('%s', '%s')", $this -> name, $this -> age);}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
echo $dog;
0x13:PHP 魔術方法 — __invoke()
0x1301:方法簡介
當我們嘗試將一個對象當作一個方法來使用時就會自動調用它的 __invoke()
方法,如果目標對象中不包含該方法,就會直接報錯。
0x1302:方法聲明
該方法可以有返回值,也可以沒有,對于返回值的類型,它也沒有任何限制:
class ClassName {public mixed function __invoke() {// 內部代碼}
}
0x1303:調用示例
下面的代碼,我們給 Dog 類加上了 __invoke()
魔術方法,然后我們就可以將它的實例當作普通方法來調用了:
<?phpclass Dog {public $name; // 姓名private $age; // 年齡function __construct($name, $age) {echo "恭喜你,你成功創建了一只 🐕 !!!\n";$this -> name = $name; // 初始化 Dog 的名稱echo "Dog Name : " . $this -> name . "\n";$this -> age = $age; // 初始化 Dog 的年齡echo "Dog Age : " . $this -> age . "\n";}function __invoke() {echo "Hello, My Name is " . $this -> name . " I am " .$this ->age . "Years Old Now !!!";}
}$dog = new Dog("旺財", "1"); // 實例化一只小狗
$dog(); // 把 dog 對象當作方法調用