Php 3.x與4.x中關於物件程式設計的不相容問題 (轉)

gugu99發表於2007-08-15
Php 3.x與4.x中關於物件程式設計的不相容問題 (轉)[@more@]

3.x與4.x中關於的不相容問題

  “物件導向”聽起來是個很流行的詞彙,似乎到了如果你還沒有,那不如回家種白菜的地步。
  Php從版本3.x開始支援物件程式設計,雖然它的Class從一開始就飽受員們的指責,但它的確給我們帶來了意外的驚喜。一路跌跌撞撞走來,到了4.x,Php已經相當OOP了。當然,它對於類變數的處理依然不能讓人滿意,沒有私有、公有、保護、靜態的宣告方法。Php物件導向的可用性不在本文討論範圍內。
  伴隨著4.x中關於物件程式設計的完善,Php team給我們帶來了些許“麻煩”:3.x和4.x中關於物件程式設計的一些遊戲規則改變了,不相容。筆者就實際開發過程遇到的問題稍作討論,相信有些問題可能筆者尚未遇到,歡迎諸位補充、共賞。

三、魔法說
  4.x中開始出現的魔法函式,用以達到被Php小組宣稱的“不可思議”(magical)的效果。
  魔法函式一詞直譯於“magic function”。
  這是一類具有保留名字的類函式,Php小組在推薦中有這樣的表述:定義自己的類函式不應該以兩個下劃線“__”開始,因為這可能與現在或後續版本中的魔法函式發生重名衝突並將嚴重影響使用者定義類的正常工作。所以,如果你過去的類程式碼中如果定義了“__xxx”形式的方法,強烈建議你修改它們。
  魔法函式會在類遇到某些事件時自動觸發,好像某些中的。截至php-4.3.2RC4,共有兩個魔法函式:__sleep和__wakeup。從字面上看就是說這兩個函式分別在物件發生睡著動作或甦醒動作時觸發,事實上是__sleep在物件被序列化(serialize)時觸發,__wakeup在物件被反序列化(unserialize)時觸發。
  說到這裡,可能已經讓人覺得枯燥了,因為還沒有出現什麼意外的驚喜激勵我們。看看下面的例子。

1、

  class ABaby
  {
  var $fileName,$fileOpen,$offset,$length;
  function ABaby(){
  $this->fileName='ABaby.class.php';
  $this->fileOpen=fopen($this->fileName,'rb');
  $this->offset=0;
  $this->length=0;
  }
  function getData($offset,$length){
  $this->offset=$offset;
  $this->length=$length;
  fseek($this->fileOpen,$offset);
  return(fread($this->fileOpen,$length));
  }
  function getLastData(){
  return($this->getData($this->offset,$this->length));
  }
  }
  $ABaby=new ABaby();
  echo($ABaby->getData(7,77));
  $ABabyBed=serialize($ABaby);
  $newABaby=unserialize($ABabyBed);
  echo($newABaby->getLastData());

註釋:
  這個類的任務是開啟一個讀取指定位置和位元組長度資料,並可以返回最近一次讀取的資料。例項化一個物件後,首先讀取指定檔案第7個位元組開始共77位元組的內容,然後把這個類序列化儲存進一個字串,再從這個字串反序列化出物件後輸出最近一次讀取的資料。這次不用__wakeup。
  將上面程式碼儲存成檔案ABaby.class.php並執行。出錯了:supplied argument is not a valid stream re!這是因為序列化時解析器把$fileOpen迴歸了它的本來面目——integer,已經不是原來意義上的檔案流操作控制程式碼(resource),實施上,這時候檔案還是處於開啟狀態的,可以把最後一行換作“echo($ABaby->getLastData());”證實。同樣的情況還會發生在資料庫連線、連線等其他控制程式碼上,讀者可以自行測試一下。
  下面我們透過魔法函式__wakeup修正。當然不是一定要用__wakeup,比如你也可以首先反序列化得到物件$newABaby中,然後將例項變數$offset和$length儲存到另外一處,然後$newABaby的構造器,然後呼叫getData()方法,這樣就可以達到呼叫getLastData()符合預期的目的。一連這麼多個然後......現在看__wakeup的魔力。

2、

  class ABaby
  {
  var $fileName,$fileOpen,$offset,$length;
  function ABaby(){
  $this->fileName='ABaby.class.php';
  $this->fileOpen=fopen($this->fileName,'rb');
  $this->offset=0;
  $this->length=0;
  }
  function getData($offset,$length){
  $this->offset=$offset;
  $this->length=$length;
  fseek($this->fileOpen,$offset);
  return(fread($this->fileOpen,$length));
  }
  function getLastData(){
  return($this->getData($this->offset,$this->length));
  }
  function __wakeup(){
  $this->fileOpen=fopen($this->fileName,'rb');
  }
  }
  $ABaby=new ABaby();
  echo($ABaby->getData(7,77));
  $ABabyBed=serialize($ABaby);
  $newABaby=unserialize($ABabyBed);
  echo($newABaby->getLastData());

