自 PHP 5 引入完整物件模型以來,OOP 已成為企業級開發標準。PHP 8.x 更加入了屬性提升建構子、唯讀屬性、聯集型別、match 運算式等現代特性。本文系統梳理從類別定義、可見性、魔術方法,到介面、抽象類別、Trait、最終類別等核心概念,並提供生產級程式碼範例。
<?php
declare(strict_types=1);
namespace App\Domain\HR;
final class Employee {
// 唯讀屬性(PHP 8.1+):初始化後不可變,適合 ID、建立時間
public readonly string $id;
// 型別宣告 + 可見性
private string $name;
protected string $title;
public float $salary;
// 類別常數
public const string TAX_RATE = '0.05';
// 靜態屬性:屬於類別而非實例
private static int $instanceCount = 0;
// 建構子屬性提升(PHP 8.0+):簡化屬性宣告與賦值
public function __construct(
string $id,
string $name,
string $title,
float $salary
) {
$this->id = $id;
$this->name = $name;
$this->title = $title;
$this->salary = $salary;
self::$instanceCount++;
}
// 解構子:資源釋放、日誌記錄
public function __destruct() {
self::$instanceCount--;
error_log("Employee {$this->id} destroyed");
}
// 公開方法
public function clockIn(): void {
echo "Employee {$this->name} ({$this->id}) clocked in at " . date('H:i:s') . PHP_EOL;
}
// 存取器模式(PHP 8.2+ 可用 #[Property] 屬性)
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
if (strlen($name) < 2) {
throw new \InvalidArgumentException('Name too short');
}
$this->name = $name;
}
// 靜態方法
public static function getInstanceCount(): int {
return self::$instanceCount;
}
// final 方法:禁止子類別覆寫
final public function getTaxRate(): float {
return (float)self::TAX_RATE;
}
}
?>
| 關鍵字 | 作用範圍 | 繼承行為 | 典型用途 |
|---|---|---|---|
| public | 任意處 | 保持 public | API 介面、DTO 屬性 |
| protected | 類別內部 + 子類別 | 保持 protected | 內部邏輯、模板方法 |
| private | 僅目前類別 | 不可見、不可覆寫 | 核心狀態、實作細節 |
| static | 類別層級 | 共享同一儲存空間 | 計數器、快取、單例 |
| final | 類別/方法 | 禁止繼承/覆寫 | 領域模型、安全關鍵邏輯 |
| readonly | 屬性 | 子類別可繼承但不可寫 | 不可變物件、值物件 |
<?php
abstract class Manager extends Employee {
// 抽象方法:強制子類別實作
abstract public function approveLeave(string $employeeId, int $days): bool;
// 模板方法:定義流程骨架,延遲具體步驟到子類別
final public function processLeaveRequest(string $employeeId, int $days): array {
$this->validateLeaveDays($days);
$approved = $this->approveLeave($employeeId, $days);
$this->notifyApplicant($employeeId, $approved);
return ['approved' => $approved];
}
private function validateLeaveDays(int $days): void {
if ($days <= 0 || $days > 30) {
throw new \DomainException('Invalid leave days');
}
}
protected function notifyApplicant(string $employeeId, bool $approved): void {
// 實際對接通知服務
error_log("Notify {$employeeId}: " . ($approved ? 'Approved' : 'Rejected'));
}
}
class SeniorManager extends Manager {
public function approveLeave(string $employeeId, int $days): bool {
// 高階主管自動批准 ≤ 7 天
return $days <= 7;
}
}
class Director extends Manager {
public function approveLeave(string $employeeId, int $days): bool {
// 總監全部批准
return true;
}
}
// 使用範例
$mgr = new SeniorManager('M001', 'Alice', 'Senior Manager', 150000);
$result = $mgr->processLeaveRequest('E100', 5); // ['approved' => true]
?>
<?php
class FlexibleEntity {
private array $data = [];
// 屬性不存在時讀取
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
// 屬性不存在時寫入
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
// isset() / empty() 檢查
public function __isset(string $name): bool {
return isset($this->data[$name]);
}
// unset() 操作
public function __unset(string $name): void {
unset($this->data[$name]);
}
// 物件當作字串輸出
public function __toString(): string {
return json_encode($this->data, JSON_UNESCAPED_UNICODE);
}
// 物件當作函數呼叫
public function __invoke(string $action, ...$args): mixed {
return match($action) {
'dump' => $this->data,
'keys' => array_keys($this->data),
default => throw new \BadMethodCallException("Unknown action: $action"),
};
}
// 序列化控制(PHP 7.4+ 建議用 __serialize/__unserialize)
public function __serialize(): array {
return ['data' => $this->data, 'version' => 1];
}
public function __unserialize(array $data): void {
$this->data = $data['data'] ?? [];
}
// 克隆時深拷貝關聯物件
public function __clone(): void {
foreach ($this->data as $k => $v) {
if (is_object($v) && method_exists($v, '__clone')) {
$this->data[$k] = clone $v;
}
}
}
// 呼叫不存在的方法
public function __call(string $name, array $arguments): mixed {
throw new \BadMethodCallException("Method $name not found");
}
// 靜態呼叫不存在的方法
public static function __callStatic(string $name, array $arguments): mixed {
throw new \BadMethodCallException("Static method $name not found");
}
}
?>
| 維度 | 介面 | 抽象類別 |
|---|---|---|
| 多重實作 | 一個類別可 implements 多個介面 | 單一繼承 |
| 方法實作 | PHP 8.0+ 可有預設實作 | 可包含具體與抽象方法混合 |
| 屬性定義 | 不可定義屬性(僅常數) | 可定義屬性、建構子 |
| 適用場景 | 定義「行為契約」、跨領域複用 | 定義「核心骨架」、共享實作邏輯 |
| 經典例子 | LoggerInterface, CacheInterface, Serializable | BaseEntity, AbstractController, BaseRepository |
<?php
interface PaymentGateway {
public function charge(float $amount, string $currency): PaymentResult;
public function refund(string $transactionId, float $amount): RefundResult;
}
class StripeGateway implements PaymentGateway { /* ... */ }
class PayPalGateway implements PaymentGateway { /* ... */ }
class PaymentService {
public function __construct(
private PaymentGateway $gateway // 建構子注入,依賴抽象
) {}
public function processOrder(Order $order): void {
$result = $this->gateway->charge($order->total, $order->currency);
// ...
}
}
?>
<?php
abstract class BaseModel {
protected \PDO $db;
protected string $table;
protected array $fillable = [];
public function __construct(\PDO $db) {
$this->db = $db;
}
// 具體方法:共享 CRUD 邏輯
public function find(int $id): ?static {
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
return $row ? $this->hydrate($row) : null;
}
public function save(): bool {
if (empty($this->id)) {
return $this->insert();
}
return $this->update();
}
// 抽象方法:子類別定義表名與欄位
abstract protected function getTable(): string;
abstract protected function getFillable(): array;
// 受保護輔助方法
protected function hydrate(array $row): static {
$obj = new static($this->db);
foreach ($row as $k => $v) {
$obj->$k = $v;
}
return $obj;
}
}
class UserModel extends BaseModel {
protected function getTable(): string { return 'users'; }
protected function getFillable(): array { return ['name', 'email', 'password_hash']; }
}
?>
<?php
trait Timestampable {
public \DateTimeImmutable $createdAt;
public \DateTimeImmutable $updatedAt;
public function touch(): void {
$this->updatedAt = new \DateTimeImmutable();
}
}
trait SoftDeletes {
public ?\DateTimeImmutable $deletedAt = null;
public function delete(): void {
$this->deletedAt = new \DateTimeImmutable();
}
public function isDeleted(): bool {
return $this->deletedAt !== null;
}
}
class Article {
use Timestampable, SoftDeletes;
public function __construct() {
$this->createdAt = new \DateTimeImmutable();
$this->updatedAt = $this->createdAt;
}
}
?>
注意:Trait 衝突解決用 insteadof、as 關鍵字;避免過度使用導致「Trait 爆炸」。
readonly class Config { public string $key; } — 所有屬性自動唯讀enum Status: string { case Draft = 'draft'; case Published = 'published'; } — 替代常數類別#[Route('/api/users', methods: ['GET'])] — 取代 DocBlock 註解public function __construct(public string $name) {}PHP OOP 已演進為功能完整、型別嚴謹的現代物件系統。掌握「可見性封裝、介面契約、抽象骨架、Trait 橫向複用、魔術方法動態行為」五大支柱,並結合 PHP 8.x 新特性,即可建構可維護、可測試、可擴充的企業級應用。下一步建議深入學習 設計模式、DDD 戰術模式 與 PHPStan 靜態分析,將程式碼品質推向更高層次。