PHP強制對象類型之instanceof操作符
一、簡介
在PHP中實現強制對象類型有時可能非常重要。如果缺少了它,或是因為缺乏這方面的知識——基于不正確的編程假設,或者僅僅是由于懶惰,那么你會在特定的Web應用程序中看到你所不希望的結果。特別是當用PHP 4進行編程時,使用'is_a()'函數(盡管還有其它方法)來驗證你所使用的對象的類型是非常容易的事情。毫無疑問,強制對象類型還可以被用于過濾輸入對象(需要被作為參數傳遞到同一個應用程序中的其它PHP類)。
不過,PHP 4并沒有暴露一些有關于它的對象模型的弱點-為了實現某些在成熟的面向對象的語言中出現的特征,它偶而可能要求編寫另外的代碼。長時間以來,這一事實已經為PHP社區眾所周知。然而,隨著PHP 5的發行,許多這些極有價值的特征作為改進的對象模型的一部分被添加到其中。它們將有助于更為緊密地實現基于對象的代碼的開發-允許你使用特定的對象特征。
在上面的情況下,當涉及到對象類型強制時應該特別注意。實際上,在一個Web應用程序的執行期間,PHP 5提供給開發者至少兩種方法來檢查對象類型——它們分別是“instanceof”操作符和“類型提示”特征。現在轉到本文的主題,我將介紹PHP 5中'instanceof'操作符的使用;你很快就會發現,它可以非常方便地用來確定是否你正在使用的對象屬于一個特定的類型。
本文將通過一些面向對象的示例來幫助你理解如何在PHP 5中實現強制對象類型。
二、 你不該做什么
為了展示在PHP 5中如何實現對象類型強制,我將使用(X)HTML widget類,還有一個簡單的頁面生成器類,并作了簡單的修改以適合PHP 5開發環境。
我的第一個示例列舉了一些派生自一個抽象的基類'HTMLElement'的(X)HTML widget類,它跳過了到它們的輸入對象類型的檢查。請先看下面的類:
//定義抽象類'HTMLElement'abstract class HTMLElement{ protected $attributes; protected function __construct($attributes){if(!is_array($attributes)){ throw new Exception('Invalid attribute type');}$this->attributes=$attributes; } // 抽象的'getHTML()'方法 abstract protected function getHTML();}//定義具體的類'Div'-擴展HTMLElementclass Div extends HTMLElement{ private $output='<div '; private $data; public function __construct($attributes=array(),$data){parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);$this->output.=$this->data.'</div>';return $this->output; }}//定義具體類'Header1'-擴展HTMLElementclass Header1 extends HTMLElement{ private $output='<h1 '; private $data; public function __construct($attributes=array(),$data){parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體的實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);$this->output.=$this->data.'</h1>';return $this->output; }}//定義具體類'Paragraph'-擴展HTMLElementclass Paragraph extends HTMLElement{ private $output='<p '; private $data; public function __construct($attributes=array(),$data){parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){$this->output.=$attribute.'=''.$value.'' '; } $this->output=substr_replace($this->output,'>',-1); $this->output.=$this->data.'</p>'; return $this->output;}}//定義具體類'UnorderedList'-擴展HTMLElementclass UnorderedList extends HTMLElement{ private $output='<ul '; private $items=array(); public function __construct($attributes=array(),$items=array()){parent::__construct($attributes);if(!is_array($items)){ throw new Exception('Invalid parameter for list items');}$this->items=$items; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);foreach($this->items as $item){ $this->output.='<li>'.$item.'</li>';} $this->output.='</ul>';return $this->output; }}
如你所見,上面的(X)HTML widget類在生成一個網面中特定的元素時是非常有用的,但是我有意地把每一個類的代碼寫成這樣,這樣它們就不能夠驗證輸入參數的有效性。你可能已經想到,輸入參數將直接被傳遞到類構造器中并且作為屬性賦值。問題出現了:這樣做有什么錯誤嗎?是的,有。現在,我將定義我的最簡單的頁面生成器類,并且用這樣一些widget來填充(feed)它,這樣你就可以看到這個類的輸入是如何與不正確的對象相混雜。下面是該頁面生成器類的簽名:
class PageGenerator{ private $output=''; private $title; public function __construct($title='Default Page'){$this->title=$title; } public function doHeader(){$this->output='<html><head><title>'.$this->title.'</title></head><body>'; } public function addHTMLElement($htmlElement){$this->output.=$htmlElement->getHTML(); } public function doFooter(){$this->output.='</body></html>'; } public function fetchHTML(){return $this->output; }}
現在,我們開始實例化一些(X)HTML widget對象,并且把它們傳遞到相應的生成器類,如下面的示例所示:
try{ //生成一些HTML元素 $h1=new Header1(array('name'=>'header1','class'=>'headerclass'),'Content for H1element goes here'); $div=new Div(array('name'=>'div1','class'=>'divclass'),'Content for Div elementgoes here'); $par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraphelement goes here'); $ul=new UnorderedList(array ('name'=>'list1','class'=>'listclass'),array('item1'=>'value1','item2'=>'value2','item3'=>'value3'));//實例化頁面生成器類 $pageGen=new Page生成器(); $pageGen->doHeader(); // 添加'HTMLElement'對象 $pageGen->addHTMLElement($h1); $pageGen->addHTMLElement($div); $pageGen->addHTMLElement($par); $pageGen->addHTMLElement($ul); $pageGen->doFooter(); //顯示網面 echo $pageGen->fetchHTML();}catch(Exception $e){ echo $e->getMessage(); exit();}
在運行上面的PHP代碼后,你所得到的結果是一個簡單的網頁-它包含一些前面創建的(X)HTML對象。這種情況下,如果因某些原因該網頁生成器類收到一個不正確的對象并調用它的'addHTML()'方法,那么你很容易理解將會發生的事情。在此,我重新修改了這里的沖突條件-通過使用一個不存在的(X)HTML widget對象。請再次看一下下面的代碼:
try{ //生成一些HTML元素 $h1=new Header1(array('name'=>'header1','class'=>'headerclass'),'Content for H1element goes here'); $div=new Div(array('name'=>'div1','class'=>'divclass'),'Content for Div elementgoes here'); $par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraphelement goes here'); $ul=new UnorderedList(array ('name'=>'list1','class'=>'listclass'),array('item1'=>'value1','item2'=>'value2','item3'=>'value3')); //實例化頁面生成器類 $pageGen=new Page生成器(); $pageGen->doHeader(); //添加'HTMLElement'對象 $pageGen->addHTMLElement($fakeobj) //把并不存在的對象傳遞到這個方法 $pageGen->addHTMLElement($div); $pageGen->addHTMLElement($par); $pageGen->addHTMLElement($ul); $pageGen->doFooter(); // 顯示網面 echo $pageGen->fetchHTML();}catch(Exception $e){ echo $e->getMessage(); exit();}
在這種情況中,如下面一行所顯示的:
$pageGen->addHTMLElement($fakeobj)//把不存在的對象傳遞到這個方法
一個并不存在的(X)HTML widget對象被傳遞到該頁面生成器類,這樣會導致一個致命性錯誤:
Fatal error: Call to a member function on a non-object inpath/to/file
怎么樣?這就是對傳遞到生成器類的對象的類型不進行檢查的直接懲罰!因此在編寫你的腳本時一定要記住這個問題。幸好,還有一個簡單的方案來解決這些問題,而且這也正是'instanceof'操作符的威力所在。如果你想要看一下這個操作符是如何使用的,請繼續往下讀吧。
三、 使用'instanceof'操作符
如你所見,'instanceof'操作符的使用非常簡單,它用兩個參數來完成其功能。第一個參數是你想要檢查的對象,第二個參數是類名(事實上是一個接口名),用于確定是否這個對象是相應類的一個實例。當然,我故意使用了上面的術語,這樣你就可以看到這個操作符的使用是多么直觀。它的基本語法如下:
if (object instanceof class name){ //做一些有用的事情}
現在,既然你已經了解了這個操作符在PHP 5是如何使用的,那么,為了驗證被傳遞到它的'addHTMLElement()'方法的對象的類型,讓我們再定義相應的網頁生成器類。下面是這個類的新的簽名,我在前面已經提到,它使用了'instanceof'操作符:
class PageGenerator{ private $output=''; private $title; public function __construct($title='Default Page'){$this->title=$title; } public function doHeader(){$this->output='<html><head><title>'.$this->title.'</title></head><body>'; } public function addHTMLElement($htmlElement){if(!$htmlElement instanceof HTMLElement){ throw new Exception('Invalid (X)HTML element');}$this->output.=$htmlElement->getHTML(); } public function doFooter(){$this->output.='</body></html>'; } public function fetchHTML(){return $this->output; }}
請注意,在上面的類中,為了確定所有傳遞的對象是早些時候定義的'HTMLElement'類的實例,'instanceof'操作符是如何包含在'addHTMLElement()'方法中的。現在,有可能重新構建你前面看到的網頁,在這種情況下,請確保所有的傳遞到該網頁生成器類的輸入對象都是真正的(X)HTML widget對象。下面是相應示例:
try{ //生成一些HTML元素 $h1=new Header1(array('name'=>'header1','class'=>'headerclass'),'Content for H1 element goes here'); $div=new Div(array('name'=>'div1','class'=>'divclass'),'Content for Div element goes here'); $par=new Paragraph(array('name'=>'par1','class'=>'parclass'),'Content for Paragraph element goes here'); $teststr='This is not a HTML element'; //實例化頁面生成器類 $pageGen=new Page生成器(); $pageGen->doHeader(); //添加'HTMLElement'對象 $pageGen->addHTMLElement($teststr) //把簡單的字符串傳遞到這個方法 $pageGen->addHTMLElement($h1); $pageGen->addHTMLElement($div); $pageGen->addHTMLElement($par); $pageGen->doFooter(); //顯示網頁 echo $pageGen->fetchHTML();}catch(Exception $e){ echo $e->getMessage(); exit();}
正如你在上面的示例已經看到的,我把一個簡單的測試用字符串(并不是一個'HTMLElement'對象)傳遞到該頁面生成器類中,這將通過addHTMLElement()'方法拋出一個異常-為特定的'catch'塊所捕獲,如下所示:
Invalid (X)HTML element
此時,為了確定輸入對象的有效性,我使用了'instanceof'操作符,這樣以來,可以把上面的網頁生成器類轉換成一部分更為有效的代碼片斷。我希望你能真正體會到,通過使用這個操作符,對你的類的方法的輸入進行過濾的極端重要性,這樣就可以免除外來的不正確的數據輸入。
在展示了'instanceof'操作符在網頁生成器類內的正確實現后,還有更多的事情要做。類似于我在前面一篇文章中為PHP 4所編寫的(X)HTML widget類,我想包含這個操作符作為它們的'getHTML()'方法的一部分,這樣就可以允許創建生成嵌套的(X)HTML元素的網頁。下面,讓我們討論這是如何實現的。
四、 擴展'instanceof'操作符的使用:嵌套(X)HTML widget
好。你已經看到了'instanceof'操作符在被直接注入到頁面生成器類的輸入對象進行類型檢查方面所表現出的良好功能。現在,我將再進一步來把一個檢查例程添加到(X)HTML widget類的構造器和'getHTML()'方法中,這樣它們可以接受其它的widget作為輸入參數。請檢查下面改進的類:
class Div extends HTMLElement{ private $output='<div '; private $data; public function __construct($attributes=array(),$data){if(!$data instanceof HTMLElement&&!is_string($data)){ throw new Exception('Invalid parameter type');}parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);$this->output.=($this->data instanceof HTMLElement)?$this->data->getHTML():$this->data;$this->output.='</div>';return $this->output; }}class Header1 extends HTMLElement{ private $output='<h1 '; private $data; public function __construct($attributes=array(),$data){if(!$data instanceof HTMLElement&&!is_string($data)){ throw new Exception('Invalid parameter type');}parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);$this->output.=($this->data instanceof HTMLElement)?$this->data->getHTML():$this->data;$this->output.='</h1>';return $this->output; }}class Paragraph extends HTMLElement{ private $output='<p '; private $data; public function __construct($attributes=array(),$data){if(!$data instanceof HTMLElement&&!is_string($data)){ throw new Exception('Invalid parameter type');}parent::__construct($attributes);$this->data=$data; } //'getHTML()'方法的具體實現 public function getHTML(){foreach($this->attributes as $attribute=>$value){ $this->output.=$attribute.'=''.$value.'' ';}$this->output=substr_replace($this->output,'>',-1);$this->output.=($this->data instanceof HTMLElement)?$this->data->getHTML():$this->data;$this->output.='</p>';return $this->output; }}class UnorderedList extends HTMLElement{ private $output='<ul '; private $items=array(); public function __construct($attributes=array(),$items=array()){parent::__construct($attributes);if(!is_array($items)){ throw new Exception('Invalid parameter for list items'); } $this->items=$items;}//'getHTML()'方法的具體實現public function getHTML(){ foreach($this->attributes as $attribute=>$value){$this->output.=$attribute.'=''.$value.'' '; } $this->output=substr_replace($this->output,'>',-1); foreach($this->items as $item){$this->output.=($item instanceofHTMLElement)?'<li>'.$item->getHTML().'</li>':'<li>'.$item.'</li>'; } $this->output.='</ul>'; return $this->output;}}
如上面的類所展示的,為了允許在生成相應的網頁時實現嵌套的(X)HTML元素,我分別重構了它們的構造器和'getHTML()'方法。請注意,我在每一個類的構造器中包含了下面的條件塊:
if(!$data instanceof HTMLElement&&!is_string($data)){throw new Exception('Invalid parameter type');}
至此,我實際做的是確保僅有字符串數據和'HTMLElement'類型對象允許作為每一個類的輸入參數。否則,將分別由各自方法拋出一個異常,并且有可能導致應用程序的停止執行。所以,這就是對輸入數據的檢查過程。現在,讓我們看一下'getHTML()'方法的新的簽名,其中也使用了'instanceof'操作符:
$this->output.=($this->data instanceof HTMLElement)?$this->data->getHTML():$this->data;
如你所見,在這種情況下,對于利用(X)HTML widget類的多態性特征方面this操作符是非常有用的。如果$data屬性也是一個widget,那么它的'getHTML()'方法將被正確調用,這會導致顯示嵌套的網頁元素。另一方面,如果它僅是一個字符串,那么它就被直接添加到當前類的所有輸出上。
至此,為了確保某些對象屬于一個特定的類型,你可能已經理解了PHP 5中'instanceof'操作符的用法。正如你在本文中所見,在PHP 5中強制對象類型其實是一個相當直接的工作。現在,你最好開發一個使用這個方法來過濾你的PHP應用程序中的對象的例子來加深自己的理解。
五、小結
在本文中,你學習了如何使用PHP 5中的'instanceof'操作符來檢查你的輸入對象的類型;然而,我所向你展示的方法不是唯一的。在后面的一篇中,我將向你解釋怎樣實現PHP 5中的良好的'類型提示'特征,這是實現強制對象類型的另外一種方法。
作者:朱先忠編譯出處:天極開發
