Menu
CoddyTech

后期静态绑定

本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 36 课,共 91 课。

在父类中使用 self:: 时,它始终指向编写它的类,而不是实际使用的类。这可能会导致继承中的行为出乎意料。延迟静态绑定通过 static:: 关键字解决了这个问题。

请看这个示例,其中 self:: 的行为与预期不符:

<?php
class ParentClass {
    public static function create() {
        return new self();
    }
    
    public static function getClassName() {
        return self::class;
    }
}

class ChildClass extends ParentClass {}

$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();

输出:

ParentClass
ParentClass

尽管我们调用了 ChildClass::create(),但得到的是一个 ParentClass 对象,因为 self:: 会在编译时解析。现在我们改用 static:::

<?php
class ParentClass {
    public static function create() {
        return new static();
    }
    
    public static function getClassName() {
        return static::class;
    }
}

class ChildClass extends ParentClass {}

$obj = ChildClass::create();
echo get_class($obj) . "\n";
echo ChildClass::getClassName();

输出:

ChildClass
ChildClass

使用 static:: 时,PHP 会等到运行时才确定要使用哪个类。这称为“延迟”绑定,因为这个决定会推迟到运行时。这对于工厂方法以及任何应遵循继承关系的静态方法都至关重要。

关键点:如果你希望子类正确继承并自定义静态行为,请使用 static::,而不是 self::。

challenge icon

挑战

简单

我们来构建一个模型注册系统,展示后期静态绑定的强大之处。你将创建一个基础 Model 类,其中包含一个子类可以继承的工厂方法。借助 static::,每个子类都能正确创建自身的实例,而不是父类的实例。

你将把代码分成 4 个文件:

  • Model.php:创建一个基础 Model 类,其中包含受保护的静态属性 $tableName,并将它设为 "models"。添加一个静态方法 create(),使用 new static() 返回调用该方法的类的新实例。再添加一个静态方法 getTable(),让它返回 static::$tableName。最后,添加一个 describe() 方法,使用 static::class 和 static::$tableName 返回 "[ClassName] from table [tableName]"。
  • User.php:创建一个继承 Model 的 User 类。引入父类文件。将静态属性 $tableName 覆盖为 "users"。该类继承 Model 的所有方法,无需做任何修改。
  • Product.php:创建一个继承 Model 的 Product 类。引入父类文件。将静态属性 $tableName 覆盖为 "products"。同样,该类会继承父类的所有方法。
  • main.php:引入 User 和 Product 文件。使用继承而来的 create() 工厂方法,分别创建 User 和 Product 的实例。对每个实例调用 describe(),并将结果各自输出到一行。然后使用 User::getTable() 和 Product::getTable() 输出每个类的表名,每个表名各占一行。

这里的关键在于,create() 和 getTable() 方法只在父类 Model 中定义,但由于 static:: 会将绑定延迟到运行时,它们仍能正确用于子类。调用 User::create() 时,PHP 创建的是 User 实例,而不是 Model 实例,因为 static:: 指向实际被调用的类。

输出应包含 4 行:User 实例的描述、Product 实例的描述、User 的表名,以及 Product 的表名。

亲手试试

<?php

require_once 'User.php';
require_once 'Product.php';

// TODO: 使用继承来的 create() 工厂方法创建一个 User 实例

// TODO: 使用继承来的 create() 工厂方法创建一个 Product 实例

// TODO: 调用 User 实例的 describe() 方法并打印结果

// TODO: 调用 Product 实例的 describe() 方法并打印结果

// TODO: 打印 User::getTable()

// TODO: 打印 Product::getTable()

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器