天天看點

php中"::"雙冒号有什麼作用

PHP代碼

lazycms::$sysname  

第一次見到這種表現方式,請問是雙冒号什麼意思 

答:

直接屬類的方法或屬性。

也就是static 靜态方法或屬性的使用。

域運算符,一般用于在B類對象中使用A類對象的屬性/方法!

本文描述 PHP V5 中對象和類的基礎知識,從最基本的概念一直講到繼承,主要針對經驗豐富的面向對象程式員和尚未接觸過對象的讀者。

作為 PHP 程式員,您肯定知道變量和函數。但類和對象可能就是另一回事。不定義單個類,就可以建立完美的系統。但即使您決定在自己的代碼中不使用面向對象的程式設計,您仍可能需要了解面向對象的程式設計。例如,如果使用第三方庫,比如通過 PHP Extension and Application Repository (PEAR) 可以使用的庫,您将發現自己在執行個體化對象和調用方法。

什麼是類和對象?

簡單地說,類 是一個由變量和方法組成的獨立塊或束。這些元件通常結合實作單個責任或一組責任。在本文中,您将建立一個類,該類收集了用于查詢和填充由項和值組成的詞典的方法。

類可以直接用作組織資料和功能的簡單方法,就像一組函數和變量一樣。但使用類可以忽略它的存在。類可用于在記憶體中生成多個執行個體。這樣的執行個體叫做對象。每個對象可以通路一組相同的函數(在面向對象上下文中叫做方法)和變量(叫做特性或執行個體變量),但每個變量的實際值在每個對象中是不同的。

考慮角色扮演遊戲中的一個單元——比如坦克。類可能為坦克設定一組變量:防禦和進攻能力,範圍,健康狀況,等等。該類也可能定義一組函數,其中包括 move() 和 attack()。當系統包含一個坦克類時,該類可用于生成數十個或數百個坦克對象,每個對象都潛在地具有自己的健康狀況或範圍特征。是以,類是用于生成對象的藍圖或模闆。

了解類和對象最簡單的方法可能就是建立一些類和對象。

第一個類

可以用 class 關鍵字建立類。最簡單的情況是,類由關鍵字類、名稱和代碼塊組成:

class Dictionary {   

}  

類名可以包含字母、數字和下劃線字元的任何組合,但不能以數字打頭。

上例中的 Dictionary 類盡管用處有限,但完全合法。那麼如何使用該類來建立一些對象呢?

$obj1 = new Dictionary();   

$obj2 = new Dictionary();   

$obj3 = new Dictionary();  

至少在形式上,執行個體化對象與調用函數相似。對于函數調用,必須提供圓括号。與函數一樣,一些類需要您為其傳遞參數。您還必須使用 new 關鍵字。這就告訴 PHP 引擎您希望執行個體化一個新對象。然後,傳回的對象可以存儲在一個變量中以供将來使用。

屬性

在類的主體中,可以聲明叫做屬性的特殊變量。在 PHP V4 中,屬性必須用關鍵字 var 調用。這仍是合法的文法,但主要是為了向後相容。在 PHP V5 中,屬性必須聲明為 public、private 或 protected。可以在 關鍵字:在此我們是否可以有一點隐私?中閱讀有關這些限定詞的内容。但現在在例子中将所有屬性聲明為 public。清單 1 顯示一個聲明了兩個屬性的類。

清單 1. 聲明兩個屬性的類

     public $translations = array();   

     public $type ="En";   

正如所看到的,可以同時聲明屬性并為其指派。可以用 print_r() 函數快速浏覽一下對象的狀态。清單 2 顯示 Dictionary 對象現在具有更多成員。

清單 2. Dictionary 對象一覽

$en = new Dictionary();   

print_r( $en );    

如果運作該腳本,将看到如下對象的輸出:

Dictionary Object   

(   

     [translations] => Array   

         (   

         )   

     [type] => En   

)  

可以使用對象操作符 -> 通路公共對象屬性。是以 $en->type 表示由 $en 引用的 Dictionary 對象的 $type 屬性。如果可以通路屬性,就意味着可以設定和獲得其值。清單 3 中的代碼建立 Dictionary 類的兩個執行個體 —— 換言之,它執行個體化兩個 Dictionary 對象。它更改一個對象的 $type 屬性,并添加兩個對象的翻譯:

清單 3. 建立 Dictionary 類的兩個執行個體

$en->translations['TREE'] = "tree";   

$fr = new Dictionary();   