註釋:
  透過增加一個魔法函式__wakeup修正上面的問題。
  執行上面的指令碼應該一切正常。unserialize()操作過程的最後一步會自動檢測類是否定義了__wakeup()魔法函式,有則自動呼叫。這裡面我們透過__wakeup()只是簡單的重新開啟檔案流操作控制程式碼。可以在__wakeup()中加入“echo('ABaby');”證實它確實被自動呼叫了。

  上面的程式碼存在一個“隱患”。
  我們序列化一個物件後,通常意味著近期不會馬上使用它,但是上面的程式碼我們卻沒有顯式的關閉開啟的檔案流操作控制程式碼。雖然,幾乎每門語言都宣稱自己的垃圾回收機制可以自動釋放掉不用的資源,但是實際並不總是這樣。所以及時釋放掉不用的資源是個好的習慣。下面我們透過__sleep()魔法函式告訴解析器一旦這個物件被序列化,它應該釋放掉佔用的控制程式碼。
3、

  class ABaby
  {
  var $fileName,$fileOpen,$offset,$length;
  function ABaby(){
  $this->fileName='ABaby.class.php';
  $this->fileOpen=fopen($this->fileName,'rb');
  $this->offset=0;
  $this->length=0;
  }
  function getData($offset,$length){
  $this->offset=$offset;
  $this->length=$length;
  fseek($this->fileOpen,$offset);
  return(fread($this->fileOpen,$length));
  }
  function getLastData(){
  return($this->getData($this->offset,$this->length));
  }
  function __sleep(){
  fclose($this->fileOpen);
  }
  function __wakeup(){
  $this->fileOpen=fopen($this->fileName,'rb');
  }
  }
  $ABaby=new ABaby();
  echo($ABaby->getData(7,77));
  $ABabyBed=serialize($ABaby);
  $newABaby=unserialize($ABabyBed);
  echo($newABaby->getLastData());

註釋:
  試著增加__sleep()在序列化後釋放掉不再使用的、被遺忘的控制程式碼。
  執行上面的指令碼。出錯了:__sleep()應該返回一個陣列,這個屬組包含類中的例項變數的名字。這是__sleep()的精緻之處:可以告訴解析器哪些變數需要被序列化儲存下來。這個屬組不包含的例項變數(注意這個陣列存放的實際是變數名而非值)將被拋棄,這樣可以給序列化產生的字串瘦身、並在反序列化時提速。現在看來,__sleep()可以從兩個方面:
  1、釋放資源;
  2、有選擇性的序列化例項變數。

  修正上面的程式碼如下:
4、

  class ABaby
  {
  var $fileName,$fileOpen,$offset,$length;
  function ABaby(){
  $this->fileName='ABaby.class.php';
  $this->fileOpen=fopen($this->fileName,'rb');
  $this->offset=0;
  $this->length=0;
  }
  function getData($offset,$length){
  $this->offset=$offset;
  $this->length=$length;
  fseek($this->fileOpen,$offset);
  return(fread($this->fileOpen,$length));
  }
  function getLastData(){
  return($this->getData($this->offset,$this->length));
  }
  function __sleep(){
  fclose($this->fileOpen);
  $arr=array();
  array_push($arr,'fileName');
  array_push($arr,'offset');
  array_push($arr,'length');
  return($arr);
  }
  function __wakeup(){
  $this->fileOpen=fopen($this->fileName,'rb');
  }
  }
  $ABaby=new ABaby();
  echo($ABaby->getData(7,77));
  $ABabyBed=serialize($ABaby);
  $newABaby=unserialize($ABabyBed);
  echo($newABaby->getLastData());

註釋:
  修正了__sleep()定義
  執行正常,結果符合預期。

  最後補充一點有關物件序列化的內容。物件序列化通常被用來儲存物件當時的狀態以備後用,更適合於跨頁、跨時間、跨/傳遞物件格式的資料。
  對於跨頁傳遞物件,可能會有人提出直接使用session存放物件,這未嘗不可。不過有兩個問題值得注意:
  1、事實上變數被存放進session時會被自動序列化成字串再存入;從session中讀取物件時,解析器會首先反序列化(unserialize())再返回物件。自然,魔法函式對這個過程也應該是有效的。
  2、由於session是會話級變數,所有使用session_start的頁面都必須包含類定義的指令碼檔案,因為在session_start時會自動發序列化(unserialize())存放的物件(前面自動序列化得到的字串)
  這樣看來,向session中存放物件要付出一定的代價。所以個人建議是將要儲存的物件序列化得到的字串儲存進session,使用時再顯式的反序列化出例項物件。
  關於魔法函式的說告一段落。
  至此,本人在Php物件程式設計中遇到的幾個典型的相容性問題以及部分心得基本介紹完畢。水平所限,有所疏漏或謬誤懇請指正。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-957974/,如需轉載,請註明出處,否則將追究法律責任。

相關文章