基本概念
PHP對待對象的方式與引用和控制代碼相同,即每個變數都持有對象的引用,而不是整個對象的拷貝。
當建立新對象時,該對象總是被賦值,除非該對象定義了建構函式並且在出錯時拋出了一個異常。類應在被執行個體化之前定義。
建立對象時,如果該類屬於一個名字空間,則必須使用其完整名稱。
在類定義內部,可以用new self和new parent建立對象。
var = '$assigned will have this value.';$instance = null;var_dump($instance);var_dump($reference);var_dump($assigned);
這段代碼的輸出如下,這是為什麼呢?
nullnullobject(stdClass)[1] public 'var' => string '$assigned will have this value.' (length=31)
PHP 5.3引進了兩個新方法來建立一個對象的執行個體,可以使用下面的方法建立執行個體。
PHP不支援多重繼承,被繼承的方法和屬性可以通過同樣的名字重新聲明被覆蓋,注意參數必須保持一致,當然建構函式除外。但是如果父類定義方法時使用了final,則該方法不可被覆蓋。可以通過parent::來訪問被覆蓋的方法和屬性,parent::只能訪問父類中的常量const,不能訪問變數。
name;}}class B extends A {private $name = 'B';const conname = 'B';public function getName() {return $this->name;}public function getParent() {return parent::conname;}}class C extends B {private $name = 'C';const conname = 'C';public function getName() {return $this->name;}public function getParent() {return parent::conname;}}$a = new A;var_dump($a->getName()); // A$b = new B;var_dump($b->getName()); // Bvar_dump($b->getParent()); // A$c = new C;var_dump($c->getName()); // Cvar_dump($c->getParent()); // B
自PHP 5.5起,關鍵詞class也可用於類名的解析。使用ClassName::class你可以擷取一個字串,包含了類ClassName的完全限定名稱。
屬性
屬性,也就是類的變數成員。屬性中的變數可以初始化,但是初始化的值必須是常數。這裡的常數是指 PHP 指令碼在編譯階段時就可以得到其值,而不依賴於運行時的資訊才能求值。
在類的成員方法裡,訪問非靜態屬性使用$this->property,訪問靜態屬性使用self::$property。靜態屬性聲明時使用static關鍵字。
類常量
在定義常量時不需要$符號和存取控制關鍵字。
介面(interface)中也可以定義常量。
自動載入類
寫物件導向的應用程式時,通常對每個類的定義簡曆一個PHP源檔案。當某個檔案需要調用這些類時,需要在檔案開頭寫一個長長的包含檔案清單。其實,並不需要這樣,可以定義一個__autoload()函數,它會在試圖使用尚未被定義的類時自動調用。
手冊Tip說,spl_autoload_register()提供了一種更加靈活的方式來實作類別的自動載入,這個後面再看。
自動載入不可用於PHP的CLI互動模式,也就是命令列模式。
使用者輸入中可能存在危險字元,起碼要在__autoload()時驗證下輸入。
可以通過下面的方式自動載入類。
對於異常處理,後面再看。
建構函式和解構函式
PHP 5允許開發人員在一個類中定義一個方法作為建構函式,建構函式也不支援重載。
如果子類中定義了建構函式,則不會隱式調用父類的建構函式,否則會如同一個普通類方法那樣從父類繼承(前提是未被定義為private)。要執行父類的建構函式,需要在子類建構函式中調用parent::__construct()。
與其它方法不同,當__construct()與父類__construct()具有不同參數時,可以覆蓋。
自PHP 5.3.3起,在命名空間中,與類名同名的方法不再作為建構函式。
解構函式會在某個對象的所有引用都被刪除或者對象被顯示銷毀時執行。解構函式即使在使用exit()終止指令碼運行時也會被調用。
試圖在解構函式中拋出異常,將會導致致命錯誤。
存取控制
類屬性必須定義為公有、受保護、私人之一,不能省略關鍵字。如果類中方法沒有設定存取控制的關鍵字,則該方法預設為公有。
同一個類的對象,即使不是同一個執行個體,也可以互相訪問對方的私人與保護成員。樣本程式如下。
foo = $foo;}private function bar() {echo 'Accessed the private method.';}public function baz(Test $other) {$other->foo = 'hello';var_dump($other->foo);$other->bar();}}$test = new Test('test');$test->baz(new Test('other'));
對象繼承
如果一個類擴充了另一個,則父類必須在子類前被聲明。
範圍解析操作符
範圍解析操作符,簡單地說就是一對冒號,可以用於訪問靜態成員、類常量,還可以用於調用父類中的屬性和方法。
當在類定義之外引用這些項目時,要使用類名。
static
使用static關鍵字可以用來定義靜態方法和屬性,也可用於定義靜態變數以及後期靜態繫結。聲明類屬性或方法為靜態,就可以不執行個體化類而直接存取。
靜態屬性不能通過一個類已執行個體化的對象來訪問,但靜態方法可以。
如果沒有指定存取控制,屬性和方法預設為公有。
用靜態方法調用一個非靜態方法會導致一個E_STRICT層級的錯誤。
抽象類別
PHP 5支援抽象類別和抽象方法。類中如果有一個抽象方法,那這個類必須被聲明為抽象的。
抽象類別不能被執行個體化。抽象方法只是聲明了其調用方式(參數),不能定義其具體的功能實現。繼承抽象類別時,子類必須定義父類中的所有抽象方法,且這些方法的存取控制必須和父類一樣活更寬鬆。
方法的調用方式必須匹配。但是,子類定義了一個選擇性參數,而父類抽象方法的聲明裡沒有,則兩者的聲明並無衝突。這也試用與PHP 5.4起的建構函式。可以在子類中定義父類簽名中不存在的選擇性參數。
prefixName('Pacman');echo $class->prefixName('Pacwoman');
對象介面
聽說過介面,一直沒用過。使用介面,可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容,也就是說介面中定義的所有方法都是空的。介面中定義的所有方法都必須是公有的,這是介面的特性。
介面也可以繼承多個介面,用逗號分隔,使用extends操作符。類中必須實現介面中定義的所有方法,否則會報錯。要實現一個介面,使用implements操作符。類可以實現多個介面,用逗號分隔。實現多個介面時,介面中的方法不能有重名。類要實現介面,必須使用和介面中所定義的方法完全一致的方式。
介面中也可定義常量。介面常量和類常量的使用完全相同,但是不能被子類或子介面覆蓋。
traits
從PHP 5.4.0開始,可以使用traits實現代碼複用。Traits 是一種為類似 PHP 的單繼承語言而準備的代碼複用機制。Trait 不能通過它自身來執行個體化。它為傳統繼承增加了水平特性的組合。
優先順序是來自當前類的成員覆蓋了 trait 的方法,而 trait 則覆蓋了被繼承的方法。
通過逗號分隔,在 use 聲明列出多個 trait,可以都插入到一個類中。如果兩個 trait 都插入了一個同名的方法,如果沒有明確解決衝突將會產生一個致命錯誤,為解決衝突,需使用insteadof操作符來指明使用衝突方法中的哪一個,這種方法僅允許排除掉其它方法。as操作符可以將其中一個衝突的方法以另一個名稱(別名)來引入。
smallTalk(); // b$t->bigTalk(); // A$t->talk(); // B
使用as操作符還可以用來調整方法的存取控制,或者給方法一個改變了存取控制的別名,原版方法的存取控制規則沒有改變。
就像類能夠使用trait那樣,多個trait能夠組合為一個trait。
為了對使用的類施加強制要求,trait 支援抽象方法的使用。
getWorld();}abstract public function getWorld();}class MyHelloWorld {private $world;use Hello;public function getWorld() {return $this->world;}public function setWorld($val) {$this->world = $val;}}$c = new MyHelloWorld;$c->setWorld('world');$c->sayHelloWorld();
如果trait定義了一個屬性,那類將不能定義同樣名稱的屬性,否則會產生錯誤。
重載
PHP提供的重載是指動態地建立類屬性和方法,與其它絕大多數物件導向語言不同。通過魔術方法來實現。當使用不可訪問的屬性或方法時,重載方法會被調用。所有的重載方法都必須被聲明為public。
使用__get(),__set(),__isset(),__unset()進行屬性重載,樣本如下。
';$this->data[$name] = $value;}public function __get($name) {echo "Getting $name.
";if(array_key_exists($name, $this->data)) {return $this->data[$name];}return null;}public function __isset($name) {echo "Is $name set?
";return isset($this->data[$name]);}public function __unset($name) {echo "Unsetting $name.
";unset($this->data[$name]);}}$obj = new PropertyTest;$obj->a = 1;var_dump($obj->a);var_dump(isset($obj->a));unset($obj->a);var_dump(isset($obj->a));var_dump($obj->declared);var_dump($obj->hidden);
輸出結果如下:
Setting a to 1. Getting a. int 1Is a set? boolean trueUnsetting a. Is a set? boolean falseint 1Getting hidden. null
在對象中調用一個不可存取方法時,__call()會被調用。用靜態方式中調用一個不可存取方法時,__callStatic()會被調用。參數為調用方法的名稱和一個枚舉數組,注意區分大小寫。
使用__call()和__callStatic()對方法重載,樣本如下。
';}public static function __callStatic($name, $arguments) {echo "Calling static method $name " . implode(', ', $arguments) . '
';}}$obj = new MethodTest;$obj->runTest('in object context');MethodTest::runTest('in static context');
遍曆對象
對象可以用過單元列表來遍曆,例如用foreach語句。預設所有可見屬性都將被用於遍曆。
$value) {echo "$key => $value
";}
樣本程式2實現了Iterator介面的對象遍曆,樣本程式3通過實現IteratorAggregate來遍曆對象。
魔術方法
PHP 將所有以__(兩個底線)開頭的類方法保留為魔術方法。定義類方法時,除魔術方法外,建議不要以__為首碼。
前面遇到過的魔術方法有:__construct(),__destruct(),__call(),__callStatic(),__get(),__set(),__isset(),__unset()。後面將會介紹:__sleep(),__wakeup(),__toString(),__invoke(),__set_state(),__clone()和__debugInfo()。
__sleep和__wakeup不清楚具體做什麼用的,樣本程式中給出了個資料庫連接的例子。
__toString方法用於一個類被當成字串時應怎樣回應。此方法必須返回一個字串,且不能再方法中拋出異常。如果將一個未定義__toString()方法的對象轉換為字串,將產生錯誤。
當嘗試以調用函數的方式調用一個對象時,__invoke()方法會被調用。
當調用var_export()匯出類時,__set_state()會被調用。
當調用var_dump()時,__debugInfo會被調用。PHP 5.6新加入,沒合適的環境無法測試。
final
果父類中的方法被聲明為final,則子類無法覆蓋該方法。如果一個類被聲明為final,則不能被繼承。屬性不能被定義為final,只有類和方法才能被定義為final。
對象複製
多數情況,我們不需要完全複製一個對象,但有時確實需要。對象複製可以通過clone關鍵字來完成。這種複製是通過調用對象的__clone()方法實現的,但是對象中的__clone()方法不能被直接調用。
對象比較
比較子==為真的條件是:兩個對象的屬性和屬性值都相等,而且兩個對象是同一個類的執行個體。
繼承與統一個基類的兩個子類的對象不會相等==。
全等運算子===為真的條件是:兩個物件變數一定要指向某個類的同一個執行個體(即同一個對象)。
類型約束
類型約束是指函數的參數可以指定必須為對象、介面、數組或者callable類型。但是類型約束不能用於標量類型如int或string,traits也不允許。類型約束允許NULL值。
後期靜態繫結
後期靜態繫結,用於在繼承範圍內引用靜態調用的類。
轉寄調用,指的是通過以下幾種方式進行的靜態調用:self::,parent::,static::以及forward_static_call()。
後期靜態繫結的工作原理是,儲存了上一個非轉寄調用的類名。
當進行靜態方法調用時,該類名即為明確指定的那個;當進行非靜態方法調用時,即為該對象所屬的類。
使用self::或者__CLASS__對當前類的靜態引用,取決於定義當前方法所在的類。
用static::關鍵字表示運行時最初調用的類,後期靜態繫結就是這樣使用。如下面程式所示,也就是說調用test()時引用的類是B而不是A。
樣本2給出的是非靜態環境下使用static::。
後期靜態繫結的解析,會一直到取得一個完全解析了的靜態調用為止。另外,如果靜態調用使用parent::或self::將轉寄調用資訊。
那麼問題來了,結果為什麼是這樣的呢?
對象和引用
預設情況下,對象時通過引用傳遞的。但這種說法不完全正確,其實兩個物件變數不是引用的關係,只是他們都儲存著同一個標識符的拷貝,這個標識符指向同一個對象的真正內容。
對象序列化
所有PHP裡面的值,都可以使用函數serialize()來返回一個包含位元組流的字串來表示。unserialize()函數能夠重新把字串變為原來的值。
序列化一個對象,將會儲存對象的所有變數,但是不會儲存對象的方法,只會儲存類的名字。為了能夠unserialize()一個對象,這個對象的類必須已經定義過。在應用程式中序列化對象以便在之後使用,強烈推薦在整個應用程式都包含對象的類的定義。
(全文完)
以上就介紹了類與對象 - PHP手冊筆記,包括了方面的內容,希望對PHP教程有興趣的朋友有所協助。