$fr->type = "Fr";   

$fr->translations['TREE'] = "arbre";   

foreach ( array( $en, $fr ) as $dict ) {   

     print "type: {$dict->type} ";   

     print "TREE: {$dict->translations['TREE']}/n";   

該腳本輸出如下

type: En TREE: tree

type: Fr TREE: arbre

是以 Dictionary 類現在比較有用了。單個對象可以存儲不同的鍵值組合,還有一個标志,該标志告訴用戶端有關這種 Dictionary 的詳細資訊。

盡管 Dictionary 類目前與關聯數組的包裝器相差無幾,但這裡有一些了解對象功能的線索。目前,我們已經可以很好地表示我們的示例資料了,如清單 4 所示。

清單 4. 示例資料

$en = array(   

     'translations'=>array( 'TREE' => 'tree' ),   

     'type'=>'En'  

);   

$fr = array(   

     'translations'=>array( 'TREE' => 'arbre' ),   

     'type'=>'Fr'  

);  

雖然該資料結構完成了與 Dictionary 類相同的目的,但它沒有提供結構的保證。如果傳遞 Dictionary 對象,我們知道它具有 $translations 屬性。但如果是一個關聯資料,則沒有這樣的保證。這個事實使得類似 $fr['translations']['TREE']; 的查詢有些碰運氣,除非進行查詢的代碼确定數組的起源。這是對象的重點:對象的類型是其特征的保證。

雖然用對象存儲資料有優點,但是您可能沒有一點感覺。對象可以是東西,但關鍵在于它們還可以做事情。

方法

簡單地說,方法是在類中聲明的函數。它們通常(但不總是)通過對象執行個體使用對象操作符來調用的。清單 5 向 Dictionary 類中添加一個方法,并調用該方法。

清單 5. 向 Dictionary 類中添加方法

     function summarize() {   

         $ret   = "Dictionary type: {$this->type}/n";   

         $ret .= "Terms: ".count( $this->translations )."/n";   

         return $ret;   

     }   

}   

print $en->summarize();   

它提供如下輸出:

Dictionary type: En

Terms: 1

正如所看到的,聲明 summarize() 方法與聲明任何函數的方式一樣,隻不過它是在類中聲明。summarize() 方法是通過 Dictionary 執行個體使用對象操作符調用的。summarize() 函數通路屬性來提供對象狀态的簡述。

注意對于本文來說的一個新特性的用法。$this 僞變量提供了一種用于對象引用自己的屬性和方法的機制。在對象外部,可以使用句柄來通路它的元素(在本例子中是 $en)。在對象内部,則無此句柄,是以必須求助于 $this。如果覺得 $this 有些迷惑,則在代碼中遇到它時,試着在頭腦中用目前執行個體 替換它。

類通常使用通用模組化語言 (Universal Modeling Language,UML) 表示在圖表中。UML 的詳細資訊超出了本文的範圍,但這種圖表不過是一種可視化類關系的好方法。圖 1 顯示用 UML 表示的 Dictionary 類。類名位于頂層,屬性在中間,方法在底層。

構造函數

PHP 引擎識别許多“魔術”方法。如果定義了方法,則 PHP 引擎将在相應的情況發生時自動調用這些方法。最常實作的方法是構造函數方法。PHP 引擎在執行個體化對象時調用構造函數。對象的所有基本設定代碼都放在構造函數中。在 PHP V4 中,通過聲明與類同名的方法來建立構造函數。在 V5 中,應聲明叫做 __construct() 的方法。清單 6 顯示需要 DictionaryIO 對象的構造函數。

