分(fēn)享到:

魔術(shù)方法的應用

日(rì)期:2016-10-25 20:45:00     閱讀(dú):386     文章(zhāng)來(lái)源:源美網絡     标簽:深圳網頁設計(jì),魔術(shù)方法

魔術(shù)方法是以兩個下畫(huà)線“_”開頭、具有特殊作(zuò)用的一些方法,可(kě)以看(kàn)做PHP的“語法糖”。

語法糖指那些沒有給計(jì)算機(jī)語言添加新功能,而隻是對人(rén)類來(lái)說(shuō)更“甜蜜”的語法。語法糖往往給程序員(yuán)提供了更實用的編碼方式或者一些技巧性的用法,有益于更好的編碼風(fēng)格,使代碼更易讀(dú)。不過其并沒有給語言添加什麽新東西。PHP裡(lǐ)的引用、SPL等都(dōu)屬于語法糖。

實際上,在1.1節代碼中就(jiù)涉及魔術(shù)方法的使用。family類中的_construct方法就(jiù)是一個标準魔術(shù)方法。這個魔術(shù)方法又稱構造方法。具有構造方法的類會在每次創建對象時先調用此方法,所以非常适合在使用對象之前做一些初始化工(gōng)作(zuò)。因此,這個方法往往用于類進行初始化時執行一些初始化操作(zuò),如(rú)給屬性賦值、連接數據庫等。

以代碼清單1-1所示代碼爲例,family中的_construct方法主要做的事(shì)情就(jiù)是在創建對象的同時對屬性賦值。也可(kě)以這麽使用:

$tom=new family($student,'peking');

$tom->people->say(); 

這樣做就(jiù)不需要在創建對象後再去(qù)賦值了。有構造方法就(jiù)有對應的析構方法,即_destruct方法,析構方法會在某個對象的所有引用都(dōu)被删除,或者當對象被顯式銷毀時執行。這兩個方法是常見(jiàn)也是最有用的魔術(shù)方法。


_set和_get方法

_set和_get是兩個比較重要的魔術(shù)方法,如(rú)代碼清單所示。

代碼清單 magic.php

<?php

class Account{

private$user=1;

private$pwd=2;

}

$a=new Account();

echo$a->user;

$a->name=5;

echo$a->name;

echo$a->big;

運行這段代碼會怎樣呢(ne)?結果報錯如(rú)下:

Fatal error:Cannot access private property Account::$user in G:\bak\temp\tempcode\_sg.php on line 7 

所報錯誤大(dà)緻是說(shuō),不能訪問(wèn)Account對象的私有屬性user。在代碼清單類定義裡(lǐ)增加以下代碼,其中使用了_set魔術(shù)方法。

public function _set($name,$value){

echo "Setting$name to$value\r\n";

$this->$name=$value;

}

