后期静态绑定
概述
后期静态绑定(Late Static Binding, LSB)是 PHP 5.3 引入的特性,通过 static:: 关键字在运行时解析被调用的类,而非在编译时绑定到定义时的类。self:: 始终指向定义方法的类,而 static:: 指向实际调用方法的类。这在工厂模式、继承链中的方法调用等场景中非常重要。
基础概念
self:: vs static::
| 关键字 | 绑定时机 | 指向 |
|---|---|---|
self:: | 编译时 | 定义方法的类 |
static:: | 运行时 | 实际调用方法的类 |
parent:: | 编译时 | 父类 |
get_called_class()
get_called_class() 返回实际调用方法的类名,与 static:: 的行为一致。
语法与代码
self:: 的问题
php
<?php
declare(strict_types=1);
class Base
{
public static function create(): static
{
// self:: 始终指向 Base,而非子类
return new self();
}
public static function who(): string
{
return self::class;
}
}
class Child extends Base
{
}
echo Base::who(); // "Base"
echo Child::who(); // "Base" — 不是期望的 "Child"static:: 解决方案
php
<?php
declare(strict_types=1);
class Base
{
public static function create(): static
{
// static:: 在运行时解析为实际调用的类
return new static();
}
public static function who(): string
{
return static::class;
}
}
class Child extends Base
{
}
echo Base::who(); // "Base"
echo Child::who(); // "Child" — 符合预期get_called_class()
php
<?php
declare(strict_types=1);
class Animal
{
public static function identify(): string
{
return get_called_class();
}
}
class Dog extends Animal {}
class Cat extends Animal {}
echo Animal::identify(); // "Animal"
echo Dog::identify(); // "Dog"
echo Cat::identify(); // "Cat"工厂模式中的使用
php
<?php
declare(strict_types=1);
abstract class Model
{
protected static string $table = '';
public static function find(int $id): ?static
{
// static:: 保证了在子类调用时使用子类的表名
$table = static::$table;
echo "SELECT * FROM {$table} WHERE id = {$id}";
// 实际查询逻辑...
return null;
}
public static function all(): array
{
$table = static::$table;
echo "SELECT * FROM {$table}";
return [];
}
public function save(): void
{
$table = static::$table;
echo "INSERT INTO {$table} ...";
}
}
class User extends Model
{
protected static string $table = 'users';
}
class Order extends Model
{
protected static string $table = 'orders';
}
User::find(1); // SELECT * FROM users WHERE id = 1
Order::find(1); // SELECT * FROM orders WHERE id = 1
User::all(); // SELECT * FROM users继承链中的方法转发
php
<?php
declare(strict_types=1);
class BaseService
{
public static function process(): string
{
return static::handle();
}
protected static function handle(): string
{
return 'BaseService::handle';
}
}
class UserService extends BaseService
{
protected static function handle(): string
{
return 'UserService::handle';
}
}
class AdminService extends UserService
{
protected static function handle(): string
{
return 'AdminService::handle';
}
}
echo BaseService::process(); // BaseService::handle
echo UserService::process(); // UserService::handle
echo AdminService::process(); // AdminService::handle详细说明
static:: 的解析规则
static::在运行时根据实际调用上下文解析- 即使在父类方法中使用
static::,也会解析为子类 static::同样适用于属性访问(static::$property)static::可以与非静态方法一起使用(不推荐)
static:: 在非静态方法中
php
<?php
declare(strict_types=1);
class ParentClass
{
protected string $prefix = 'parent';
public function getPrefix(): string
{
return static::$prefix; // 运行时解析
}
}
class ChildClass extends ParentClass
{
protected string $prefix = 'child';
}
$parent = new ParentClass();
$child = new ChildClass();
echo $parent->getPrefix(); // "parent"
echo $child->getPrefix(); // "child"注意
在非静态方法中使用 static:: 访问静态属性时,需要确保子类也声明了该属性。
forward_static_call()
php
<?php
declare(strict_types=1);
class Processor
{
public static function process(string $type): string
{
return forward_static_call([static::class, 'handle' . ucfirst($type)]);
}
}
class TextProcessor extends Processor
{
public static function handleText(): string
{
return 'Text processed';
}
}
echo TextProcessor::process('text'); // "Text processed"实战示例
场景一:Active Record 模式
php
<?php
declare(strict_types=1);
abstract class ActiveRecord
{
protected static string $tableName = '';
protected array $attributes = [];
public static function find(int $id): ?static
{
$sql = sprintf(
'SELECT * FROM %s WHERE id = %d',
static::$tableName,
$id
);
// 模拟查询
$data = ['id' => $id, 'name' => 'Test'];
$instance = new static();
$instance->attributes = $data;
return $instance;
}
public static function create(array $attributes): static
{
$instance = new static();
$instance->attributes = $attributes;
// INSERT INTO static::$tableName ...
return $instance;
}
public function __get(string $name): mixed
{
return $this->attributes[$name] ?? null;
}
}
class User extends ActiveRecord
{
protected static string $tableName = 'users';
}
class Product extends ActiveRecord
{
protected static string $tableName = 'products';
}
$user = User::find(1);
$product = Product::find(10);
$newUser = User::create(['name' => 'Alice']);场景二:Builder 模式
php
<?php
declare(strict_types=1);
abstract class QueryBuilder
{
protected array $wheres = [];
protected int $limit = 0;
protected int $offset = 0;
public static function query(): static
{
return new static();
}
public function where(string $column, mixed $value): static
{
$this->wheres[$column] = $value;
return $this;
}
public function limit(int $limit): static
{
$this->limit = $limit;
return $this;
}
abstract public function get(): array;
}
class UserQuery extends QueryBuilder
{
public function active(): static
{
return $this->where('status', 'active');
}
public function get(): array
{
// 实现查询逻辑
return [
'table' => 'users',
'wheres' => $this->wheres,
'limit' => $this->limit,
];
}
}
$users = UserQuery::query()->active()->limit(10)->get();注意事项
注意事项
self::在编译时绑定,始终指向定义方法的类static::在运行时绑定,指向实际调用的类- 在静态方法中
new self()会创建定义类的实例,new static()创建调用类的实例 static::不能用于常量访问(PHP 8.3 前不支持static::CONST)
小贴士
- 在工厂方法中使用
new static()而非new self() - 在继承链中需要多态行为时使用
static:: - 对于明确的调用,使用
self::或parent::
最佳实践
1. 工厂方法使用 static 返回类型
php
<?php
declare(strict_types=1);
abstract class Entity
{
public static function create(array $data): static
{
return new static();
}
}2. 明确区分 self 和 static
php
<?php
declare(strict_types=1);
class Configuration
{
private static array $defaults = [];
// 明确需要当前类的 defaults
public static function getDefaults(): array
{
return self::$defaults;
}
// 需要子类的实现
public static function getTable(): string
{
return static::$table;
}
}