强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PHP 完全指南 / 第 11 章 — OOP 进阶

第 11 章 — OOP 进阶:继承、接口、Trait、抽象类与命名空间

11.1 继承(Inheritance)

<?php
declare(strict_types=1);

// 基类
class Animal
{
    public function __construct(
        protected string $name,
        protected int $age,
    ) {}

    public function eat(): string
    {
        return "{$this->name} is eating.";
    }

    public function sleep(): string
    {
        return "{$this->name} is sleeping.";
    }

    public function info(): string
    {
        return "{$this->name}, {$this->age} years old";
    }
}

// 子类继承
class Dog extends Animal
{
    public function __construct(
        string $name,
        int $age,
        private string $breed,
    ) {
        parent::__construct($name, $age);  // 调用父类构造器
    }

    // 重写方法
    public function info(): string
    {
        return parent::info() . ", breed: {$this->breed}";
    }

    // 新增方法
    public function bark(): string
    {
        return "{$this->name} says: Woof!";
    }
}

$dog = new Dog('Buddy', 3, 'Golden Retriever');
echo $dog->eat();    // Buddy is eating.
echo $dog->bark();   // Buddy says: Woof!
echo $dog->info();   // Buddy, 3 years old, breed: Golden Retriever

// 类型检查
var_dump($dog instanceof Dog);     // true
var_dump($dog instanceof Animal);  // true

final 关键字

<?php
// final 方法 — 子类不能重写
class BaseModel
{
    final public function save(): bool
    {
        // 保存逻辑
        return true;
    }
}

// final 类 — 不能被继承
final class SecurityToken
{
    public function __construct(
        private readonly string $token,
    ) {}
}
// class ExtendedToken extends SecurityToken {}  // Error!

11.2 抽象类(Abstract Class)

<?php
abstract class Shape
{
    public function __construct(
        protected string $color = 'black',
    ) {}

    // 抽象方法 — 子类必须实现
    abstract public function area(): float;
    abstract public function perimeter(): float;

    // 具体方法 — 子类可直接使用
    public function describe(): string
    {
        return sprintf(
            "%s: area=%.2f, perimeter=%.2f",
            static::class,
            $this->area(),
            $this->perimeter()
        );
    }
}

class Circle extends Shape
{
    public function __construct(
        string $color,
        private readonly float $radius,
    ) {
        parent::__construct($color);
    }

    public function area(): float
    {
        return M_PI * $this->radius ** 2;
    }

    public function perimeter(): float
    {
        return 2 * M_PI * $this->radius;
    }
}

class Rectangle extends Shape
{
    public function __construct(
        string $color,
        private readonly float $width,
        private readonly float $height,
    ) {
        parent::__construct($color);
    }

    public function area(): float
    {
        return $this->width * $this->height;
    }

    public function perimeter(): float
    {
        return 2 * ($this->width + $this->height);
    }
}

// 使用多态
$shapes = [
    new Circle('red', 5),
    new Rectangle('blue', 4, 6),
];

foreach ($shapes as $shape) {
    echo $shape->describe() . "\n";
}
// Circle: area=78.54, perimeter=31.42
// Rectangle: area=24.00, perimeter=20.00

11.3 接口(Interface)

<?php
interface Serializable2
{
    public function serialize(): string;
    public static function deserialize(string $data): self;
}

interface Loggable
{
    public function toLogContext(): array;
}

interface Cacheable
{
    public function getCacheKey(): string;
    public function getCacheTTL(): int;
}

// 实现多个接口
class User implements Serializable2, Loggable, Cacheable
{
    public function __construct(
        private string $name,
        private string $email,
    ) {}

    public function serialize(): string
    {
        return json_encode(['name' => $this->name, 'email' => $this->email]);
    }

    public static function deserialize(string $data): self
    {
        $decoded = json_decode($data, true);
        return new self($decoded['name'], $decoded['email']);
    }

    public function toLogContext(): array
    {
        return ['user' => $this->name, 'email' => $this->email];
    }

    public function getCacheKey(): string
    {
        return 'user:' . md5($this->email);
    }

    public function getCacheTTL(): int
    {
        return 3600;
    }
}

