基本概念

class

每個類的定義都以關(guān)鍵字 class 開頭,后面跟著類名,后面跟著一對花括號,里面包含有類的屬性與方法的定義。

類名可以是任何非 PHP 保留字 的合法標簽。一個合法類名以字母或下劃線開頭,后面跟著若干字母,數(shù)字或下劃線。以正則表達式表示為: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$

一個類可以包含有屬于自己的 常量,變量(稱為“屬性”)以及函數(shù)(稱為“方法”)。

示例 #1 簡單的類定義

<?php
class SimpleClass
{
    
// 聲明屬性
    
public $var 'a default value';

    
// 聲明方法
    
public function displayVar() {
        echo 
$this->var;
    }
}
?>

當一個方法在類定義內(nèi)部被調(diào)用時,有一個可用的偽變量 $this$this 是一個到當前對象的引用。

警告

以靜態(tài)方式去調(diào)用一個非靜態(tài)方法,將會拋出一個 Error。 在 PHP 8.0.0 之前版本中,將會產(chǎn)生一個廢棄通知,同時 $this 將會被聲明為未定義。

示例 #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

new

要創(chuàng)建一個類的實例,必須使用 new 關(guān)鍵字。當創(chuàng)建新對象時該對象總是被賦值,除非該對象定義了 構(gòu)造函數(shù) 并且在出錯時拋出了一個 異常。類應在被實例化之前定義(某些情況下則必須這樣)。

如果在 new 之后跟著的是一個包含有類名的字符串 string,則該類的一個實例被創(chuàng)建。如果該類屬于一個命名空間,則必須使用其完整名稱。

注意:

如果沒有參數(shù)要傳遞給類的構(gòu)造函數(shù),類名后的括號則可以省略掉。

示例 #3 創(chuàng)建實例

<?php
$instance 
= new SimpleClass();

// 也可以這樣做:
$className 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>

PHP 8.0.0 起,支持任意表達式中使用 new。如果表達式生成一個 string,這將允許更復雜的實例化。表達式必須使用括號括起來。

示例 #4 使用任意表達式創(chuàng)建實例

在下列示例中,我們展示了多個生成類名的任意有效表達式的示例。展示了函數(shù)調(diào)用,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) {
}

在類定義內(nèi)部,可以用 new selfnew parent 創(chuàng)建新對象。

當把一個對象已經(jīng)創(chuàng)建的實例賦給一個新變量時,新變量會訪問同一個實例,就和用該對象賦值一樣。此行為和給函數(shù)傳遞入實例時一樣??梢杂? 克隆 給一個已創(chuàng)建的對象建立一個新實例。

示例 #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);
?>

以上例程會輸出:

NULL
NULL
object(SimpleClass)#1 (1) {
   ["var"]=>
     string(30) "$assigned will have this value"
}

有幾種方法可以創(chuàng)建一個對象的實例。

示例 #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);
?>

以上例程會輸出:

bool(true)
bool(true)
bool(true)

可以通過一個表達式來訪問新創(chuàng)建對象的成員:

示例 #7 訪問新創(chuàng)建對象的成員

<?php
echo (new DateTime())->format('Y');
?>

以上例程的輸出類似于:

2016

注意: 在 PHP 7.1 之前,如果類沒有定義構(gòu)造函數(shù),則不對參數(shù)進行執(zhí)行。

屬性和方法

類的屬性和方法存在于不同的“命名空間”中,這意味著同一個類的屬性和方法可以使用同樣的名字。 在類中訪問屬性和調(diào)用方法使用同樣的操作符,具體是訪問一個屬性還是調(diào)用一個方法,取決于你的上下文,即用法是變量訪問還是函數(shù)調(diào)用。

示例 #8 訪問類屬性 vs. 調(diào)用類方法

<?php
class Foo
{
    public 
$bar 'property';

    public function 
bar() {
        return 
'method';
    }
}

$obj = new Foo();
echo 
$obj->barPHP_EOL$obj->bar(), PHP_EOL;

以上例程會輸出:

property
method

這意味著,如果你的類屬性被分配給一個 匿名函數(shù) 你將無法直接調(diào)用它。因為訪問類屬性的優(yōu)先級要更高,在此場景下需要用括號包裹起來調(diào)用。

示例 #9 類屬性被賦值為匿名函數(shù)時的調(diào)用示例

<?php
class Foo
{
    public 
$bar;

    public function 
__construct() {
        
$this->bar = function() {
            return 
42;
        };
    }
}

$obj = new Foo();

echo (
$obj->bar)(), PHP_EOL;

以上例程會輸出:

42

extends

一個類可以在聲明中用 extends 關(guān)鍵字繼承另一個類的方法和屬性。PHP 不支持多重繼承,一個類只能繼承一個基類。