public function _get($name){

if(!isset($this->$name)){

echo'未設置';

$this->$name="正在爲你(nǐ)設置默認值";

}

return$this->$name;

再次運行,看(kàn)到正常輸出,沒有報錯。在類裡(lǐ)以兩個下畫(huà)線開頭的方法都(dōu)屬于魔術(shù)方法(除非是你(nǐ)自(zì)定義的),它們是PHP中的内置方法,有特殊含義。手冊裡(lǐ)把這兩個方法歸到重載。

PHP的重載和Java等語言的重載不同。Java裡(lǐ),重載指一個類中可(kě)以定義參數列表不同但(dàn)名字相(xiàng)同的多個方法。比如(rú),Java也有構造函數,Java允許有多個構造函數,隻要保證方法簽名不一樣就(jiù)行;而PHP則在一個類中隻允許有一個構造函數。

PHP提供的“重載”指動态地“創建”類屬性和方法。因此,_set和_get方法被歸到重載裡(lǐ)。

這裡(lǐ)可(kě)以直觀看(kàn)到,若類中定義了_set和_get這一對魔術(shù)方法,那麽當給對象屬性賦值或者取值時,即使這個屬性不存在,也不會報錯,一定程度上增強了程序的健壯性。

我們注意到,在account類裡(lǐ),user屬性的訪問(wèn)權限是私有的,私有屬性意味着這個屬性是類的“私有财産”,隻能在類内部對其進行操作(zuò)。如(rú)果沒有_set這個魔術(shù)方法,直接在類的外部對屬性進行賦值操作(zuò)是會報錯的,隻能通過在類中定義一個public的方法,然後在類外調用這個公開的方法進行屬性讀(dú)寫操作(zuò)。

現在有了這兩個魔術(shù)方法,是不是對私有屬性的操作(zuò)變得(de)更方便了呢(ne)?實際上,并沒有什麽奇怪的,因爲這兩個方法本身(shēn)就(jiù)是public的。它們和在對外的public方法中操作(zuò)private屬性的原理(lǐ)一樣。隻不過這對魔術(shù)方法使其操作(zuò)更簡單,不需要顯式地調用一個public的方法,因爲這對魔術(shù)方法在操作(zuò)類變量時是自(zì)動調用的。當然,也可(kě)以把類屬性定義成public的,這樣就(jiù)可(kě)以随意在類的外部進行讀(dú)寫。不過,如(rú)果隻是爲了方便,類屬性在任意時候都(dōu)定義成public權限顯然是不合适的,也不符合面向對象的設計(jì)思想。


_call和_callStatic方法

如(rú)何防止調用不存在的方法而出錯?一樣的道理(lǐ),使用_call魔術(shù)重載方法。

_call方法原型如(rú)下:

mixed _call(string$name,array$arguments) 

當調用一個不可(kě)訪問(wèn)的方法(如(rú)未定義,或者不可(kě)見(jiàn))時,_call()會被調用。其中$name參數是要調用的方法名稱。$arguments參數是一個數組,包含着要傳遞給方法的參數,如(rú)下所示:

public function _call($name,$arguments){

switch(count($arguments)){

case 2:

echo $arguments[0]*$arguments[1],PHP_EOL;

break;

case 3:

echo array_sum($arguments),PHP_EOL;

break;

default:

echo '參數不對',PHP_EOL;

break;

}

}

$a->make(5);

$a->make(5,6); 

以上代碼模拟了類似其他(tā)語言中的根據參數類型進行重載。跟_call方法配套的魔術(shù)方法是_callStatic。當然,使用魔術(shù)方法“防止調用不存在的方法而報錯”,并不是魔術(shù)方法的本意。實際上,魔術(shù)方法使方法的動态創建變爲可(kě)能,這在MVC等框架設計(jì)中是很有用的語法。假設一個控制器調用了不存在的方法,那麽隻要定義了_call魔術(shù)方法,就(jiù)能友好地處理(lǐ)這種情況。

試着理(lǐ)解代碼清單1-3所示代碼。這段代碼通過使用_callStatic這一魔術(shù)方法進行方法的動态創建和延遲綁定,實現一個簡單的ORM模型。

代碼清單1-3 simpleOrm.php

<?php

abstract class ActiveRecord{

protected static $table;

protected $fieldvalues;

public $select;

static function findById($id){

$query = "select*from"

.static::$table

."where id=$id";

return self::createDomain($query);

}

function _get($fieldname){

return $this->fieldvalues[$fieldname];

}

static function _callStatic($method,$args){

$field = preg_replace('/^findBy(\w*)$/','${1}',$method);

$query = "select*from"

.static::$table

."where$field = '$args[0]'";

return self::createDomain($query);

}

private static function createDomain($query){

$klass = get_called_class();

$domain = new$klass();

$domain->fieldvalues = array();

$domain->select = $query;

foreach($klass::$fields as$field=>$type){

$domain->fieldvalues[$field]='TODO:set from sql result';

}

return $domain;

}

}

class Customer extends ActiveRecord{

protected static $table='custdb';

protected static $fields=array(

'id'=>'int',

'email'=>'varchar',

'lastname'=>'varchar'

);

}

class Sales extends ActiveRecord{

protected static $table='salesdb';

protected static $fields=array(

'id'=>'int',

'item'=>'varchar',

'qty'=>'int'

);

}

assert("select*from custdb where id=123"==

Customer::findById(123)->select);

assert("TODO:set from sql result"==

Customer::findById(123)->email);

assert("select*from salesdb where id=321"==

Sales::findById(321)->select);

assert("select*from custdb where Lastname='Denoncourt'"==

Customer::findByLastname('Denoncourt')->select); 

再舉個類似的例子。PHP裡(lǐ)有很多字符串函數,假如(rú)要先過濾字符串首尾的空格,再求出字符串的長度,一般會這麽寫:

strlen(trim($str)); 

如(rú)果要實現JS裡(lǐ)的鏈式操作(zuò),比如(rú)像下面這樣,應該怎麽實現?

很簡單,先實現一個String類,對這個類的對象調用方法進行處理(lǐ)時,觸發_call魔術(shù)方法,接着執行call_user_func即可(kě)。


_toString方法

再看(kàn)另外一個魔術(shù)方法_TOstring(在這裡(lǐ)故意這麽寫,是要說(shuō)明PHP中方法不區分(fēn)大(dà)小寫,但(dàn)實際開發中還(hái)需要注意規範)。

當進行測試時,需要知道是否得(de)出正确的數據。比如(rú)打印一個對象時,看(kàn)看(kàn)這個對象都(dōu)有哪些屬性,其值是什麽,如(rú)果類定義了_toString方法,就(jiù)能在測試時,echo打印對象體(tǐ),對象就(jiù)會自(zì)動調用它所屬類定義的_toString方法,格式化輸出這個對象所包含的數據。如(rú)果沒有這個方法,那麽echo一個對象将報錯,例如(rú)"Catchable fatal error:Object of class Account could not be converted to string"語法錯誤,實際上這是一個類型匹配失敗錯誤。不過仍然可(kě)以用print_r()和var_dump()函數輸出一個對象。當然,_toString是可(kě)以定制的,所提供的信息和樣式更豐富,如(rú)代碼清單1-4所示。

代碼清單1-4 magic_2.php

<?php

class Account{

public $user = 1;

private $pwd = 2;

//自(zì)定義的格式化輸出方法

public function_toString(){

return"當前對象的用戶名是{$this->user},密碼是{$this->pwd}";

}

}

$a = new Account();

echo$a;

echo PHP_EOL;

print_r($a); 

運行這段代碼發現,使用_toString方法後,輸出的結果是可(kě)定制的,更易于理(lǐ)解。實際上,PHP的_toString魔術(shù)方法的設計(jì)原型來(lái)源于Java。Java中也有這麽一個方法,而且在Java中,這個方法被大(dà)量使用,對于調試程序比較方便。實際上,_toString方法也是一種序列化,我們知道PHP自(zì)帶serialize/unserialize也是進行序列化的,但(dàn)是這組函數序列化時會産生(shēng)一些無用信息,如(rú)屬性字符串長度,造成存儲空間的無謂浪費。因此,可(kě)以實現自(zì)己的序列化和反序列化方法,或者json_encode/json_decode也是一個不錯的選擇。

爲什麽直接echo一個對象就(jiù)會報語法錯誤,而如(rú)果這個對象實現_toString方法後就(jiù)可(kě)以直接輸出呢(ne)?原因很簡單,echo本來(lái)可(kě)以打印一個對象,而且也實現了這個接口,但(dàn)是PHP對其做了個限制,隻有實現_toString後才允許使用。從(cóng)下面的PHP源代碼裡(lǐ)可(kě)以得(de)到驗證:

ZEND_VM_HANDLER(40,ZEND_ECHO,CONST|TMP|VAR|CV,ANY)

{

zend_op*opline = EX(opline);

zend_free_op free_op1;

zval z_copy;

zval *z =GET_OP1_ZVAL_PTR(BP_VAR_R);

//此處的代碼預留了把對象轉換爲字符串的接口

if(OP1_TYPE ! = IS_CONST&&

Z_TYPE_P(z) == IS_OBJECT&&Z_OBJ_HT_P(z)->get_method ! = NULL&&

zend_std_cast_object_tostring(z,&z_copy,IS_STRING TSRMLS_CC) == SUCCESS){

zend_print_variable(&z_copy);

zval_dtor(&z_copy);

}else{

zend_print_variable(z);

}

FREE_OP1();

ZEND_VM_NEXT_OPCODE();

由此可(kě)見(jiàn),魔術(shù)方法并不神奇。

有比較才有認知。最後,針對本節代碼給出一個Java版本的代碼,供各位讀(dú)者用來(lái)對比兩種語言中重載和魔術(shù)方法的異同,如(rú)代碼清單1-5所示。

代碼清單1-5 Account.java

import org.apache.commons.lang3.builder.ToStringBuilder;

/**

*類的重載演示Java版本

*@author wfox

*@date@verson

*/

public class Account{

private String user;//用戶名

private String pwd;//密碼

public Account(){

System.out.println("構造函數");

}

public Account(String user,String pwd){

System.out.println("重載構造函數");

System.out.println(user+"---"+pwd);

}

public void say(String user){

System.out.println("用戶是:"+user);

}

public void say(String user,String pwd){

System.out.println("用戶:"+user);

System.out.println("密碼"+pwd);

}

public String getUser(){

return user;

}

public void setUser(String user){

this.user = user;

}

public String getPwd(){

return pwd;

}

public void setPwd(String pwd){

}

@Override

public String toString(){

return ToStringBuilder.reflectionToString(this);

}

public static void main(String……){

Account account = new Account();

account.setUser("張三");

account.setPwd("123456");

account.say("李四");

account.say("王五","123");

System.out.println(account);

}

可(kě)以看(kàn)出,Java的構造方法比PHP好用,PHP由于有了_set/_get這一對魔術(shù)方法,使得(de)動态增加對象的屬性字段變得(de)很方便,而對Java來(lái)說(shuō),要實現類似的效果,就(jiù)不得(de)不借助反射API或直接修改編譯後字節碼的方式來(lái)實現。這體(tǐ)現了動态語言的優勢,簡單、靈活。





文章(zhāng)引用:

本站(zhàn)文章(zhāng)爲深圳網站(zhàn)建設·源美網絡原創策劃,如(rú)有版權糾紛或者違規問(wèn)題,請(qǐng)聯系我們删除,謝謝!

上一篇: 在網站(zhàn)制作(zuò)中各種語言中的多态

下一篇: 網站(zhàn)設計(jì)中的說(shuō)服力

返回列表
最新案例
OUR ADVANTAGE WORKS

售後保障

承諾任何問(wèn)題1小時内解決

數據備份

更安全、更高效、更穩定

價格公道精準

項目經理(lǐ)精準報價不弄虛作(zuò)假

合作(zuò)無風(fēng)險

重合同講信譽,無效全額退款