一、簡介
在PHP中實作強制物件類型有時可能非常重要。如果缺少了它,或是因為缺乏這方面的知識——基於不正確的程式設計假設,或者只是由於懶惰,那麼你會在特定的Web應用程式中看到你所不希望的結果。特別是當用PHP 4進行程式設計時,使用"is_a()"函數(儘管還有其它方法)來驗證你所使用的物件的類型是非常容易的事情。毫無疑問,強制物件類型也可以用來過濾輸入物件(需要被傳遞到同一個應用程式中的其它PHP類別)。
不過,PHP 4並沒有暴露一些有關於它的物件模型的弱點-為了實現某些在成熟的物件導向的語言中出現的特徵,它偶而可能要求編寫另外的程式碼。長時間以來,這一事實已經為PHP社群眾所周知。然而,隨著PHP 5的發行,許多這些極有價值的特徵作為改進的物件模型的一部分被添加到其中。它們將有助於更為緊密地實現基於物件的程式碼的開發-允許你使用特定的物件特徵。
在上面的情況下,當涉及到物件類型強制時應該特別注意。實際上,在一個網路應用程式的執行期間,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'-擴充HTMLElement
class 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'-擴充HTMLElement
class 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'-擴充HTMLElement
class 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'-擴充HTMLElement
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.='<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 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');
$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 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');
$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 in
path/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"操作符,這樣以來,可以把上面的網頁生成器類別轉換成一部分更為有效的程式碼片段。
的
資料輸入。
HTML widget類,我想包含這個運算元作為它們的"getHTML()"方法的一部分,這樣就可以允許建立產生嵌套的(X)HTML元素的網頁。
四、 擴充"instanceof"運算子的使用:巢狀(X)HTML 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 instanceof
HTMLElement)?'<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中的良好的"類型提示"特徵,這是實現強制對象類型的另外一種方法。
作者:朱先忠編譯出處:天極開發