Skip to content

相对类类型 self / parent / static

概述

PHP 提供了三种相对类类型selfparentstatic。它们不是具体的类名,而是相对于当前类上下文的类型引用。这三种类型在参数类型和返回值类型声明中各有不同的语义和行为,特别是在继承场景中差异明显。

核心要点

  • 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));  // OK

static 作为返回值类型(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" — 符合预期

最佳实践

  1. 工厂方法使用 static 返回类型:当方法需要返回当前类或子类的实例时,使用 static 支持 Fluent Interface。

  2. 严格类型约束使用 self:当参数必须是当前类实例(不接受子类)时,使用 self

  3. 链式调用使用 static:Builder 模式中,static 确保子类扩展 Builder 时链式调用仍然返回正确类型。

  4. parent 用于调用父类方法parent 主要用于在子类中调用父类的同名方法或构造函数。

  5. ActiveRecord 模式使用 static:ORM 中的 findByIdcreate 等方法需要返回运行时类的实例。

  6. 避免在接口中使用相对类型:接口中的 self 指向实现类,static 在接口中的行为可能与预期不同。

参考链接