前言
php中把以兩個下劃線__開頭的方法稱為魔術方法(magic methods),這些方法在php中充當了舉足輕重的作用。 魔術方法包括:
__construct(),類的構造函數
__destruct(),類的析構函數
__call(),在對象中調用一個不可通路方法時調用
__callstatic(),用靜态方式中調用一個不可通路方法時調用
__get(),獲得一個類的成員變量時調用
__set(),設定一個類的成員變量時調用
__isset(),當對不可通路屬性調用isset()或empty()時調用
__unset(),當對不可通路屬性調用unset()時被調用。
__sleep(),執行serialize()時,先會調用這個函數
__wakeup(),執行unserialize()時,先會調用這個函數
__tostring(),類被當成字元串時的回應方法
__invoke(),調用函數的方式調用一個對象時的回應方法
__set_state(),調用var_export()導出類時,此靜态方法會被調用。
__clone(),當對象複制完成時調用
__autoload(),嘗試加載未定義的類
__debuginfo(),列印所需調試資訊
範例
下面讓我們以執行個體的形式向大家講解下這幾個魔術方法時如何使用的。
一、 __construct(),類的構造函數
php中構造方法是對象建立完成後第一個被對象自動調用的方法。在每個類中都有一個構造方法,如果沒有顯示地聲明它,那麼類中都會預設存在一個沒有參數且内容為空的構造方法。
1、 構造方法的作用
通常構造方法被用來執行一些有用的初始化任務,如對成員屬性在建立對象時賦予初始值。
2、 構造方法的在類中的聲明格式
function __constrct([參數清單]){
方法體 //通常用來對成員屬性進行初始化指派
}
3、 在類中聲明構造方法需要注意的事項
1、在同一個類中隻能聲明一個構造方法,原因是,php不支援構造函數重載。 2、構造方法名稱是以兩個下畫線開始的__construct()
下面是它的例子:
<?php
class person
{
public $name;
public $age;
public $sex;
/**
* 顯示聲明一個構造方法且帶參數
*/
public function __construct($name="", $sex="男", $age=22) { $this->name = $name; $this->sex = $sex; $this->age = $age; } /** * say 方法 */ public function say() { echo "我叫:" . $this->name . ",性别:" . $this->sex . ",年齡:" . $this->age;
}
}
建立對象$person1且不帶任參數
$person1 = new person();
echo $person1->say(); //輸出:我叫:,性别:男,年齡:27
建立對象$person2且帶參數“小明”
$person2 = new person("小明"); echo $person2->say(); //輸出:我叫:張三,性别:男,年齡:27
建立對象$person3且帶三個參數
$person3 = new person("李四","男",25); echo $person3->say(); //輸出:我叫:李四,性别:男,年齡:25
二、__destruct(),類的析構函數
通過上面的講解,現在我們已經知道了什麼叫構造方法。那麼與構造方法對應的就是析構方法。
析構方法允許在銷毀一個類之前執行的一些操作或完成一些功能,比如說關閉檔案、釋放結果集等。
析構方法是php5才引進的新内容。
析造方法的聲明格式與構造方法 <code>__construct()</code> 比較類似,也是以兩個下劃線開始的方法 <code>__destruct()</code> ,這種析構方法名稱也是固定的。
1、 析構方法的聲明格式
function __destruct()
{
//方法體
}
注意:析構函數不能帶有任何參數。
2、 析構方法的作用
一般來說,析構方法在php中并不是很常用,它屬類中可選擇的一部分,通常用來完成一些在對象銷毀前的清理任務。
舉例示範,如下:
class person{
public $name;
public $age;
public $sex;
public function __construct($name="", $sex="男", $age=22) { $this->name = $name; $this->sex = $sex; $this->age = $age; } /** * say 說話方法 */ public function say() { echo "我叫:".$this->name.",性别:".$this->sex.",年齡:".$this->age; } /** * 聲明一個析構方法 */ public function __destruct() { echo "我覺得我還可以再搶救一下,我的名字叫".$this->name; } } $person = new person("小明"); unset($person); //銷毀上面建立的對象$person
上面的程式運作時輸出:
我覺得我還可以再搶救一下,我的名字叫小明
三、 __call(),在對象中調用一個不可通路方法時調用。
該方法有兩個參數,第一個參數 <code>$function_name</code> 會自動接收不存在的方法名,第二個 <code>$arguments</code> 則以數組的方式接收不存在方法的多個參數。
1、 __call() 方法的格式:
function __call(string $function_name, array $arguments)
// 方法體
2、 __call() 方法的作用:
為了避免當調用的方法不存在時産生錯誤,而意外的導緻程式中止,可以使用 __call() 方法來避免。 該方法在調用的方法不存在時會自動調用,程式仍會繼續執行下去。
請參考如下代碼:
class person
{
function say()
{
echo "hello, world!<br>"; } /** * 聲明此方法用來處理調用對象中不存在的方法 */ function __call($funname, $arguments) { echo "你所調用的函數:" . $funname . "(參數:" ; // 輸出調用不存在的方法名 print_r($arguments); // 輸出調用不存在的方法時的參數清單 echo ")不存在!<br>\n"; // 結束換行 } } $person = new person(); $person->run("teacher"); // 調用對象中不存在的方法,則自動調用了對象中的__call()方法 $person->eat("小明", "蘋果");
$person->say();
運作結果:
你所調用的函數:run(參數:array ( [0] => teacher ) )不存在!
你所調用的函數:eat(參數:array ( [0] => 小明 [1] => 蘋果 ) )不存在!
hello, world!
四、 __callstatic(),用靜态方式中調用一個不可通路方法時調用
此方法與上面所說的 __call() 功能除了 __callstatic() 是未靜态方法準備的之外,其它都是一樣的。
請看下面代碼:
{
echo "hello, world!<br>"; } /** * 聲明此方法用來處理調用對象中不存在的方法 */ public static function __callstatic($funname, $arguments) { echo "你所調用的靜态方法:" . $funname . "(參數:" ; // 輸出調用不存在的方法名 print_r($arguments); // 輸出調用不存在的方法時的參數清單 echo ")不存在!<br>\n"; // 結束換行 } } $person = new person(); $person::run("teacher"); // 調用對象中不存在的方法,則自動調用了對象中的__call()方法 $person::eat("小明", "蘋果");
運作結果如下:
你所調用的靜态方法:run(參數:array ( [0] => teacher ) )不存在!
你所調用的靜态方法:eat(參數:array ( [0] => 小明 [1] => 蘋果 ) )不存在!
hello, world!
五、 __get(),獲得一個類的成員變量時調用
在 php 面向對象程式設計中,類的成員屬性被設定為 <code>private</code> 後,如果我們試圖在外面調用它則會出現“不能通路某個私有屬性”的錯誤。那麼為了解決這個問題,我們可以使用魔術方法 <code>__get()</code>。
魔術方法__get()的作用
在程式運作過程中,通過它可以在對象的外部擷取私有成員屬性的值。
我們通過下面的 __get() 的執行個體來更進一步的連接配接它吧:
private $name;
private $age;
function __construct($name="", $age=1) { $this->name = $name; $this->age = $age; } /** * 在類中添加__get()方法,在直接擷取屬性值時自動調用一次,以屬性名作為參數傳入并處理 * @param $propertyname * * @return int */ public function __get($propertyname) { if ($propertyname == "age") { if ($this->age > 30) { return $this->age - 10; } else { return $this->$propertyname; } } else { return $this->$propertyname; } } } $person = new person("小明", 60); // 通過person類執行個體化的對象,并通過構造方法為屬性賦初值 echo "姓名:" . $person->name . "<br>"; // 直接通路私有屬性name,自動調用了__get()方法可以間接擷取 echo "年齡:" . $person->age . "<br>"; // 自動調用了__get()方法,根據對象本身的情況會傳回不同的值
運作結果:
姓名:小明
年齡:50
六、 __set(),設定一個類的成員變量時調用
__set() 的作用:
__set( $property, $value )` 方法用來設定私有屬性, 給一個未定義的屬性指派時,此方法會被觸發,傳遞的參數是被設定的屬性名和值。
請看下面的示範代碼:
我叫小紅,今年16歲了
七、 __isset(),當對不可通路屬性調用isset()或empty()時調用
在看這個方法之前我們看一下<code>isset()</code>函數的應用,<code>isset()</code>是測定變量是否設定用的函數,傳入一個變量作為參數,如果傳入的變量存在則傳回true,否則傳回false。
那麼如果在一個對象外面使用<code>isset()</code>這個函數去測定對象裡面的成員是否被設定可不可以用它呢?
分兩種情況,如果對象裡面成員是公有的,我們就可以使用這個函數來測定成員屬性,如果是私有的成員屬性,這個函數就不起作用了,原因就是因為私有的被封裝了,在外部不可見。那麼我們就不可以在對象的外部使用<code>isset()</code>函數來測定私有成員屬性是否被設定了呢?當然是可以的,但不是一成不變。你隻要在類裡面加上一個<code>__isset()</code>方法就可以了,當在類外部使用<code>isset()</code>函數來測定對象裡面的私有成員是否被設定時,就會自動調用類裡面的<code>__isset()</code>方法了幫我們完成這樣的操作。
__isset()的作用:當對不可通路屬性調用 isset() 或 empty() 時,__isset() 會被調用。
請看下面代碼示範:
public $sex;
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } /** * @param $content * * @return bool */ public function __isset($content) { echo "當在類外部使用isset()函數測定私有成員{$content}時,自動調用<br>"; echo isset($this->$content); } } $person = new person("小明", 25); // 初始指派 echo isset($person->sex),"<br>"; echo isset($person->name),"<br>"; echo isset($person->age),"<br>";
1 // public 可以 isset()
當在類外部使用isset()函數測定私有成員name時,自動調用 // __isset() 内 第一個echo
1 // __isset() 内第二個echo
當在類外部使用isset()函數測定私有成員age時,自動調用 // __isset() 内 第一個echo
八、 __unset(),當對不可通路屬性調用unset()時被調用。
看這個方法之前呢,我們也先來看一下 <code>unset()</code> 函數,<code>unset()</code>這個函數的作用是删除指定的變量且傳回true,參數為要删除的變量。
那麼如果在一個對象外部去删除對象内部的成員屬性用<code>unset()</code>函數可以嗎?
這裡自然也是分兩種情況:
1、 如果一個對象裡面的成員屬性是公有的,就可以使用這個函數在對象外面删除對象的公有屬性。
2、 如果對象的成員屬性是私有的,我使用這個函數就沒有權限去删除。
雖然有以上兩種情況,但我想說的是同樣如果你在一個對象裡面加上<code>__unset()</code>這個方法,就可以在對象的外部去删除對象的私有成員屬性了。在對象裡面加上了<code>__unset()</code>這個方法之後,在對象外部使用“unset()”函數删除對象内部的私有成員屬性時,對象會自動調用<code>__unset()</code>函數來幫我們删除對象内部的私有成員屬性。
請看如下代碼:
當在類外部使用unset()函數來删除私有成員時自動調用的
1當在類外部使用unset()函數來删除私有成員時自動調用的
九、 __sleep(),執行serialize()時,先會調用這個函數
<code>serialize()</code> 函數會檢查類中是否存在一個魔術方法 <code>__sleep()</code>。如果存在,則該方法會優先被調用,然後才執行序列化操作。
此功能可以用于清理對象,并傳回一個包含對象中所有應被序列化的變量名稱的數組。
如果該方法未傳回任何内容,則 null 被序列化,并産生一個 e_notice 級别的錯誤。
注意:
__sleep() 不能傳回父類的私有成員的名字。這樣做會産生一個 e_notice 級别的錯誤。可以用 serializable 接口來替代。
作用:
__sleep() 方法常用于送出未送出的資料,或類似的清理操作。同時,如果有一些很大的對象,但不需要全部儲存,這個功能就很好用。
具體請參考如下代碼:
代碼運作結果:
當在類外部使用serialize()時會調用這裡的__sleep()方法
o:6:"person":2:{s:4:"name";s:8:"5bcp5pio";s:3:"age";i:25;}
十、 __wakeup(),執行unserialize()時,先會調用這個函數
如果說 <code>__sleep()</code> 是白的,那麼 <code>__wakeup()</code> 就是黑的了。
那麼為什麼呢?
因為:
與之相反,`unserialize()` 會檢查是否存在一個 `__wakeup()` 方法。如果存在,則會先調用 `__wakeup` 方法,預先準備對象需要的資源。
__wakeup() 經常用在反序列化操作中,例如重建立立資料庫連接配接,或執行其它初始化操作。
還是看代碼:
string(58) "o:6:"person":2:{s:4:"name";s:8:"5bcp5pio";s:3:"age";i:25;}" 當在類外部使用serialize()時會調用這裡的__sleep()方法 當在類外部使用unserialize()時會調用這裡的__wakeup()方法 object(person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> int(2) ["age"]=> int(25) }
十一、 __tostring(),類被當成字元串時的回應方法
__tostring() 方法用于一個類被當成字元串時應怎樣回應。例如 `echo $obj;` 應該顯示些什麼。
此方法必須傳回一個字元串,否則将發出一條 `e_recoverable_error` 級别的緻命錯誤。
警告:
不能在 __tostring() 方法中抛出異常。這麼做會導緻緻命錯誤。
代碼:
public $name;
public $age;
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __tostring() { return 'go go go'; } } $person = new person('小明'); // 初始指派 echo $person;
結果:
go go go
那麼如果類中沒有 __tostring() 這個魔術方法運作會發生什麼呢?讓我們來測試下:
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } } $person = new person('小明'); // 初始指派 echo $person;
catchable fatal error: object of class person could not be converted to string in d:\phpstudy\www\test\index.php on line 18
很明顯,頁面報了一個緻命錯誤,這是文法所不允許的。
十二、 __invoke(),調用函數的方式調用一個對象時的回應方法
當嘗試以調用函數的方式調用一個對象時,__invoke() 方法會被自動調用。
本特性隻在 php 5.3.0 及以上版本有效。
直接上代碼:
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __invoke() { echo '這可是一個對象哦'; } } $person = new person('小明'); // 初始指派
$person();
檢視運作結果:
這可是一個對象哦
當然,如果你執意要将對象當函數方法使用,那麼會得到下面結果:
fatal error: function name must be a string in d:\phpstudy\www\test\index.php on line 18
十三、 __set_state(),調用var_export()導出類時,此靜态方法會被調用。
自 php 5.1.0 起,當調用 var_export() 導出類時,此靜态方法會被自動調用。
參數:
本方法的唯一參數是一個數組,其中包含按 array(‘property’ => value, …) 格式排列的類屬性。
下面我們先來看看在沒有加 __set_state() 情況按下,代碼及運作結果如何:
上代碼:
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } } $person = new person('小明'); // 初始指派
var_export($person);
看結果:
person::__set_state(array( 'sex' => '男', 'name' => '小明', 'age' => 25, ))
很明顯,将對象中的屬性都列印出來了
加了 __set_state() 之後:
繼續上代碼:
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public static function __set_state($an_array) { $a = new person(); $a->name = $an_array['name']; return $a; } } $person = new person('小明'); // 初始指派 $person->name = '小紅';
繼續看結果:
person::__set_state(array( 'sex' => '男', 'name' => '小紅', 'age' => 25, ))
十四、 __clone(),當對象複制完成時調用
在多數情況下,我們并不需要完全複制一個對象來獲得其中屬性。但有一個情況下确實需要:如果你有一個 gtk
視窗對象,該對象持有視窗相關的資源。你可能會想複制一個新的視窗,保持所有屬性與原來的視窗相同,但必須是一個新的對象(因為如果不是新的對象,那麼一個視窗中的改變就會影響到另一個視窗)。還有一種情況:如果對象
a 中儲存着對象 b 的引用,當你複制對象 a 時,你想其中使用的對象不再是對象 b 而是 b 的一個副本,那麼你必須得到對象 a
的一個副本。
對象複制可以通過 clone 關鍵字來完成(如果可能,這将調用對象的 __clone() 方法)。對象中的 __clone() 方法不能被直接調用。
文法:
$copy_of_object = clone $object;
當對象被複制後,php 5 會對對象的所有屬性執行一個淺複制(shallow copy)。所有的引用屬性 仍然會是一個指向原來的變量的引用。 當複制完成時,如果定義了 __clone() 方法,則新建立的對象(複制生成的對象)中的 __clone() 方法會被調用,可用于修改屬性的值(如果有必要的話)。
看代碼:
public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __clone() { echo __method__."你正在克隆對象<br>"; } } $person = new person('小明'); // 初始指派 $person2 = clone $person; var_dump('persion1:'); var_dump($person); echo '<br>'; var_dump('persion2:');
var_dump($person2);
person::__clone你正在克隆對象
string(9) "persion1:" object(person)#1 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) } string(9) "persion2:" object(person)#2 (3) { ["sex"]=> string(3) "男" ["name"]=> string(6) "小明" ["age"]=> int(25) }
克隆成功。
十五、__autoload(),嘗試加載未定義的類
你可以通過定義這個函數來啟用類的自動加載。
在魔術函數 <code>__autoload()</code> 方法出現以前,如果你要在一個程式檔案中執行個體化100個對象,那麼你必須用include或者require包含進來100個類檔案,或者你把這100個類定義在同一個類檔案中 —— 相信這個檔案一定會非常大,然後你就痛苦了。
但是有了 <code>__autoload()</code> 方法,以後就不必為此大傷腦筋了,這個類會在你執行個體化對象之前自動加載制定的檔案。
還是通過例子來看看吧:
先看看以往的方式:
/**
* 檔案non_autoload.php
*/
require_once('project/class/a.php');
require_once('project/class/b.php');
require_once('project/class/c.php');
if (條件a) {
$a = new a();
$b = new b();
$c = new c();
// … 業務邏輯
} else if (條件b) {
$a = newa();
看到了嗎?不用100個,隻是3個看起來就有點煩了。而且這樣就會有一個問題:如果腳本執行“條件b”這個分支時,c.php這個檔案其實沒有必要包含。因為,任何一個被包含的檔案,無論是否使用,均會被php引擎編譯。如果不使用,卻被編譯,這樣可以被視作一種資源浪費。更進一步,如果c.php包含了d.php,d.php包含了e.php。并且大部分情況都執行“條件b”分支,那麼就會浪費一部分資源去編譯c.php,d.php,e.php三個“無用”的檔案。
那麼如果使用 <code>__autoload()</code> 方式呢?
* 檔案autoload_demo.php
function __autoload($classname) {
$filepath = “project/class/{$classname}.php”;
if (is_readable($filepath)) {
require($filepath);
}
}
ok,不論效率怎麼用,最起碼界面看起來舒服多了,沒有太多備援的代。
再來看看這裡的效率如何,我們分析下:
當php引擎第一次使用類a,但是找不到時,會自動調用 <code>__autoload</code> 方法,并将類名“a”作為參數傳入。是以,我們在 <code>__autoload()</code> 中需要的做的就是根據類名,找到相應的檔案,并包含進來,如果我們的方法也找不到,那麼php引擎就會報錯了。
這裡可以隻用require,因為一旦包含進來後,php引擎再遇到類a時,将不會調用__autoload,而是直接使用記憶體中的類a,不會導緻多次包含。
擴充:
其實php發展到今天,已經有将 `spl_autoload_register` — 注冊給定的函數作為 __autoload 的實作了,但是這個不在啊本文講解之内,有興趣可以自行看手冊。
十六、__debuginfo(),列印所需調試資訊
該方法在php 5.6.0及其以上版本才可以用,如果你發現使用無效或者報錯,請檢視啊你的版本。
class c {
private $prop;
public function __construct($val) {
$this->prop = $val;
/**
* @return array
*/
public function __debuginfo() {
return [
'propsquared' => $this->prop ** 2,
];
var_dump(new c(42));
object(c)#1 (1) { ["propsquared"]=> int(1764) }
再次注意:
這裡的 `**` 是乘方的意思,也是在php5.6.0及其以上才可以使用,詳情請檢視php手冊
總結
以上就是php中我了解到的魔術方法了,常用的包括 <code>__set()</code> <code>__get()</code> <code>__autoload</code>() 等應該熟悉,其他的了解也沒有關系,畢竟知識不怕多嘛。
好了,有興趣的或者我這裡沒有說明白的,可以參考啊官方文檔。
<code></code>
作者:chenyanxx
來源:51cto