基本概念

class

每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。

类名可以是任何非 PHP 保留字 的合法标签。一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线。以正则表达式表示为: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$

一个类可以包含有属于自己的 常量变量(称为“属性”)以及函数(称为“方法”)。

示例 #1 简单的类定义

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

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

当一个方法在类定义内部被调用时,有一个可用的伪变量 $this$this 是一个到当前对象的引用。

警告

以静态方式去调用一个静态方法,将会抛出一个 Error。 在 PHP 8.0.0 之前版本中,将会产生一个废弃通知,同时 $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

要创建一个类的实例,必须使用 new 关键字。当创建新对象时该对象总是被赋值,除非该对象定义了 构造函数 并且在出错时抛出了一个 异常。类应在被实例化之前定义(某些情况下则必须这样)。

如果在 new 之后跟着的是一个包含有类名的字符串 string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称。

注意:

如果没有参数要传递给类的构造函数,类名后的括号则可以省略掉。

示例 #3 创建实例

<?php
$instance 
= new SimpleClass();

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

在类定义内部,可以用 new selfnew parent 创建新对象。

当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用 克隆 给一个已创建的对象建立一个新实例。

示例 #4 对象赋值

<?php

$instance 
= new SimpleClass();

$assigned   =  $instance;
$reference  =& $instance;

$instance->var '$assigned will have this value';

$instance null// $instance and $reference become null

var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>

以上例程会输出:

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

有几种方法可以创建一个对象的实例。

示例 #5 创建新对象

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

可以通过一个表达式来访问新创建对象的成员:

示例 #6 访问新创建对象的成员

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

以上例程的输出类似于:

2016

注意: 在 PHP 7.1 之前,如果类没有定义构造函数,则不对参数进行执行。

属性和方法

类的属性和方法存在于不同的“命名空间”中,这意味着同一个类的属性和方法可以使用同样的名字。在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。

示例 #7 访问类属性 vs. 调用类方法

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

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

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

以上例程会输出:

property
method

这意味着,如果你的类属性被分配给一个 匿名函数 你将无法直接调用它。因为访问类属性的优先级要更高,在此场景下需要用括号包裹起来调用。

示例 #8 类属性被赋值为匿名函数时的调用示例

<?php
class Foo
{
    public 
$bar;

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

$obj = new Foo();

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

以上例程会输出:

42

extends

一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

示例 #9 简单的类继承

<?php
class ExtendClass extends SimpleClass
{
    
// 同样名称的方法,将会覆盖父类的方法
    
function displayVar()
    {
        echo 
"Extending class\n";
        
parent::displayVar();
    }
}

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

以上例程会输出:

Extending class
a default value

Signature compatibility rules

When overriding a method, its signature must be compatible with the parent method. Otherwise, a fatal error is emitted, or, prior to PHP 8.0.0, an E_WARNING level error is generated. A signature is compatible if it respects the variance rules, makes a mandatory parameter optional, and if any new parameters are optional. This is known as the Liskov Substitution Principle, or LSP for short. The constructor, and private methods are exempt from these signature compatibility rules, and thus won't emit a fatal error in case of a signature mismatch.

示例 #10 Compatible child methods

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

The following examples demonstrate that a child method which removes a parameter, or makes an optional parameter mandatory, is not compatible with the parent method.

示例 #11 Fatal error when a child method removes a parameter

<?php

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

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

Output of the above example in PHP 8 is similar to:

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

示例 #12 Fatal error when a child method makes an optional parameter mandatory

<?php

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

class 
Extend extends Base
{
    function 
foo(int $a)
    {
        
parent::foo($a);
    }
}

Output of the above example in PHP 8 is similar to:

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

Renaming a method's parameter in a child class is not a signature incompatibility. However, this is discouraged as it will result in a runtime Error if named arguments are used.

示例 #13 Error when using named arguments and parameters were renamed in a child class

<?php

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

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

$obj = new B;

// Pass parameters according to A::test() contract
$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

关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。

示例 #14 类名的解析

<?php
namespace NS {
    class 
ClassName {
    }

    echo 
ClassName::class;
}
?>

以上例程会输出:

NS\ClassName

注意:

使用 ::class 解析类名操作会在底层编译时进行。这意味着在执行该操作时,类还没有被加载。因此,即使要调用的类不存在,类名也会被展示。在此种场景下,并不会发生错误。

示例 #15 解析不存在的类名

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

以上例程会输出:

Does\Not\Exist

自 PHP 8.0.0 起,::class 关键字也可以对象上使用。与上述情况不同,此时解析将会在运行时进行。此操作的运行结果和 get_class() 函数一致。

示例 #16 类名解析

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

以上例程会输出:

NS\ClassName

Nullsafe 方法和属性

自 PHP 8.0.0 起,类属性和方法新增加了一个 "nullsafe" 操作符: ?->。The nullsafe operator works the same as property or method access as above, except that if the object being dereferenced is null then null will be returned rather than an exception thrown. If the dereference is part of a chain, the rest of the chain is skipped.

此操作的结果,类似于在每次访问前使用 is_null() 函数判断方法和属性是否存在,但更加简洁。

示例 #17 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 操作符。如果业务中需要明确指示错误,抛出异常会是更好的处理方式。