接口继承

<?php
interface Repository
{
    public function find(int $id): ?array;
    public function findAll(): array;
    public function save(array $data): bool;
    public function delete(int $id): bool;
}

// 接口可以继承接口
interface UserRepository extends Repository
{
    public function findByEmail(string $email): ?array;
    public function findActive(): array;
}

// 接口常量
interface HttpStatus
{
    const OK = 200;
    const NOT_FOUND = 404;
    const SERVER_ERROR = 500;
}

11.4 Trait

Trait 是一种代码复用机制,解决了 PHP 单继承的限制。

<?php
declare(strict_types=1);

trait Timestamps
{
    private ?DateTimeImmutable $createdAt = null;
    private ?DateTimeImmutable $updatedAt = null;

    public function getCreatedAt(): DateTimeImmutable
    {
        return $this->createdAt ??= new DateTimeImmutable();
    }

    public function getUpdatedAt(): DateTimeImmutable
    {
        return $this->updatedAt ??= new DateTimeImmutable();
    }

    public function touch(): void
    {
        $this->updatedAt = new DateTimeImmutable();
    }
}

trait SoftDelete
{
    private ?DateTimeImmutable $deletedAt = null;

    public function delete(): void
    {
        $this->deletedAt = new DateTimeImmutable();
    }

    public function restore(): void
    {
        $this->deletedAt = null;
    }

    public function isDeleted(): bool
    {
        return $this->deletedAt !== null;
    }
}

trait HasValidation
{
    private array $errors = [];

    public function validate(): bool
    {
        $this->errors = [];
        $this->validateRules();
        return empty($this->errors);
    }

    public function getErrors(): array
    {
        return $this->errors;
    }

    abstract protected function validateRules(): void;

    protected function addError(string $field, string $message): void
    {
        $this->errors[$field][] = $message;
    }
}

// 使用 Trait
class Article
{
    use Timestamps;
    use SoftDelete;
    use HasValidation;

    public function __construct(
        private string $title = '',
        private string $content = '',
    ) {
        $this->createdAt = new DateTimeImmutable();
    }

    protected function validateRules(): void
    {
        if ($this->title === '') {
            $this->addError('title', 'Title is required');
        }
        if (mb_strlen($this->content) < 10) {
            $this->addError('content', 'Content too short');
        }
    }
}

$article = new Article('', 'Short');
var_dump($article->validate());  // false
print_r($article->getErrors());

Trait 冲突解决

<?php
trait A
{
    public function hello(): string { return 'Hello from A'; }
    public function onlyA(): string { return 'Only in A'; }
}

trait B
{
    public function hello(): string { return 'Hello from B'; }
    public function onlyB(): string { return 'Only in B'; }
}

class MyClass
{
    // 解决冲突:选择一个
    use A, B {
        A::hello insteadof B;   // 使用 A 的 hello
        B::hello as helloFromB; // B 的 hello 别名为 helloFromB
    }

    // 为 trait 方法设置别名
    use A {
        onlyA as public exposedA;
    }
}

$obj = new MyClass();
echo $obj->hello();        // Hello from A
echo $obj->helloFromB();   // Hello from B
echo $obj->exposedA();     // Only in A

11.5 命名空间(Namespaces)

11.5.1 基本用法

<?php
// 文件: src/Models/User.php
namespace App\Models;

class User
{
    public function __construct(
        public string $name,
    ) {}
}
<?php
// 文件: src/Services/AuthService.php
namespace App\Services;

use App\Models\User;               // 导入类
use App\Repositories\UserRepository as UserRepo;  // 别名
use function App\Helpers\formatDate;  // 导入函数
use const App\Config\MAX_RETRIES;      // 导入常量

class AuthService
{
    public function __construct(
        private UserRepo $userRepo,
    ) {}

    public function login(string $email): ?User
    {
        $user = $this->userRepo->findByEmail($email);
        if ($user) {
            formatDate(new \DateTime());  // 使用完全限定名调用全局类
        }
        return $user;
    }
}

11.5.2 命名空间层级

