PHP對待對象的方式與引用和句柄相同,即每個變量都持有對象的引用,而不是整個對象的拷貝。
當建立新對象時,該對象總是被指派,除非該對象定義了構造函數并且在出錯時抛出了一個異常。類應在被執行個體化之前定義。
建立對象時,如果該類屬于一個名字空間,則必須使用其完整名稱。
在類定義内部,可以用<code>new self</code>和<code>new parent</code>建立對象。
這段代碼的輸出如下,這是為什麼呢?
PHP 5.3引進了兩個新方法來建立一個對象的執行個體,可以使用下面的方法建立執行個體。
PHP不支援多重繼承,被繼承的方法和屬性可以通過同樣的名字重新聲明被覆寫,注意參數必須保持一緻,當然構造函數除外。但是如果父類定義方法時使用了<code>final</code>,則該方法不可被覆寫。可以通過<code>parent::</code>來通路被覆寫的方法和屬性,<code>parent::</code>隻能通路父類中的常量<code>const</code>,不能通路變量。
自PHP 5.5起,關鍵詞<code>class</code>也可用于類名的解析。使用<code>ClassName::class</code>你可以擷取一個字元串,包含了類<code>ClassName</code>的完全限定名稱。
屬性,也就是類的變量成員。屬性中的變量可以初始化,但是初始化的值必須是常數。這裡的常數是指 PHP 腳本在編譯階段時就可以得到其值,而不依賴于運作時的資訊才能求值。
在類的成員方法裡,通路非靜态屬性使用<code>$this->property</code>,通路靜态屬性使用<code>self::$property</code>。靜态屬性聲明時使用<code>static</code>關鍵字。
在定義常量時不需要<code>$</code>符号和通路控制關鍵字。
接口(interface)中也可以定義常量。
寫面向對象的應用程式時,通常對每個類的定義履歷一個PHP源檔案。當某個檔案需要調用這些類時,需要在檔案開頭寫一個長長的包含檔案清單。其實,并不需要這樣,可以定義一個<code>__autoload()</code>函數,它會在試圖使用尚未被定義的類時自動調用。
手冊Tip說,<code>spl_autoload_register()</code>提供了一種更加靈活的方式來實作類的自動加載,這個後面再看。
自動加載不可用于PHP的CLI互動模式,也就是指令行模式。
使用者輸入中可能存在危險字元,起碼要在<code>__autoload()</code>時驗證下輸入。
可以通過下面的方式自動加載類。
對于異常處理,後面再看。
PHP 5允許開發者在一個類中定義一個方法作為構造函數,構造函數也不支援重載。
如果子類中定義了構造函數,則不會隐式調用父類的構造函數,否則會如同一個普通類方法那樣從父類繼承(前提是未被定義為<code>private</code>)。要執行父類的構造函數,需要在子類構造函數中調用<code>parent::__construct()</code>。
與其它方法不同,當<code>__construct()</code>與父類<code>__construct()</code>具有不同參數時,可以覆寫。
自PHP 5.3.3起,在命名空間中,與類名同名的方法不再作為構造函數。
析構函數會在某個對象的所有引用都被删除或者對象被顯示銷毀時執行。析構函數即使在使用<code>exit()</code>終止腳本運作時也會被調用。
試圖在析構函數中抛出異常,将會導緻緻命錯誤。
類屬性必須定義為公有、受保護、私有之一,不能省略關鍵字。如果類中方法沒有設定通路控制的關鍵字,則該方法預設為公有。
同一個類的對象,即使不是同一個執行個體,也可以互相通路對方的私有與保護成員。示例程式如下。
如果一個類擴充了另一個,則父類必須在子類前被聲明。
範圍解析操作符,簡單地說就是一對冒号,可以用于通路靜态成員、類常量,還可以用于調用父類中的屬性和方法。
當在類定義之外引用這些項目時,要使用類名。
使用<code>static</code>關鍵字可以用來定義靜态方法和屬性,也可用于定義靜态變量以及後期靜态綁定。聲明類屬性或方法為靜态,就可以不執行個體化類而直接通路。
靜态屬性不能通過一個類已執行個體化的對象來通路,但靜态方法可以。
如果沒有指定通路控制,屬性和方法預設為公有。
用靜态方法調用一個非靜态方法會導緻一個<code>E_STRICT</code>級别的錯誤。
PHP 5支援抽象類和抽象方法。類中如果有一個抽象方法,那這個類必須被聲明為抽象的。
抽象類不能被執行個體化。抽象方法隻是聲明了其調用方式(參數),不能定義其具體的功能實作。繼承抽象類時,子類必須定義父類中的所有抽象方法,且這些方法的通路控制必須和父類一樣活更寬松。
方法的調用方式必須比對。但是,子類定義了一個可選參數,而父類抽象方法的聲明裡沒有,則兩者的聲明并無沖突。這也試用與PHP 5.4起的構造函數。可以在子類中定義父類簽名中不存在的可選參數。
聽說過接口,一直沒用過。使用接口,可以指定某個類必須實作哪些方法,但不需要定義這些方法的具體内容,也就是說接口中定義的所有方法都是空的。接口中定義的所有方法都必須是公有的,這是接口的特性。
接口也可以繼承多個接口,用逗号分隔,使用<code>extends</code>操作符。類中必須實作接口中定義的所有方法,否則會報錯。要實作一個接口,使用<code>implements</code>操作符。類可以實作多個接口,用逗号分隔。實作多個接口時,接口中的方法不能有重名。類要實作接口,必須使用和接口中所定義的方法完全一緻的方式。
接口中也可定義常量。接口常量和類常量的使用完全相同,但是不能被子類或子接口覆寫。
從PHP 5.4.0開始,可以使用<code>traits</code>實作代碼複用。Traits 是一種為類似 PHP 的單繼承語言而準備的代碼複用機制。Trait 不能通過它自身來執行個體化。它為傳統繼承增加了水準特性的組合。
優先順序是來自目前類的成員覆寫了 trait 的方法,而 trait 則覆寫了被繼承的方法。
通過逗号分隔,在 use 聲明列出多個 trait,可以都插入到一個類中。如果兩個 trait 都插入了一個同名的方法,如果沒有明确解決沖突将會産生一個緻命錯誤,為解決沖突,需使用<code>insteadof</code>操作符來指明使用沖突方法中的哪一個,這種方法僅允許排除掉其它方法。<code>as</code>操作符可以将其中一個沖突的方法以另一個名稱(别名)來引入。
使用<code>as</code>操作符還可以用來調整方法的通路控制,或者給方法一個改變了通路控制的别名,原版方法的通路控制規則沒有改變。
就像類能夠使用<code>trait</code>那樣,多個<code>trait</code>能夠組合為一個<code>trait</code>。
為了對使用的類施加強制要求,trait 支援抽象方法的使用。
如果<code>trait</code>定義了一個屬性,那類将不能定義同樣名稱的屬性,否則會産生錯誤。
PHP提供的重載是指動态地建立類屬性和方法,與其它絕大多數面向對象語言不同。通過魔術方法來實作。當使用不可通路的屬性或方法時,重載方法會被調用。所有的重載方法都必須被聲明為<code>public</code>。
使用<code>__get()</code>,<code>__set()</code>,<code>__isset()</code>,<code>__unset()</code>進行屬性重載,示例如下。
輸出結果如下:
在對象中調用一個不可通路方法時,<code>__call()</code>會被調用。用靜态方式中調用一個不可通路方法時,<code>__callStatic()</code>會被調用。參數為調用方法的名稱和一個枚舉數組,注意區分大小寫。
使用<code>__call()</code>和<code>__callStatic()</code>對方法重載,示例如下。
對象可以用過單元清單來周遊,例如用<code>foreach</code>語句。預設所有可見屬性都将被用于周遊。
示例程式2實作了Iterator接口的對象周遊,示例程式3通過實作IteratorAggregate來周遊對象。
PHP 将所有以<code>__</code>(兩個下劃線)開頭的類方法保留為魔術方法。定義類方法時,除魔術方法外,建議不要以<code>__</code>為字首。
前面遇到過的魔術方法有:<code>__construct()</code>,<code>__destruct()</code>,<code>__call()</code>,<code>__callStatic()</code>,<code>__get()</code>,<code>__set()</code>,<code>__isset()</code>,<code>__unset()</code>。後面将會介紹:<code>__sleep()</code>,<code>__wakeup()</code>,<code>__toString()</code>,<code>__invoke()</code>,<code>__set_state()</code>,<code>__clone()</code>和<code>__debugInfo()</code>。
<code>__sleep</code>和<code>__wakeup</code>不清楚具體做什麼用的,示例程式中給出了個資料庫連接配接的例子。
<code>__toString</code>方法用于一個類被當成字元串時應怎樣回應。此方法必須傳回一個字元串,且不能再方法中抛出異常。如果将一個未定義<code>__toString()</code>方法的對象轉換為字元串,将産生錯誤。
當嘗試以調用函數的方式調用一個對象時,<code>__invoke()</code>方法會被調用。
當調用<code>var_export()</code>導出類時,<code>__set_state()</code>會被調用。
當調用<code>var_dump()</code>時,<code>__debugInfo</code>會被調用。PHP 5.6新加入,沒合适的環境無法測試。
果父類中的方法被聲明為<code>final</code>,則子類無法覆寫該方法。如果一個類被聲明為<code>final</code>,則不能被繼承。屬性不能被定義為<code>final</code>,隻有類和方法才能被定義為<code>final</code>。
多數情況,我們不需要完全複制一個對象,但有時确實需要。對象複制可以通過<code>clone</code>關鍵字來完成。這種複制是通過調用對象的<code>__clone()</code>方法實作的,但是對象中的<code>__clone()</code>方法不能被直接調用。
比較運算符<code>==</code>為真的條件是:兩個對象的屬性和屬性值都相等,而且兩個對象是同一個類的執行個體。
繼承與統一個基類的兩個子類的對象不會相等<code>==</code>。
全等運算符<code>===</code>為真的條件是:兩個對象變量一定要指向某個類的同一個執行個體(即同一個對象)。
類型限制是指函數的參數可以指定必須為對象、接口、數組或者<code>callable</code>類型。但是類型限制不能用于标量類型如<code>int</code>或<code>string</code>,<code>traits</code>也不允許。類型限制允許<code>NULL</code>值。
後期靜态綁定,用于在繼承範圍内引用靜态調用的類。
轉發調用,指的是通過以下幾種方式進行的靜态調用:<code>self::</code>,<code>parent::</code>,<code>static::</code>以及<code>forward_static_call()</code>。
後期靜态綁定的工作原理是,存儲了上一個非轉發調用的類名。
當進行靜态方法調用時,該類名即為明确指定的那個;當進行非靜态方法調用時,即為該對象所屬的類。
使用<code>self::</code>或者<code>__CLASS__</code>對目前類的靜态引用,取決于定義目前方法所在的類。
用<code>static::</code>關鍵字表示運作時最初調用的類,後期靜态綁定就是這樣使用。如下面程式所示,也就是說調用<code>test()</code>時引用的類是<code>B</code>而不是<code>A</code>。
示例2給出的是非靜态環境下使用<code>static::</code>。
後期靜态綁定的解析,會一直到取得一個完全解析了的靜态調用為止。另外,如果靜态調用使用<code>parent::</code>或<code>self::</code>将轉發調用資訊。
那麼問題來了,結果為什麼是這樣的呢?
預設情況下,對象時通過引用傳遞的。但這種說法不完全正确,其實兩個對象變量不是引用的關系,隻是他們都儲存着同一個辨別符的拷貝,這個辨別符指向同一個對象的真正内容。
所有PHP裡面的值,都可以使用函數<code>serialize()</code>來傳回一個包含位元組流的字元串來表示。<code>unserialize()</code>函數能夠重新把字元串變為原來的值。
序列化一個對象,将會儲存對象的所有變量,但是不會儲存對象的方法,隻會儲存類的名字。為了能夠<code>unserialize()</code>一個對象,這個對象的類必須已經定義過。在應用程式中序列化對象以便在之後使用,強烈推薦在整個應用程式都包含對象的類的定義。
(全文完)