PHP 物件導向程式設計(OOP)完整指南
PHP 5 引入完整物件模型,PHP 7/8 持續強化效能與語法。OOP 將資料與行為封裝於類別,支援封裝、繼承、多型,是構建大型可維護系統的基石。本文系統整理 PHP OOP 核心概念與實務應用。
核心概念
| 術語 | 說明 |
| 類別 | 物件的藍圖,定義屬性(變數)與方法(函數) |
| 物件 | 類別的實例,擁有獨立狀態 |
| 屬性 | 類別內部變數,可設可視性 |
| 方法 | 類別內部函數,操作物件資料 |
| 建構子 | __construct(),實例化時自動呼叫初始化 |
| 解構子 | __destruct(),物件銷毀前呼叫清理 |
| 繼承 | 子類別重用父類別程式碼,extends |
| 多型 | 相同介面不同實作,執行時決定具體行為 |
| 介面 | 定義契約,implements 強制實作方法 |
| Trait | 程式碼復用單位,use 組合進類別 |
| 抽象類別 | 不可實例化,含抽象方法強制子類別實作 |
類別定義與實例化
<?php
class User {
// 屬性(可視性:public/protected/private)
public string $name;
protected string $email;
private string $passwordHash;
public readonly string $createdAt; // PHP 8.1+ 唯讀屬性
// 建構子(PHP 8.0+ 提升屬性語法)
public function __construct(
public string $username, // 自動建立並賦值屬性
string $email,
string $password
) {
$this->email = $email;
$this->passwordHash = password_hash($password, PASSWORD_DEFAULT);
$this->createdAt = (new DateTime())->format('c');
}
// 方法
public function getEmail(): string {
return $this->email;
}
public function verifyPassword(string $password): bool {
return password_verify($password, $this->passwordHash);
}
// 魔術方法:序列化控制
public function __sleep(): array {
return ['username', 'email', 'createdAt']; // 排除 passwordHash
}
}
// 實例化
$user = new User('john_doe', 'john@example.com', 'secret123');
echo $user->username; // john_doe (public)
echo $user->getEmail(); // john@example.com
// $user->email; // Error: protected
// $user->passwordHash; // Error: private
?>
可視性控制
| 關鍵字 | 類別內部 | 子類別 | 外部 |
| public | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✗ |
| private | ✓ | ✗ | ✗ |
<?php
class ParentClass {
public $pub = 'public';
protected $pro = 'protected';
private $pri = 'private';
public function printAll() {
echo $this->pub . ' ' . $this->pro . ' ' . $this->pri;
}
}
class ChildClass extends ParentClass {
public function testAccess() {
echo $this->pub; // ✓
echo $this->pro; // ✓
// echo $this->pri; // Error
}
}
$obj = new ChildClass();
$obj->pub; // ✓
// $obj->pro; // Error
// $obj->pri; // Error
$obj->printAll(); // public protected private
$obj->testAccess(); // public protected
靜態成員
<?php
class Counter {
private static int $count = 0;
public static string $version = '1.0';
public static function increment(): int {
return ++self::$count;
}
public static function getCount(): int {
return self::$count;
}
}
Counter::increment();
Counter::increment();
echo Counter::getCount(); // 2
echo Counter::$version; // 1.0
// 靜態方法中不可用 $this
// 晚期靜態綁定 (PHP 5.3+)
class A {
public static function who() { echo __CLASS__; }
public static function test() { static::who(); } // 晚期綁定
}
class B extends A {
public static function who() { echo __CLASS__; }
}
B::test(); // B (而非 A)
常數
<?php
class Config {
public const API_URL = 'https://api.example.com';
public const TIMEOUT = 30;
// PHP 8.1+ final 常數不可覆寫
final public const VERSION = '2.0';
}
// 存取
echo Config::API_URL;
// 動態存取 (PHP 5.3+)
$class = 'Config';
echo $class::API_URL;
繼承與方法覆寫
<?php
class Animal {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
public function speak(): string {
return "{$this->name} 發出聲音";
}
final public function getName(): string { // final 禁止覆寫
return $this->name;
}
}
class Dog extends Animal {
public function speak(): string { // 覆寫
return "{$this->name} 汪汪叫";
}
public function fetch(): string {
return "{$this->name} 撿回球";
}
}
$dog = new Dog('旺財');
echo $dog->speak(); // 旺財 汪汪叫
echo $dog->fetch(); // 旺財 撿回球
echo $dog->getName(); // 旺財
抽象類別
<?php
abstract class PaymentGateway {
protected string $merchantId;
public function __construct(string $merchantId) {
$this->merchantId = $merchantId;
}
// 抽象方法:子類別必須實作
abstract public function charge(float $amount, string $currency): bool;
abstract public function refund(string $transactionId): bool;
// 具體方法:子類別可繼承或覆寫
protected function log(string $message): void {
error_log("[{$this->merchantId}] $message");
}
}
class StripeGateway extends PaymentGateway {
public function charge(float $amount, string $currency): bool {
$this->log("Stripe 扣款 $amount $currency");
// API 呼叫...
return true;
}
public function refund(string $transactionId): bool {
$this->log("Stripe 退款 $transactionId");
return true;
}
}
// $gateway = new PaymentGateway(); // Error: 抽象類別不可實例化
$stripe = new StripeGateway('merch_123');
$stripe->charge(100, 'USD');
介面
<?php
interface LoggerInterface {
public function log(string $level, string $message): void;
public function emergency(string $message): void;
}
interface CacheInterface {
public function get(string $key): mixed;
public function set(string $key, mixed $value, int $ttl): bool;
}
// 多重實作
class RedisLogger implements LoggerInterface, CacheInterface {
public function log(string $level, string $message): void {
// Redis 列表儲存日誌
}
public function emergency(string $message): void {
// 立即告警
}
public function get(string $key): mixed { /* ... */ }
public function set(string $key, mixed $value, int $ttl): bool { /* ... */ }
}
// 介面作為型別提示
function processOrder(Order $order, LoggerInterface $logger): void {
$logger->log('info', "處理訂單 {$order->id}");
}
Trait 程式碼復用
<?php
trait Timestampable {
public DateTime $createdAt;
public DateTime $updatedAt;
public function setTimestamps(): void {
$now = new DateTime();
$this->createdAt = $this->createdAt ?? $now;
$this->updatedAt = $now;
}
}
trait SoftDeletes {
public ?DateTime $deletedAt = null;
public function delete(): void {
$this->deletedAt = new DateTime();
}
public function isDeleted(): bool {
return $this->deletedAt !== null;
}
// 衝突解決
public function restore(): void {
$this->deletedAt = null;
}
}
class Article {
use Timestampable, SoftDeletes {
// 解決方法名衝突
SoftDeletes::restore insteadof Timestampable;
Timestampable::setTimestamps as traitSetTimestamps;
}
public string $title;
public string $content;
}
$article = new Article();
$article->title = 'PHP OOP 指南';
$article->setTimestamps();
$article->delete();
echo $article->isDeleted() ? '已刪除' : '正常';
魔術方法
| 方法 | 觸發時機 |
__construct() | new 實例化 |
__destruct() | 物件銷毀/腳本結束 |
__call($name, $args) | 呼叫不可存取方法 |
__callStatic($name, $args) | 靜態呼叫不可存取方法 |
__get($name) | 讀取不可存取屬性 |
__set($name, $value) | 寫入不可存取屬性 |
__isset($name) | isset() 檢查不可存取屬性 |
__unset($name) | unset() 不可存取屬性 |
__sleep() | serialize() |
__wakeup() | unserialize() |
__toString() | 物件作為字串使用 |
__invoke() | 物件作為函數呼叫 |
__clone() | clone 複製物件 |
__debugInfo() | var_dump() |
__serialize() | serialize() (PHP 7.4+) |
__unserialize() | unserialize() (PHP 7.4+) |
型別宣告與嚴格模式
<?php
declare(strict_types=1); // 檔案頂部啟用嚴格模式
class Calculator {
// 參數/回傳型別
public function add(int|float $a, int|float $b): int|float { // PHP 8.0+ 聯合型別
return $a + $b;
}
// 回傳型別聲明
public function getName(): string { /* ... */ }
// void 明確無回傳
public function log(string $msg): void { /* ... */ }
// never 不會返回 (PHP 8.1+)
public function throwError(): never {
throw new Exception('Error');
}
// mixed 任意型別 (PHP 8.0+)
public function process(mixed $data): mixed { /* ... */ }
}
唯讀屬性與類別
<?php
// PHP 8.1+ 唯讀屬性(初始化後不可修改)
class Config {
public function __construct(
public readonly string $apiKey,
public readonly int $timeout = 30
) {}
}
// PHP 8.2+ 唯讀類別(所有屬性隱含 readonly)
readonly class DTO {
public function __construct(
public string $id,
public string $name,
public DateTime $createdAt
) {}
}
列舉
<?php
// PHP 8.1+ 列舉
enum Status: string {
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
public function label(): string {
return match($this) {
self::Draft => '草稿',
self::Published => '已發布',
self::Archived => '已封存',
};
}
}
// 使用
$status = Status::Published;
echo $status->value; // published
echo $status->label(); // 已發布
// 型別提示
function setStatus(Status $status): void { /* ... */ }
最佳實踐原則
- 單一職責:類別只有一個變更理由
- 開放封閉:擴展開放、修改關閉(介面+實作)
- 里氏替換:子類別可替換父類別不破壞行為
- 介面隔離:多個小介面優於單一大介面
- 依賴反轉:依賴抽象(介面)而非具體類別
- 組合優於繼承:Trait、策略模式、裝飾模式替代深層繼承
- 不可變物件:readonly 屬性、DTO、值物件減少狀態錯誤
- 嚴格型別:
declare(strict_types=1) 捕獲型別錯誤
PHP OOP 經歷多版本演進,已具備現代語言完整特性。善用型別系統、可視性控制、介面契約、Trait 組合、魔術方法,配合 SOLID 原則,能構建優雅、可測試、可維護的企業級應用。