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

PHP 完全指南 / 第 10 章 — OOP 基础

第 10 章 — OOP 基础:类、属性、方法、构造器与枚举

10.1 类的定义

<?php
declare(strict_types=1);

class User
{
    // 属性声明(PHP 7.4+ 类型化属性)
    public string $name;
    public int $age;
    public string $email;
    protected string $password;
    private array $roles = [];

    // 构造器
    public function __construct(string $name, int $age, string $email)
    {
        $this->name = $name;
        $this->age = $age;
        $this->email = $email;
        $this->password = '';
    }

    // 方法
    public function greet(): string
    {
        return "你好,我是 {$this->name},今年 {$this->age} 岁。";
    }

    // 访问私有属性
    public function addRole(string $role): void
    {
        $this->roles[] = $role;
    }

    public function getRoles(): array
    {
        return $this->roles;
    }
}

$user = new User('Alice', 30, '[email protected]');
echo $user->greet();  // 你好,我是 Alice,今年 30 岁。

10.2 构造器提升(Constructor Promotion)— PHP 8.0+

<?php
// 传统写法
class UserOld
{
    public string $name;
    public int $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

// PHP 8.0+ 构造器提升(一行搞定)
class User
{
    public function __construct(
        public string $name,
        public int $age,
        protected string $password = '',
        private array $roles = [],
    ) {}
}

$user = new User('Alice', 30);
echo $user->name;  // Alice

10.3 属性(Properties)

10.3.1 访问控制

修饰符类内子类外部
public
protected
private

10.3.2 readonly 属性(PHP 8.1+)

<?php
class Product
{
    public function __construct(
        public readonly string $id,
        public readonly string $name,
        public readonly float $price,
    ) {}
}

$product = new Product('P001', 'iPhone', 999.99);
echo $product->name;  // iPhone
// $product->name = 'iPad';  // Error: Cannot modify readonly property

10.3.3 Property Hooks(PHP 8.4+)

<?php
class User
{
    public string $name {
        set(string $value) {
            $this->name = trim($value);
        }
    }

    public string $email {
        set(string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException("Invalid email: $value");
            }
            $this->email = strtolower($value);
        }
    }

    // 计算属性(只读钩子)
    public string $displayName {
        get => $this->name . ' <' . $this->email . '>';
    }

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }
}

$user = new User('Alice', '[email protected]');
echo $user->name;          // Alice
echo $user->email;         // [email protected]
echo $user->displayName;   // Alice <[email protected]>

10.3.4 非对称可见性(PHP 8.4+)

<?php
class User
{
    // 外部可读,但只能在类内设置
    public private(set) string $email;
    public protected(set) int $loginCount = 0;

    public function __construct(string $email)
    {
        $this->email = $email;  // ✅ 类内可写
    }

    public function incrementLoginCount(): void
    {
        $this->loginCount++;  // ✅ 类内可写
    }
}

$user = new User('[email protected]');
echo $user->email;        // ✅ 可读
// $user->email = '...';  // ❌ 外部不可写

10.4 方法(Methods)

<?php
class Calculator
{
    private float $result = 0;

    // 实例方法
    public function add(float $value): static
    {
        $this->result += $value;
        return $this;  // 链式调用
    }

    public function multiply(float $value): static
    {
        $this->result *= $value;
        return $this;
    }

    public function getResult(): float
    {
        return $this->result;
    }

    // 静态方法
    public static function create(float $initial = 0): static
    {
        $calc = new static();
        $calc->result = $initial;
        return $calc;
    }
}

// 链式调用
$result = Calculator::create(10)
    ->add(5)
    ->multiply(3)
    ->getResult();
echo $result;  // 45

10.5 静态属性与方法

<?php
class AppConfig
{
    private static array $config = [];
    private static ?self $instance = null;

    // 单例模式
    private function __construct() {}

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public static function set(string $key, mixed $value): void
    {
        self::$config[$key] = $value;
    }

    public static function get(string $key, mixed $default = null): mixed
    {
        return self::$config[$key] ?? $default;
    }
}

AppConfig::set('app.name', 'MyApp');
AppConfig::set('app.debug', true);
echo AppConfig::get('app.name');  // MyApp

10.6 抽象属性与接口常量

<?php
// 接口常量
interface Status
{
    const ACTIVE   = 1;
    const INACTIVE = 0;
    const DELETED  = -1;

    public function getStatus(): int;
}

// 使用接口常量
class User implements Status
{
    public function getStatus(): int
    {
        return Status::ACTIVE;
    }
}

echo User::ACTIVE;  // 1

10.7 枚举(Enums)— PHP 8.1+

10.7.1 基础枚举

<?php
enum Color
{
    case Red;
    case Green;
    case Blue;
}

// 使用
$color = Color::Red;
echo $color->name;  // "Red"

// 比较(严格)
var_dump($color === Color::Red);    // true
var_dump($color === Color::Green);  // false

// 从名称创建
$color = Color::from('Green');        // 不存在时抛 ValueError
$color = Color::tryFrom('Unknown');   // 不存在时返回 null

