相对类类型 self / parent / static
概述
PHP 提供了三种相对类类型:self、parent 和 static。它们不是具体的类名,而是相对于当前类上下文的类型引用。这三种类型在参数类型和返回值类型声明中各有不同的语义和行为,特别是在继承场景中差异明显。
核心要点
self指向定义该方法的类(编译时解析)。parent指向当前类的父类。static指向运行时调用的类(后期静态绑定)。- PHP 8.0 起
static可作为返回值类型。 - 三者都不能用于联合类型中的交集部分。
- :::
基础概念
三种相对类型对比
| 类型 | 解析时机 | 指向 | 用途 | 可用于参数 | 可用于返回值 |
|---|---|---|---|---|---|
self | 编译时 | 声明该方法的类 | 返回当前类的实例 | 可以 | 可以 |
parent | 编译时 | 当前类的父类 | 调用父类方法 | 可以 | 可以 |
static | 运行时 | 实际调用的类 | 后期静态绑定 | 不可以(仅返回值) | PHP 8.0+ 可以 |
self 的语义
self 始终指向定义该方法的类,不受继承影响:
php
<?php
declare(strict_types=1);
class Base
{
public function create(): self
{
return new self(); // 始终返回 Base 实例
}
}
class Child extends Base
{
}
$obj = new Child();
$result = $obj->create(); // 返回 Base 实例,不是 Child 实例static 的语义
static 指向实际调用该方法的类(后期静态绑定,Late Static Binding):
php
<?php
declare(strict_types=1);
class Base
{
public function create(): static
{
return new static(); // 返回实际调用类的实例
}
}
class Child extends Base
{
}
$obj = new Child();
$result = $obj->create(); // 返回 Child 实例语法与代码示例
self 作为参数和返回值类型
php
<?php
declare(strict_types=1);
class Entity
{
private int $id;
public function __construct(int $id)
{
$this->id = $id;
}
// self 作为参数:只接受 Entity 实例(不接受子类)
public function merge(self $other): self
{
// 合并逻辑...
return new self($this->id);
}
// self 作为返回值:始终返回 Entity 实例
public function clone(): self
{
return new self($this->id);
}
}
class User extends Entity
{
private string $name;
public function __construct(int $id, string $name)
{
parent::__construct($id);
$this->name = $name;
}
}
$user = new User(1, 'Alice');
// $user->merge(new User(2, 'Bob')); // TypeError: 只接受 Entity
$entity = new Entity(1);
$entity->merge(new Entity(2)); // OKstatic 作为返回值类型(PHP 8.0+)
php
<?php
declare(strict_types=1);
abstract class Builder
{
protected array $data = [];
public function setData(string $key, mixed $value): static
{
$this->data[$key] = $value;
return $this; // 返回运行时类实例
}
public function build(): static
{
$class = static::class;
return new $class();
}
}
class UserBuilder extends Builder
{
public function setName(string $name): static
{
$this->data['name'] = $name;
return $this;
}
public function setEmail(string $email): static
{
$this->data['email'] = $email;
return $this;
}
}
// 链式调用,每个方法都返回 UserBuilder 实例
$builder = (new UserBuilder())
->setName('Alice')
->setEmail('alice@example.com')
->build();parent 的使用
php
<?php
declare(strict_types=1);
abstract class Animal
{
protected string $sound;
public function __construct(string $sound)
{
$this->sound = $sound;
}
public function makeSound(): string
{
return $this->sound;
}
}
class Dog extends Animal
{
public function __construct()
{
parent::__construct('Woof');
}
// parent 作为参数类型
public function compareWith(parent $other): bool
{
return $this->makeSound() === $other->makeSound();
}
}继承中的 self 与 static 对比
php
<?php
declare(strict_types=1);
class Base
{
public function getSelf(): self
{
return new self();
}
public function getStatic(): static
{
return new static();
}
}
class Child extends Base {}
$child = new Child();
$a = $child->getSelf();
var_dump(get_class($a)); // string(4) "Base"
$b = $child->getStatic();
var_dump(get_class($b)); // string(5) "Child"详细说明
self 在继承中的行为
self 在继承中保持不变,始终指向定义方法的类:
php
<?php
declare(strict_types=1);
class Repository
{
// self 始终返回 Repository 实例
public function find(int $id): ?self
{
// 数据库查找逻辑
return null;
}
}
class UserRepository extends Repository
{
// 覆盖父类方法
public function find(int $id): ?User
{
$result = parent::find($id);
if ($result === null) {
return null;
}
return new User($result);
}
}static 返回类型的协变
在 PHP 8.0 中,static 作为返回类型时遵循协变规则。子类中的 static 可以被更具体的类型替换:
php
<?php
declare(strict_types=1);
abstract class Model
{
public static function create(): static
{
return new static();
}
public static function fromArray(array $data): static
{
$instance = new static();
foreach ($data as $key => $value) {
$instance->$key = $value;
}
return $instance;
}
}
class User extends Model
{
public string $name = '';
public static function create(): User
{
// static 的协变:返回类型从 static 缩窄为 User
return new User();
}
}static 不能用于参数类型
php
<?php
declare(strict_types=1);
class Base
{
// 错误:static 不能用于参数类型
public function compare(static $other): bool {}
// Fatal Error
// 正确:使用 self 或具体类名
public function compareWith(self $other): bool {}
public function equals(Base $other): bool {}
}static 参数限制
static 只能作为返回值类型使用(PHP 8.0+),不能用于参数类型声明。这是因为参数类型的检查发生在编译时,而 static 的解析发生在运行时。
三种相对类型与 new 操作符
php
<?php
declare(strict_types=1);
class ParentClass
{
public function newInstanceSelf(): self
{
return new self(); // ParentClass 实例
}
public function newInstanceStatic(): static
{
return new static(); // 运行时类实例
}
}
class ChildClass extends ParentClass
{
public function newInstanceParent(): parent
{
return new parent(); // ParentClass 实例
}
}
$child = new ChildClass();
echo get_class($child->newInstanceSelf()); // "ParentClass"
echo get_class($child->newInstanceStatic()); // "ChildClass"
echo get_class($child->newInstanceParent()); // "ParentClass"实战示例
ActiveRecord 模式
php
<?php
declare(strict_types=1);
abstract class ActiveRecord
{
protected static string $tableName = '';
public static function findById(int $id): ?static
{
// 模拟数据库查询
$data = self::query(
"SELECT * FROM " . static::$tableName . " WHERE id = ?",
[$id]
);
if ($data === null) {
return null;
}
return static::hydrate($data);
}
public function save(): static
{
// 保存到数据库
return $this;
}
public function delete(): void
{
// 从数据库删除
}
protected static function hydrate(array $data): static
{
$instance = new static();
foreach ($data as $key => $value) {
if (property_exists($instance, $key)) {
$instance->$key = $value;
}
}
return $instance;
}
private static function query(string $sql, array $params): ?array
{
return ['id' => 1, 'name' => 'Alice'];
}
}
class User extends ActiveRecord
{
protected static string $tableName = 'users';
public int $id = 0;
public string $name = '';
public string $email = '';
}
class Post extends ActiveRecord
{
protected static string $tableName = 'posts';
public int $id = 0;
public string $title = '';
}
// static 返回类型确保返回正确类型的实例
$user = User::findById(1); // 返回 User 实例
$post = Post::findById(1); // 返回 Post 实例Builder 模式(链式调用)
php
<?php
declare(strict_types=1);
class QueryBuilder
{
protected string $table = '';
protected array $where = [];
protected array $orderBy = [];
protected ?int $limit = null;
protected ?int $offset = null;
public static function table(string $table): static
{
$instance = new static();
$instance->table = $table;
return $instance;
}
public function where(string $column, mixed $value): static
{
$this->where[$column] = $value;
return $this;
}
public function orderBy(string $column, string $direction = 'ASC'): static
{
$this->orderBy[$column] = $direction;
return $this;
}
public function limit(int $limit): static
{
$this->limit = $limit;
return $this;
}
public function offset(int $offset): static
{
$this->offset = $offset;
return $this;
}
public function getSql(): string
{
$sql = "SELECT * FROM {$this->table}";
if (!empty($this->where)) {
$conditions = array_map(
fn($col) => "{$col} = ?",
array_keys($this->where)
);
$sql .= " WHERE " . implode(' AND ', $conditions);
}
if (!empty($this->orderBy)) {
$orderClauses = array_map(
fn($col, $dir) => "{$col} {$dir}",
array_keys($this->orderBy),
$this->orderBy
);
$sql .= " ORDER BY " . implode(', ', $orderClauses);
}
if ($this->limit !== null) {
$sql .= " LIMIT {$this->limit}";
}
if ($this->offset !== null) {
$sql .= " OFFSET {$this->offset}";
}
return $sql;
}
}
class UserQueryBuilder extends QueryBuilder
{
public function whereActive(): static
{
return $this->where('status', 'active');
}
public function orderByCreated(): static
{
return $this->orderBy('created_at', 'DESC');
}
}
// 链式调用,static 确保返回 UserQueryBuilder 实例
$sql = UserQueryBuilder::table('users')
->whereActive()
->orderByCreated()
->limit(10)
->getSql();注意事项
self 与 static 的选择指南
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 工厂方法返回实例 | static | 支持继承时返回子类实例 |
| Builder 链式调用 | static | 确保链式调用返回正确类型 |
| 方法参数类型 | self | 编译时确定,明确接受当前类 |
| 不需要支持继承的返回值 | self | 更明确,不受后期绑定影响 |
| 调用父类构造/方法 | parent | 明确指向父类 |
常见陷阱
php
<?php
declare(strict_types=1);
class Base
{
// 陷阱:self 返回 Base,子类调用时可能不符合预期
public function create(): self
{
return new self(); // 总是返回 Base
}
// 正确:static 返回运行时类
public function make(): static
{
return new static(); // 返回实际调用类
}
}
class Child extends Base {}
$child = new Child();
var_dump(get_class($child->create())); // "Base" — 可能出乎意料
var_dump(get_class($child->make())); // "Child" — 符合预期最佳实践
工厂方法使用
static返回类型:当方法需要返回当前类或子类的实例时,使用static支持 Fluent Interface。严格类型约束使用
self:当参数必须是当前类实例(不接受子类)时,使用self。链式调用使用
static:Builder 模式中,static确保子类扩展 Builder 时链式调用仍然返回正确类型。parent 用于调用父类方法:
parent主要用于在子类中调用父类的同名方法或构造函数。ActiveRecord 模式使用
static:ORM 中的findById、create等方法需要返回运行时类的实例。避免在接口中使用相对类型:接口中的
self指向实现类,static在接口中的行为可能与预期不同。