App\
├── Controllers\
│   ├── HomeController.php       → App\Controllers\HomeController
│   └── Api\
│       └── UserController.php   → App\Controllers\Api\UserController
├── Models\
│   ├── User.php                 → App\Models\User
│   └── Post.php                 → App\Models\Post
├── Services\
│   └── AuthService.php          → App\Services\AuthService
├── Repositories\
│   └── UserRepository.php       → App\Repositories\UserRepository
└── Helpers\
    └── DateHelper.php           → App\Helpers\DateHelper

11.5.3 自动加载

// composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    }
}
# 重新生成自动加载映射
composer dump-autoload

11.6 对象克隆与序列化

<?php
class Config
{
    public function __construct(
        public array $data = [],
    ) {}

    // 浅克隆
    public function __clone(): void
    {
        // 嵌套对象仍指向同一个引用
    }
}

$a = new Config(['key' => 'value']);
$b = clone $a;
$b->data['key'] = 'changed';
echo $a->data['key'];  // 'value'(独立副本)

// 深克隆
class DeepCopyConfig
{
    public function __clone(): void
    {
        foreach ($this->data as $key => $value) {
            if (is_object($value)) {
                $this->data[$key] = clone $value;
            }
        }
    }
}

// 序列化
$serialized = serialize($a);
$restored = unserialize($serialized);

// JSON 序列化(实现 JsonSerializable 接口)
class Money implements \JsonSerializable
{
    public function __construct(
        private int $cents,
        private string $currency,
    ) {}

    public function jsonSerialize(): array
    {
        return ['amount' => $this->cents / 100, 'currency' => $this->currency];
    }
}

11.7 PHP 8.x 类新特性

11.7.1 readonly 类(PHP 8.2+)

<?php
readonly class UserDTO
{
    public function __construct(
        public string $name,
        public int $age,
    ) {}
}

$user = new UserDTO('Alice', 30);
// $user->name = 'Bob';  // Error: Cannot modify readonly property

11.7.2 #[\Override] 属性(PHP 8.3+)

<?php
class Base
{
    public function process(): void {}
}

class Child extends Base
{
    #[\Override]  // 如果方法名拼写错误或父类删除了该方法,会报错
    public function process(): void
    {
        parent::process();
    }
}

11.8 业务场景:仓储模式

<?php
declare(strict_types=1);

namespace App\Repositories;

use App\Models\User;
use PDO;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function findAll(int $limit = 100, int $offset = 0): array;
    public function save(User $user): bool;
    public function delete(int $id): bool;
}

class UserRepository implements UserRepositoryInterface
{
    public function __construct(
        private readonly PDO $db,
    ) {}

    public function findById(int $id): ?User
    {
        $stmt = $this->db->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? User::fromArray($row) : null;
    }

    public function findByEmail(string $email): ?User
    {
        $stmt = $this->db->prepare('SELECT * FROM users WHERE email = ?');
        $stmt->execute([$email]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? User::fromArray($row) : null;
    }

    public function findAll(int $limit = 100, int $offset = 0): array
    {
        $stmt = $this->db->prepare(
            'SELECT * FROM users ORDER BY id DESC LIMIT ? OFFSET ?'
        );
        $stmt->execute([$limit, $offset]);

        return array_map(
            fn(array $row) => User::fromArray($row),
            $stmt->fetchAll(PDO::FETCH_ASSOC)
        );
    }

    public function save(User $user): bool
    {
        if ($user->getId() === null) {
            return $this->insert($user);
        }
        return $this->update($user);
    }

    public function delete(int $id): bool
    {
        $stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
        return $stmt->execute([$id]);
    }

    private function insert(User $user): bool
    {
        $stmt = $this->db->prepare(
            'INSERT INTO users (name, email) VALUES (?, ?)'
        );
        return $stmt->execute([$user->getName(), $user->getEmail()]);
    }

    private function update(User $user): bool
    {
        $stmt = $this->db->prepare(
            'UPDATE users SET name = ?, email = ? WHERE id = ?'
        );
        return $stmt->execute([$user->getName(), $user->getEmail(), $user->getId()]);
    }
}

11.9 扩展阅读


上一章第 10 章 — OOP 基础 下一章第 12 章 — 异常处理