«

PHP类与对象

MitSeek 发布于 阅读:5


简介

PHP 具有完整的对象模型。特性包括: 访问控制,抽象类和 final 类与方法,附加的魔术方法,接口,对象复制。
PHP 对待对象的方式等同于引用或句柄,即每个变量都持有对象的引用,而不是整个对象的复制。

基本概念

class

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

类名可以是任何不是 PHP 保留字 的有效标签。自 PHP 8.4.0 起,弃用使用单个下划线 作为类名。有效类名以字母或下划线开头,后面跟着若干字母、数字或下划线。以正则表达式表示为 ^[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 是一个到当前对象的引用。

new

要创建一个类的实例,必须使用 new 关键字。

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

如果一个变量包含一个类名的 string 和 new 时,将创建该类的一个新实例。 如果该类属于一个命名空间,则必须使用其完整名称。

注意:

如果没有参数要传递给类的构造函数,类名后的括号则可以省略掉。
示例 #3 创建实例

<?php
$instance = new SimpleClass();

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

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

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

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

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

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

<?php

class Test
{
    public static 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);

?>

属性和方法

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

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

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

<?php
class Foo
{
    public $bar;

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

$obj = new Foo();

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

extends

一个类可以在声明中用 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();
?>

::class

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

示例 #15 类名的解析

<?php
namespace NS {
    class ClassName {
    }

    echo ClassName::class;
}
?>

Nullsafe 方法和属性

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

php