每個(gè)類(lèi)的定義都以關(guān)鍵字 class
開(kāi)頭,后面跟著(zhù)類(lèi)名,后面跟著(zhù)一對花括號,里面包含有類(lèi)的屬性與方法的定義。
類(lèi)名可以是任何非 PHP
保留字
的合法標簽。一個(gè)合法類(lèi)名以字母或下劃線(xiàn)開(kāi)頭,后面跟著(zhù)若干字母,數字或下劃線(xiàn)。以正則表達式表示為:
^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$
。
一個(gè)類(lèi)可以包含有屬于自己的 常量,變量(稱(chēng)為“屬性”)以及函數(稱(chēng)為“方法”)。
示例 #1 簡(jiǎn)單的類(lèi)定義
<?php
class SimpleClass
{
// 聲明屬性
public $var = 'a default value';
// 聲明方法
public function displayVar() {
echo $this->var;
}
}
?>
當一個(gè)方法在類(lèi)定義內部被調用時(shí),有一個(gè)可用的偽變量 $this。$this 是一個(gè)到當前對象的引用。
以靜態(tài)方式去調用一個(gè)非靜態(tài)方法,將會(huì )拋出一個(gè) Error。 在 PHP 8.0.0 之前版本中,將會(huì )產(chǎn)生一個(gè)廢棄通知,同時(shí) $this 將會(huì )被聲明為未定義。
示例 #2 使用 $this 偽變量的示例
<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this is defined (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this is not defined.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
以上例程在 PHP 7 中的輸出:
$this is defined (A) Deprecated: Non-static method A::foo() should not be called statically in %s on line 27 $this is not defined. Deprecated: Non-static method A::foo() should not be called statically in %s on line 20 $this is not defined. Deprecated: Non-static method B::bar() should not be called statically in %s on line 32 Deprecated: Non-static method A::foo() should not be called statically in %s on line 20 $this is not defined.
以上例程在 PHP 8 中的輸出:
$this is defined (A) Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27 Stack trace: #0 {main} thrown in %s on line 27
要創(chuàng )建一個(gè)類(lèi)的實(shí)例,必須使用 new
關(guān)鍵字。當創(chuàng )建新對象時(shí)該對象總是被賦值,除非該對象定義了 構造函數 并且在出錯時(shí)拋出了一個(gè) 異常。類(lèi)應在被實(shí)例化之前定義(某些情況下則必須這樣)。
如果在 new
之后跟著(zhù)的是一個(gè)包含有類(lèi)名的字符串
string,則該類(lèi)的一個(gè)實(shí)例被創(chuàng )建。如果該類(lèi)屬于一個(gè)命名空間,則必須使用其完整名稱(chēng)。
注意:
如果沒(méi)有參數要傳遞給類(lèi)的構造函數,類(lèi)名后的括號則可以省略掉。
示例 #3 創(chuàng )建實(shí)例
<?php
$instance = new SimpleClass();
// 也可以這樣做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
PHP 8.0.0 起,支持任意表達式中使用 new
。如果表達式生成一個(gè)
string,這將允許更復雜的實(shí)例化。表達式必須使用括號括起來(lái)。
示例 #4 使用任意表達式創(chuàng )建實(shí)例
在下列示例中,我們展示了多個(gè)生成類(lèi)名的任意有效表達式的示例。展示了函數調用,string 連接和 ::class
常量。
<?php
class ClassA extends \stdClass {}
class ClassB extends \stdClass {}
class ClassC extends ClassB {}
class ClassD extends ClassA {}
function getSomeClass(): string
{
return 'ClassA';
}
var_dump(new (getSomeClass()));
var_dump(new ('Class' . 'B'));
var_dump(new ('Class' . 'C'));
var_dump(new (ClassD::class));
?>
以上例程在 PHP 8 中的輸出:
object(ClassA)#1 (0) { } object(ClassB)#1 (0) { } object(ClassC)#1 (0) { } object(ClassD)#1 (0) { }
在類(lèi)定義內部,可以用 new self
和 new parent
創(chuàng )建新對象。
當把一個(gè)對象已經(jīng)創(chuàng )建的實(shí)例賦給一個(gè)新變量時(shí),新變量會(huì )訪(fǎng)問(wèn)同一個(gè)實(shí)例,就和用該對象賦值一樣。此行為和給函數傳遞入實(shí)例時(shí)一樣??梢杂? 克隆 給一個(gè)已創(chuàng )建的對象建立一個(gè)新實(shí)例。
示例 #5 對象賦值
<?php
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->var = '$assigned will have this value';
$instance = null; // $instance 和 $reference 變?yōu)?nbsp;null
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
以上例程會(huì )輸出:
NULL NULL object(SimpleClass)#1 (1) { ["var"]=> string(30) "$assigned will have this value" }
有幾種方法可以創(chuàng )建一個(gè)對象的實(shí)例。
示例 #6 創(chuàng )建新對象
<?php
class Test
{
static public function getNew()
{
return new static;
}
}
class Child extends Test
{}
$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);
?>
以上例程會(huì )輸出:
bool(true) bool(true) bool(true)
可以通過(guò)一個(gè)表達式來(lái)訪(fǎng)問(wèn)新創(chuàng )建對象的成員:
示例 #7 訪(fǎng)問(wèn)新創(chuàng )建對象的成員
<?php
echo (new DateTime())->format('Y');
?>
以上例程的輸出類(lèi)似于:
2016
注意: 在 PHP 7.1 之前,如果類(lèi)沒(méi)有定義構造函數,則不對參數進(jìn)行執行。
類(lèi)的屬性和方法存在于不同的“命名空間”中,這意味著(zhù)同一個(gè)類(lèi)的屬性和方法可以使用同樣的名字。 在類(lèi)中訪(fǎng)問(wèn)屬性和調用方法使用同樣的操作符,具體是訪(fǎng)問(wèn)一個(gè)屬性還是調用一個(gè)方法,取決于你的上下文,即用法是變量訪(fǎng)問(wèn)還是函數調用。
示例 #8 訪(fǎng)問(wèn)類(lèi)屬性 vs. 調用類(lèi)方法
<?php
class Foo
{
public $bar = 'property';
public function bar() {
return 'method';
}
}
$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
以上例程會(huì )輸出:
property method
這意味著(zhù),如果你的類(lèi)屬性被分配給一個(gè) 匿名函數 你將無(wú)法直接調用它。因為訪(fǎng)問(wèn)類(lèi)屬性的優(yōu)先級要更高,在此場(chǎng)景下需要用括號包裹起來(lái)調用。
示例 #9 類(lèi)屬性被賦值為匿名函數時(shí)的調用示例
<?php
class Foo
{
public $bar;
public function __construct() {
$this->bar = function() {
return 42;
};
}
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL;
以上例程會(huì )輸出:
42
一個(gè)類(lèi)可以在聲明中用 extends
關(guān)鍵字繼承另一個(gè)類(lèi)的方法和屬性。PHP 不支持多重繼承,一個(gè)類(lèi)只能繼承一個(gè)基類(lèi)。
被繼承的方法和屬性可以通過(guò)用同樣的名字重新聲明被覆蓋。但是如果父類(lèi)定義方法或者常量時(shí)使用了 final,則不可被覆蓋??梢酝ㄟ^(guò) parent:: 來(lái)訪(fǎng)問(wèn)被覆蓋的方法或屬性。
注意: 從 PHP 8.1.0 起,常量可以聲明為 final。
示例 #10 簡(jiǎn)單的類(lèi)繼承
<?php
class ExtendClass extends SimpleClass
{
// 同樣名稱(chēng)的方法,將會(huì )覆蓋父類(lèi)的方法
function displayVar()
{
echo "Extending class\n";
parent::displayVar();
}
}
$extended = new ExtendClass();
$extended->displayVar();
?>
以上例程會(huì )輸出:
Extending class a default value
當覆蓋(override)方法時(shí),簽名必須兼容父類(lèi)方法。
否則會(huì )導致 Fatal 錯誤(PHP 8.0.0 之前是 E_WARNING
級錯誤)。
兼容簽名是指:遵守協(xié)變與逆變規則;
強制參數可以改為可選參數;新參數為可選參數。
這就是著(zhù)名的里氏替換原則(Liskov Substitution Principle),簡(jiǎn)稱(chēng) LSP。
不過(guò)構造器和
私有(private
)方法不需要遵循簽名兼容規則,
哪怕簽名不匹配也不會(huì )導致 Fatal 錯誤。
示例 #11 兼容的子類(lèi)方法
<?php
class Base
{
public function foo(int $a) {
echo "Valid\n";
}
}
class Extend1 extends Base
{
function foo(int $a = 5)
{
parent::foo($a);
}
}
class Extend2 extends Base
{
function foo(int $a, $b = 5)
{
parent::foo($a);
}
}
$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);
以上例程會(huì )輸出:
Valid Valid
下面演示子類(lèi)與父類(lèi)方法不兼容的例子:通過(guò)移除參數、修改可選參數為必填參數。
示例 #12 子類(lèi)方法移除參數后,導致 Fatal 錯誤
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo()
{
parent::foo(1);
}
}
以上例程在 PHP 8 中的輸出類(lèi)似于:
Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13
示例 #13 子類(lèi)方法把可選參數改成強制參數,導致 Fatal 錯誤
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo(int $a)
{
parent::foo($a);
}
}
以上例程在 PHP 8 中的輸出類(lèi)似于:
Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
重命名子類(lèi)方法的參數名稱(chēng)也是簽名兼容的。 然而我們不建議這樣做,因為使用命名參數時(shí), 這種做法會(huì )導致運行時(shí)的 Error。
示例 #14 在子類(lèi)中重命一個(gè)命名參數,導致 Error
<?php
class A {
public function test($foo, $bar) {}
}
class B extends A {
public function test($a, $b) {}
}
$obj = new B;
// 按 A::test() 的簽名約定傳入參數
$obj->test(foo: "foo", bar: "bar"); // ERROR!
以上例程的輸出類(lèi)似于:
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14 Stack trace: #0 {main} thrown in /in/XaaeN on line 14
關(guān)鍵詞 class
也可用于類(lèi)名的解析。使用 ClassName::class
可以獲取包含類(lèi) ClassName
的完全限定名稱(chēng)。這對使用了
命名空間 的類(lèi)尤其有用。
示例 #15 類(lèi)名的解析
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>
以上例程會(huì )輸出:
NS\ClassName
注意:
使用
::class
解析類(lèi)名操作會(huì )在底層編譯時(shí)進(jìn)行。這意味著(zhù)在執行該操作時(shí),類(lèi)還沒(méi)有被加載。 因此,即使要調用的類(lèi)不存在,類(lèi)名也會(huì )被展示。在此種場(chǎng)景下,并不會(huì )發(fā)生錯誤。示例 #16 解析不存在的類(lèi)名
<?php
print Does\Not\Exist::class;
?>以上例程會(huì )輸出:
Does\Not\Exist
自 PHP 8.0.0 起,::class
關(guān)鍵字也可以對象上使用。
與上述情況不同,此時(shí)解析將會(huì )在運行時(shí)進(jìn)行。此操作的運行結果和
get_class() 函數一致。
示例 #17 類(lèi)名解析
<?php
namespace NS {
class ClassName {
}
}
$c = new ClassName();
print $c::class;
?>
以上例程會(huì )輸出:
NS\ClassName
自 PHP 8.0.0 起,類(lèi)屬性和方法可以通過(guò) "nullsafe" 操作符訪(fǎng)問(wèn):
?->
。
除了一處不同,nullsafe 操作符和以上原來(lái)的屬性、方法訪(fǎng)問(wèn)是一致的:
對象引用解析(dereference)為 null
時(shí)不拋出異常,而是返回 null
。
并且如果是鏈式調用中的一部分,剩余鏈條會(huì )直接跳過(guò)。
此操作的結果,類(lèi)似于在每次訪(fǎng)問(wèn)前使用 is_null() 函數判斷方法和屬性是否存在,但更加簡(jiǎn)潔。
示例 #18 Nullsafe 操作符
<?php
// 自 PHP 8.0.0 起可用
$result = $repository?->getUser(5)?->name;
// 上邊那行代碼等價(jià)于以下代碼
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);
if (is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}
?>
注意:
僅當 null 被認為是屬性或方法返回的有效和預期的可能值時(shí),才推薦使用 nullsafe 操作符。如果業(yè)務(wù)中需要明確指示錯誤,拋出異常會(huì )是更好的處理方式。