清單 6. 需要 DictionaryIO 對象的構造函數

     public $type;   

     public $dictio;   

     function __construct( $type, DictionaryIO $dictio ) {   

         $this->type = $type;   

         $this->dictio=$dictio;   

     //...   

要執行個體化 Dictionary 對象,需要将類型字元串和 DictionaryIO 對象傳遞給它的構造函數。構造函數使用這些參數來設定自有屬性。下列代碼顯示可以如何執行個體化 Dictionary 對象:

$en = new Dictionary( "En", new DictionaryIO() );  

Dictionary 類現在比以前更安全。所有 Dictionary 對象都已經用必需的參數初始化過了。

當然,還無法阻止一些人随後更改 $type 屬性或将 $dictio 設定為空。可喜的是,PHP V5 可以幫助您實作這一功能。

關鍵字:在此我們是否可以有一點隐私?

前面已經看到與屬性聲明相關的 public 關鍵字。該關鍵字表示屬性的可見度。事實上,屬性的可見度可以設定為 public、private 和 protected。聲明為 public 的屬性可以在類外部寫入和讀取,聲明為 private 的屬性隻在對象或類上下文中可見。聲明為 protected 的屬性隻能在目前類及其子類的上下文中可見。(在 繼承 部分将會看到這些内容起作用。)可以使用 private 屬性來真正鎖定類。如果将屬性聲明為 private 并試圖從類範圍外部通路它(如清單 7 所示),PHP 引擎将抛出緻命錯誤。

清單 7. 試圖從類範圍外部通路屬性

     private $translations = array();   

     private $dictio;   

     private $type;   

         $this->dictio = $dictio;   

     // ...   

$en = new Dictionary( "En", new DictionaryIO() );   

$en->dictio = null;   

輸出如下:

Fatal error: Cannot access private property 

Dictionary::$dictio in...

一般來說,應将大多數屬性聲明為 private,然後根據需要提供獲得和設定這些屬性的方法。這樣就可以控制類的接口,使一些資料隻讀,在将參數配置設定給屬性之前對參數進行清理或過濾,并提供與對象互動的一套明确的規則。

修改方法可見度的方法與修改屬性可見度的方法一樣,即在方法聲明中添加 public、private 或 protected。如果類需要使用一些外部世界無需知道的家務管理方法,則可以将其聲明為 private。在清單 8 中,get() 方法為 Dictionary 類的使用者提供了提取翻譯的接口。該類還需要跟蹤所有查詢,是以提供了 private 方法 logQuery()。

清單 8. get() 方法為 Dictionary 類的使用者提供了接口

function get( $term ) {   

     $value = $this->translations[$term];   

     $this->logQuery( $term, $value, "get" );   

     return $value;   

private function logQuery( $term, $value, $kind ) {   

     // write log information   

将 logQuery() 聲明為 private 簡化了公共接口,而且防止了類不适當地調用 logQuery()。與屬性一樣,嘗試從包含類外部調用私有方法将導緻緻命錯誤。

在類上下文操作

到目前為止,所看到的方法和屬性都在對象上下文中進行操作。也就是說,必須使用對象執行個體,通過 $this 僞變量或标準變量中存儲的對象引用來通路方法和屬性。有時候,可能發現通過類而不是對象執行個體來通路屬性和方法更有用。這種類成員叫做靜态 成員。

要聲明靜态屬性,将關鍵字 static 放在可見度修飾符後面,直接位于屬性變量前面。

下例顯示單個靜态屬性:$iodir,存放用于儲存和讀取 Dictionary 資料的預設目錄的路徑。因為該資料對于所有對象是相同的,是以讓它可用于所有執行個體是有意義的。

清單 9. 單個靜态 $iodir 屬性

     public static $iodir=".";   

可以使用範圍解析操作符來通路靜态屬性,該操作符由雙冒号 (::) 組成。範圍解析操作符應位于類名和希望通路的靜态屬性之間。

print Dictionary::$iodir . "/n";   

Dictionary::$iodir = "/tmp";  

正如所看到的,通路該屬性無需執行個體化 Dictionary 對象。

聲明和通路靜态方法的文法與此相似。再次,應将 static 關鍵字放在可見度修飾符後。清單 10 顯示了兩個靜态方法,它們通路聲明為 private 的 $iodir 屬性。

清單 10. 通路 $iodir 屬性的兩個靜态方法

     private static $iodir=".";   

     public static function setSaveDirectory( $dir ) {   

         if ( ! is_dir( $dir ) ||    

             ! is_writable( $dir ) ) {   

             return false;   

         }   

         self::$iodir = $dir;   

     public static function getSaveDirectory( ) {   

         return self::$iodir;   

}   

使用者不再能通路 $iodir 屬性目錄了。通過建立特殊方法來通路屬性,可以確定所提供的任何值是健全的。在本例中,方法在進行配置設定前檢查給定字元串指向可寫入的目錄。

注意,兩個方法都使用關鍵字 self 和通路解析操作符來引用 $iodir 屬性。不能在靜态方法中使用 $this,因為 $this 是對目前對象執行個體的引用,但靜态方法是通過類而不是通過對象調用的。如果 PHP 引擎在靜态方法中看到 $this,它将抛出緻命錯誤和一條提示消息。

要從類外部調用靜态方法,可使用類名加上範圍解析符和方法名。

Dictionary::setSaveDirectory("/tmp");   

print Dictionary::getSaveDirectory();  

需要使用靜态方法有兩個重要原因。首先,實用程式操作可能不需要對象執行個體來做它的工作。通過聲明為靜态,為客戶機代碼節省了建立對象的工作量。第二,靜态方法是全局可用的。這意味着可以設定一個所有對象執行個體都可以通路的值,而且使得靜态方法成為共享系統上關鍵資料的好辦法。

盡管靜态屬性通常被聲明為 private 來防止别人幹預,但有一種方法可以建立隻讀靜态範圍的屬性,即聲明常量。與全局屬性一樣,類常量一旦定義就不可更改。它用于狀态标志和程序生命周期中不發生更改的其他東西,比如 pi 或非洲的所有國家。

清單 11. 将 MAXLENGTH 設定為類常量

     const MAXLENGTH = 250;   

     // ...    

print Dictionary::MAXLENGTH;  

類常量始終為 public,是以不能使用可見度關鍵字。這并是問題,因為任何更改其值的嘗試都将導緻解析錯誤。還要注意,與正常屬性不同,類常量不以美元符号開始。

繼承

如果熟悉面向對象程式設計,您将知道我一直把最好的留到最後。類及其生成的動态對象之間的關系使得系統更靈活。例如,每個 Dictionary 對象封裝不同的翻譯資料集合,但是這些不同實體的模型定義在單個 Dictionary 類中。

但有時候需要記下類級别的差異。是否記得 DictionaryIO 類?扼要重述一下,它從 Dictionary 對象中擷取資料,将其寫入檔案系統,從一個檔案中擷取資料,将其合并回到 Dictionary 對象中。清單 12 顯示使用序列化來儲存和加載 Dictionary 資料的快速實作。

清單 12. 使用序列化的快速實作

     function asArray() {   

         return $this->translations;   

     function getType() {   

         return $this->type;   

     function export() {   

         $this->dictio->export( $this );   

     function import() {   

         $this->dictio->import( $this );   

class DictionaryIO {   

     function path( Dictionary $dictionary, $ext ) {   

         $path   = Dictionary::getSaveDirectory();   

         $path .= DIRECTORY_SEPARATOR;   

         $path .= $dictionary->getType().".$ext";   

         return $path;   

     function export( Dictionary $dictionary ) {   

         $translations = $dictionary->asArray();   

         file_put_contents( $this->path(   

                           $dictionary, 'serial'),    

                           serialize( $translations ) );     

     function import( Dictionary $dictionary ) {   

         $path = $this->path( $dictionary, 'serial' );   

         if ( ! is_file( $path ) ) return false;    

         $translations = unserialize(    

                         file_get_contents( $path ) );   

         foreach ( $translations as $term => $trans ) {   

             $dictionary->set( $term, $trans );   

$dict = new Dictionary( "En", new DictionaryIO() );   

$dict->set( "TREE", "tree" );   

$dict->export();   

本例引入兩個簡單的 Dictionary 方法,具體來說,asArray() 傳回 $translations 數組的副本。DictionaryIO 實作具有簡約的優點。因為在示例代碼中通常省略了錯誤檢查,即便如此,這仍是将資料儲存到檔案中的快速簡單的方法。

一旦部署了這種庫之後,則需要立即支援它的儲存格式。讓格式過時會冒犯那些可能以這種方式存儲備份的使用者的願望。但要求改變了,而且還可能收到輸出格式不友善使用者編輯的抱怨。這些使用者希望将導出檔案以 XML 格式發送給第三方。

現在面臨一個問題。如何在 DictionaryIO 接口中支援兩種格式?

一個解決方案是在 export() 和 import() 方法中使用條件語句,測試類型标志,如清單 13 所示。

清單 13. 在 export() 和 import() 方法中使用條件語句

function export( Dictionary $dictionary ) {   

     if ( $this->type == DictionaryIO::SERIAL ) {   

         // write serialized data   

     } else if ( $this->type == DictionaryIO::XML ) {   

         // write xml data   

function import( Dictionary $dictionary ) {   

         // read serialized data   

         // read xml data   

這種結構是壞“代碼味道”的一個例子,原因在于它依賴于複制。在一個地方進行更改(比如,添加新類型測試)需要在其他地方進行一組相應的更改(将其他類型測試帶入行中),代碼很快就會變得易錯難讀。

繼承提供了更優雅的解決方案。可以建立一個新類 XmlDictionaryIO,該類繼承由 DictionaryIO 設定的接口,但覆寫其中一些功能。

使用 extends 關鍵字建立子類。如下是 XmlDictionaryIO 類的最小實作:

XmlDictionaryIO extends DictionaryIO {   

XmlDictionaryIO 現在的功能與 DictionaryIO 完全相同。因為它從 DictionaryIO 繼承了所有的公共(和保護)屬性,是以可以将應用于 DictionaryIO 對象的相同操作應用于 XmlDictionaryIO 對象。這種關系擴充到對象類型。XmlDictionaryIO 對象顯然是 XmlDictionaryIO 類的執行個體,但它也是 DictionaryIO 的執行個體 —— 同樣地,以一般化的順序,一個人同時是人類、哺乳動物和動物。可以使用 instanceof 操作符來測試這一點,如果對象是指定類的成員,則傳回 true,如清單 14 所示。

清單 14. 使用 instanceof 操作符測試繼承

$dictio = new XmlDictionaryIO();   

if ( $dictio instanceof XmlDictionaryIO ) {   

     print "object is an instance of XmlDictionaryIO/n";   

if ( $dictio instanceof DictionaryIO ) {   

     print "object is an instance of DictionaryIO/n";   

object is an instance of XmlDictionaryIO

object is an instance of DictionaryIO

正如 instanceof 接受 $dictio 是 DictionaryIO 對象,是以方法也将接受這些對象作為參數。這意味着 XmlDictionaryIO 對象可以被傳遞給 Dictionary 類的構造函數,即使 DictionaryIO 是由構造函數的簽名指定的類型。

清單 15 是快而髒的 XmlDictionaryIO 實作,使用 DOM 來完成 XML 功能。

清單 15. XmlDictionaryIO 實作

class XmlDictionaryIO extends DictionaryIO {   

         $doc = new DOMDocument("1.0");   

         $dic_el = $doc->createElement( "dictionary" );    

         $doc->appendChild( $dic_el );   

         foreach ( $translations as $key => $val ) {   

             $term_el = $doc->createElement( "term" );   

             $dic_el->appendChild( $term_el );   

             $key_el = $doc->createElement("key", $key );   

             $val_el = $doc->createElement(   

                       "value", $val );   

             $term_el->appendChild( $key_el );   

             $term_el->appendChild( $val_el );   

         file_put_contents( $this->path(    

                           $dictionary, 'xml'),    

                           $doc->saveXML() );   

         $path = $this->path( $dictionary, 'xml');   

         if ( ! is_file( $path ) ) return false;   

         $doc = DOMDocument::loadXML(    

               file_get_contents( $path ) );   

         $termlist = $doc  

                     ->getElementsByTagName( "term" );   

         foreach ( $termlist as $term ) {   

             $key = $term->getElementsByTagName( "key" )   

                   ->item( 0 )->nodeValue;   

             $val = $term  

                   ->getElementsByTagName( "value" )   

             $dictionary->set( $key, $val );    

有關獲得并生成 XML 的詳細資訊是當然要介紹的。有許多方法能完成這一操作,其中包括完美的 SimpleXML 擴充。簡言之,import() 方法以 XML 文檔為參數,并使用它來填充 Dictionary 對象。export() 方法從 Dictionary 對象中取得資料,并将其寫入 XML 檔案中。(在現實世界中,可能會使用叫做 XLIFF 的基于 XML 的格式,該格式适用于導入到第三方翻譯工具中。)

注意,import() 和 export() 都調用實用程式方法 path(),該方法不存在于 XmlDictionaryIO 類中。但沒有關系,因為 path() 在 DictionaryIO 中實作。當 XmlDictionaryIO 實作一個方法時,則當調用該方法時,會為 XmlDictionaryIO 對象調用該實作。當沒有任何實作存在時,調用失敗傳回給父類。

結束語

由于篇幅有限,是以不可能全部介紹。進一步研究有兩個方向:廣度和深度。廣度指的是超出本文範圍的那些特性,比如抽象類、接口、疊代器接口、反射、異常和對象複制。深度指的是設計問題。盡管了解 PHP 中可用于面向對象程式設計的工具範圍很重要,但考慮如何最佳使用這些特性同樣重要。幸運的是,專門講述面向對象上下文中設計模式的可用參考資料很多

如何聯系我:【萬裡虎】www.bravetiger.cn

【QQ】3396726884 (咨詢問題100元起,幫助解決問題500元起)

【部落格】http://www.cnblogs.com/kenshinobiy/