被繼承的方法和屬性可以通過用同樣的名字重新聲明被覆蓋。但是如果父類定義方法或者常量時使用了 final,則不可被覆蓋??梢酝ㄟ^ parent:: 來訪問被覆蓋的方法或?qū)傩浴?

注意: 從 PHP 8.1.0 起,常量可以聲明為 final。

示例 #10 簡單的類繼承

<?php
class ExtendClass extends SimpleClass
{
    
// 同樣名稱的方法,將會覆蓋父類的方法
    
function displayVar()
    {
        echo 
"Extending class\n";
        
parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

以上例程會輸出:

Extending class
a default value

簽名兼容性規(guī)則

當覆蓋(override)方法時,簽名必須兼容父類方法。 否則會導致 Fatal 錯誤(PHP 8.0.0 之前是 E_WARNING 級錯誤)。 兼容簽名是指:遵守協(xié)變與逆變規(guī)則; 強制參數(shù)可以改為可選參數(shù);新參數(shù)為可選參數(shù)。 這就是著名的里氏替換原則(Liskov Substitution Principle),簡稱 LSP。 不過構(gòu)造器和 私有(private)方法不需要遵循簽名兼容規(guī)則, 哪怕簽名不匹配也不會導致 Fatal 錯誤。

示例 #11 兼容的子類方法

<?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);

以上例程會輸出:

Valid
Valid

下面演示子類與父類方法不兼容的例子:通過移除參數(shù)、修改可選參數(shù)為必填參數(shù)。

示例 #12 子類方法移除參數(shù)后,導致 Fatal 錯誤

<?php

class Base
{
    public function 
foo(int $a 5) {
        echo 
"Valid\n";
    }
}

class 
Extend extends Base
{
    function 
foo()
    {
        
parent::foo(1);
    }
}

以上例程在 PHP 8 中的輸出類似于:

Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13

示例 #13 子類方法把可選參數(shù)改成強制參數(shù),導致 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 中的輸出類似于:

Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
警告

重命名子類方法的參數(shù)名稱也是簽名兼容的。 然而我們不建議這樣做,因為使用命名參數(shù)時, 這種做法會導致運行時的 Error。

示例 #14 在子類中重命一個命名參數(shù),導致 Error

<?php

class {
    public function 
test($foo$bar) {}
}

class 
extends {
    public function 
test($a$b) {}
}

$obj = new B;

// 按 A::test() 的簽名約定傳入?yún)?shù)
$obj->test(foo"foo"bar"bar"); // ERROR!

以上例程的輸出類似于:

Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
  thrown in /in/XaaeN on line 14

::class

關(guān)鍵詞 class 也可用于類名的解析。使用 ClassName::class 可以獲取包含類 ClassName 的完全限定名稱。這對使用了 命名空間 的類尤其有用。

示例 #15 類名的解析

<?php
namespace NS {
    class 
ClassName {
    }

    echo 
ClassName::class;
}
?>

以上例程會輸出:

NS\ClassName

注意:

使用 ::class 解析類名操作會在底層編譯時進行。這意味著在執(zhí)行該操作時,類還沒有被加載。 因此,即使要調(diào)用的類不存在,類名也會被展示。在此種場景下,并不會發(fā)生錯誤。

示例 #16 解析不存在的類名

<?php
print Does\Not\Exist::class;
?>

以上例程會輸出:

Does\Not\Exist

自 PHP 8.0.0 起,::class 關(guān)鍵字也可以對象上使用。 與上述情況不同,此時解析將會在運行時進行。此操作的運行結(jié)果和 get_class() 函數(shù)一致。

示例 #17 類名解析

<?php
namespace NS {
    class 
ClassName {
    }
}
$c = new ClassName();
print 
$c::class;
?>

以上例程會輸出:

NS\ClassName

Nullsafe 方法和屬性

自 PHP 8.0.0 起,類屬性和方法可以通過 "nullsafe" 操作符訪問: ?->。 除了一處不同,nullsafe 操作符和以上原來的屬性、方法訪問是一致的: 對象引用解析(dereference)為 null 時不拋出異常,而是返回 null。 并且如果是鏈式調(diào)用中的一部分,剩余鏈條會直接跳過。

此操作的結(jié)果,類似于在每次訪問前使用 is_null() 函數(shù)判斷方法和屬性是否存在,但更加簡潔。

示例 #18 Nullsafe 操作符

<?php

// 自 PHP 8.0.0 起可用
$result $repository?->getUser(5)?->name;

// 上邊那行代碼等價于以下代碼
if (is_null($repository)) {
    
$result null;
} else {
    
$user $repository->getUser(5);
    if (
is_null($user)) {
        
$result null;
    } else {
        
$result $user->name;
    }
}
?>

注意:

僅當 null 被認為是屬性或方法返回的有效和預期的可能值時,才推薦使用 nullsafe 操作符。如果業(yè)務(wù)中需要明確指示錯誤,拋出異常會是更好的處理方式。