每个类的定义都以关键字 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 之后跟着的是一个包含有类名的字符串
      string,则该类的一个实例被创建。如果该类属于一个命名空间,则必须使用其完整名称。
  
注意:
如果没有参数要传递给类的构造函数,类名后的括号则可以省略掉。
示例 #3 创建实例
<?php
$instance = new SimpleClass();
// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>
    PHP 8.0.0 起,支持任意表达式中使用 new。如果表达式生成一个 
    string,这将允许更复杂的实例化。表达式必须使用括号括起来。
   
示例 #4 使用任意表达式创建实例
     在下列示例中,我们展示了多个生成类名的任意有效表达式的示例。展示了函数调用,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) {
}
   在类定义内部,可以用 new self 和 new parent 创建新对象。
  
当把一个对象已经创建的实例赋给一个新变量时,新变量会访问同一个实例,就和用该对象赋值一样。此行为和给函数传递入实例时一样。可以用 克隆 给一个已创建的对象建立一个新实例。
示例 #5 对象赋值
<?php
$instance = new SimpleClass();
$assigned   =  $instance;
$reference  =& $instance;
$instance->var = '$assigned will have this value';
$instance = null; // $instance 和 $reference 变为 null
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
以上例程会输出:
NULL
NULL
object(SimpleClass)#1 (1) {
   ["var"]=>
     string(30) "$assigned will have this value"
}
有几种方法可以创建一个对象的实例。
示例 #6 创建新对象
<?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)
可以通过一个表达式来访问新创建对象的成员:
示例 #7 访问新创建对象的成员
<?php
echo (new DateTime())->format('Y');
?>
以上例程的输出类似于:
2016
注意: 在 PHP 7.1 之前,如果类没有定义构造函数,则不对参数进行执行。
类的属性和方法存在于不同的“命名空间”中,这意味着同一个类的属性和方法可以使用同样的名字。 在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。
示例 #8 访问类属性 vs. 调用类方法
<?php
class Foo
{
    public $bar = 'property';
    public function bar() {
        return 'method';
    }
}
$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
以上例程会输出:
property method
这意味着,如果你的类属性被分配给一个 匿名函数 你将无法直接调用它。因为访问类属性的优先级要更高,在此场景下需要用括号包裹起来调用。
示例 #9 类属性被赋值为匿名函数时的调用示例
<?php
class Foo
{
    public $bar;
    public function __construct() {
        $this->bar = function() {
            return 42;
        };
    }
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL;
以上例程会输出:
42
   一个类可以在声明中用 extends
   关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。
  
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法或者常量时使用了 final,则不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
注意: 从 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
     当覆盖(override)方法时,签名必须兼容父类方法。
     否则会导致 Fatal 错误(PHP 8.0.0 之前是 E_WARNING 级错误)。
     兼容签名是指:遵守协变与逆变规则;
     强制参数可以改为可选参数;新参数为可选参数。
     这就是著名的里氏替换原则(Liskov Substitution Principle),简称 LSP。
     不过构造器和
     私有(private)方法不需要遵循签名兼容规则,
     哪怕签名不匹配也不会导致 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
下面演示子类与父类方法不兼容的例子:通过移除参数、修改可选参数为必填参数。
示例 #12 子类方法移除参数后,导致 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 子类方法把可选参数改成强制参数,导致 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
重命名子类方法的参数名称也是签名兼容的。 然而我们不建议这样做,因为使用命名参数时, 这种做法会导致运行时的 Error。
示例 #14 在子类中重命一个命名参数,导致 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!
以上例程的输出类似于:
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
  thrown in /in/XaaeN on line 14
    关键词 class 也可用于类名的解析。使用 ClassName::class
    可以获取包含类 ClassName 的完全限定名称。这对使用了
    命名空间 的类尤其有用。
   
示例 #15 类名的解析
<?php
namespace NS {
    class ClassName {
    }
    echo ClassName::class;
}
?>
以上例程会输出:
NS\ClassName
注意:
使用
::class解析类名操作会在底层编译时进行。这意味着在执行该操作时,类还没有被加载。 因此,即使要调用的类不存在,类名也会被展示。在此种场景下,并不会发生错误。示例 #16 解析不存在的类名
<?php
print Does\Not\Exist::class;
?>以上例程会输出:
Does\Not\Exist
    自 PHP 8.0.0 起,::class 关键字也可以对象上使用。
    与上述情况不同,此时解析将会在运行时进行。此操作的运行结果和
    get_class() 函数一致。
   
示例 #17 类名解析
<?php
namespace NS {
    class ClassName {
    }
}
$c = new ClassName();
print $c::class;
?>
以上例程会输出:
NS\ClassName
   自 PHP 8.0.0 起,类属性和方法可以通过 "nullsafe" 操作符访问:
   ?->。
   除了一处不同,nullsafe 操作符和以上原来的属性、方法访问是一致的:
   对象引用解析(dereference)为 null 时不抛出异常,而是返回 null。
   并且如果是链式调用中的一部分,剩余链条会直接跳过。
  
此操作的结果,类似于在每次访问前使用 is_null() 函数判断方法和属性是否存在,但更加简洁。
示例 #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 操作符。如果业务中需要明确指示错误,抛出异常会是更好的处理方式。