// 遍历所有 case
foreach (Color::cases() as $case) {
    echo $case->name . "\n";
}

10.7.2 标量枚举(Backed Enum)

<?php
enum OrderStatus: string
{
    case Pending   = 'pending';
    case Processing = 'processing';
    case Shipped   = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';
}

// 获取标量值
echo OrderStatus::Pending->value;  // "pending"

// 从标量值创建
$status = OrderStatus::from('shipped');
echo $status->name;  // "Shipped"

// 在 switch/match 中使用
$message = match ($status) {
    OrderStatus::Pending    => '订单待处理',
    OrderStatus::Processing => '正在处理中',
    OrderStatus::Shipped    => '已发货',
    OrderStatus::Delivered  => '已送达',
    OrderStatus::Cancelled  => '已取消',
};

10.7.3 枚举方法

<?php
enum Suit: string
{
    case Hearts   = 'hearts';
    case Diamonds = 'diamonds';
    case Clubs    = 'clubs';
    case Spades   = 'spades';

    // 枚举可以有方法
    public function color(): string
    {
        return match ($this) {
            self::Hearts, self::Diamonds => 'red',
            self::Clubs, self::Spades    => 'black',
        };
    }

    public function symbol(): string
    {
        return match ($this) {
            self::Hearts   => '♥',
            self::Diamonds => '♦',
            self::Clubs    => '♣',
            self::Spades   => '♠',
        };
    }

    public function label(): string
    {
        return match ($this) {
            self::Hearts   => '红心',
            self::Diamonds => '方块',
            self::Clubs    => '梅花',
            self::Spades   => '黑桃',
        };
    }
}

$card = Suit::Hearts;
echo $card->symbol();  // ♥
echo $card->color();   // red
echo $card->label();   // 红心

10.7.4 枚举实现接口

<?php
interface HasLabel
{
    public function label(): string;
}

enum Permission: int implements HasLabel
{
    case Read    = 1;
    case Write   = 2;
    case Execute = 4;
    case Admin   = 7;

    public function label(): string
    {
        return match ($this) {
            self::Read    => '读取',
            self::Write   => '写入',
            self::Execute => '执行',
            self::Admin   => '管理员',
        };
    }

    // 位运算权限检查
    public function includes(self $permission): bool
    {
        return ($this->value & $permission->value) === $permission->value;
    }
}

$perms = Permission::Admin;
echo $perms->includes(Permission::Read);   // true
echo $perms->includes(Permission::Write);  // true

10.8 对象比较

<?php
class Point
{
    public function __construct(
        public float $x,
        public float $y,
    ) {}
}

$p1 = new Point(1.0, 2.0);
$p2 = new Point(1.0, 2.0);
$p3 = $p1;

// 同一对象(===)
var_dump($p1 === $p3);   // true(同一个实例)
var_dump($p1 === $p2);   // false(不同实例)

// 属性值相等(==)
var_dump($p1 == $p2);    // true(属性值相同)

10.9 业务场景:值对象(Value Object)

<?php
declare(strict_types=1);

class Money
{
    public function __construct(
        private readonly int $amount,    // 以分为单位
        private readonly string $currency,
    ) {
        if ($amount < 0) {
            throw new InvalidArgumentException('Amount must be non-negative');
        }
        if (!preg_match('/^[A-Z]{3}$/', $currency)) {
            throw new InvalidArgumentException('Invalid currency code');
        }
    }

    public static function fromDecimal(float $amount, string $currency): self
    {
        return new self((int)round($amount * 100), $currency);
    }

    public function getAmount(): int
    {
        return $this->amount;
    }

    public function getDecimal(): float
    {
        return $this->amount / 100;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }

    public function add(self $other): self
    {
        $this->assertSameCurrency($other);
        return new self($this->amount + $other->amount, $this->currency);
    }

    public function subtract(self $other): self
    {
        $this->assertSameCurrency($other);
        $result = $this->amount - $other->amount;
        if ($result < 0) {
            throw new RuntimeException('Insufficient amount');
        }
        return new self($result, $this->currency);
    }

    public function multiply(int $factor): self
    {
        return new self($this->amount * $factor, $this->currency);
    }

    public function equals(self $other): bool
    {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }

    public function format(): string
    {
        return match ($this->currency) {
            'CNY' => '¥' . number_format($this->getDecimal(), 2),
            'USD' => '$' . number_format($this->getDecimal(), 2),
            'EUR' => '€' . number_format($this->getDecimal(), 2),
            default => $this->currency . ' ' . number_format($this->getDecimal(), 2),
        };
    }

    public function __toString(): string
    {
        return $this->format();
    }

    private function assertSameCurrency(self $other): void
    {
        if ($this->currency !== $other->currency) {
            throw new RuntimeException('Currency mismatch');
        }
    }
}

// 使用示例
$price = Money::fromDecimal(99.99, 'CNY');
$tax = Money::fromDecimal(12.99, 'CNY');
$total = $price->add($tax);

echo $total->format();  // ¥112.98
echo $total->getAmount();  // 11298(分)

10.10 扩展阅读


上一章第 9 章 — 字符串 下一章第 11 章 — OOP 进阶