封裝性:資訊隱藏的核心
將屬性與方法包裹在類內部,對外僅暴露必要介面。三大訪問修飾符:
| 修飾符 | 類內部 | 子類 | 外部 | 用途 |
public | ✅ | ✅ | ✅ | 公開 API |
protected | ✅ | ✅ | ❌ | 內部/繼承協作 |
private | ✅ | ❌ | ❌ | 核心邏輯隔離 |
四大屬性魔術方法 (Property Overloading)
當訪問不可見屬性 (不存在、或權限不足) 時自動觸發:
| 方法 | 觸發時機 | 簽名 | 典型用途 |
__get | 讀取不可見屬性 | __get(string $name): mixed | 延遲載入、代理、計算屬性 |
__set | 寫入不可見屬性 | __set(string $name, mixed $value): void | 驗證、日誌、觸發事件 |
__isset | isset()/empty() 檢查不可見屬性 | __isset(string $name): bool | 虛擬屬性存在性判斷 |
__unset | unset() 不可見屬性 | __unset(string $name): void | 清理快取、解除綁定 |
實戰範例:動態屬性代理
<?php
class User {
private array $data = [];
private array $loaded = [];
public function __get(string $name) {
if (isset($this->data[$name])) return $this->data[$name];
// 延遲從資料庫載入
if (in_array($name, ['posts', 'comments', 'profile'])) {
$this->data[$name] = $this->loadRelation($name);
return $this->data[$name];
}
throw new Exception("屬性不存在: $name");
}
public function __set(string $name, mixed $value) {
if ($name === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new Exception("無效 Email");
}
$this->data[$name] = $value;
$this->markDirty($name);
}
public function __isset(string $name): bool {
return isset($this->data[$name]) || in_array($name, ['posts', 'comments', 'profile']);
}
public function __unset(string $name) {
unset($this->data[$name]);
$this->markDirty($name);
}
private function loadRelation(string $name): array {
// 模擬 DB 查詢
return ["loaded_$name" => true];
}
private function markDirty(string $name) { /* ... */ }
}
$user = new User();
$user->email = 'test@example.com'; // __set 驗證
echo $user->posts; // __get 延遲載入
var_dump(isset($user->profile)); // __isset
unset($user->email); // __unset
繼承:extends 關鍵字
<?php
class Animal {
protected string $name;
public function __construct(string $name) { $this->name = $name; }
public function speak(): string { return "{$this->name} 發出聲音"; }
}
class Dog extends Animal {
public function speak(): string { // 方法覆寫
return parent::speak() . " - 汪汪!"; // 呼叫父類方法
}
public function fetch(): string { return "{$this->name} 撿回球"; }
}
$dog = new Dog('旺財');
echo $dog->speak(); // 旺財 發出聲音 - 汪汪!
echo $dog->fetch(); // 旺財 撿回球
繼承規則
- 子類繼承父類所有
public/protected 成員
private 成員不繼承 (子類不可見)
- 建構子不繼承,子類需自行定義並呼叫
parent::__construct()
- PHP 不支援多重繼承,使用
trait 組合行為
方法覆寫與 final 關鍵字
<?php
class BaseService {
final public function execute(): void { // 禁止覆寫
$this->before();
$this->doExecute();
$this->after();
}
protected function before(): void { echo "開始\n"; }
protected function after(): void { echo "結束\n"; }
// 子類必須實現
abstract protected function doExecute(): void;
}
class UserService extends BaseService {
protected function doExecute(): void { echo "處理用戶邏輯\n"; }
// 不能覆寫 execute()
}
static 靜態成員:類層級共享
| 特性 | 說明 |
| 儲存位置 | 類記憶體區,非物件實例 |
| 訪問方式 | ClassName::$prop、ClassName::method()、self::$prop、static::$prop (晚期靜態綁定) |
| 共享特性 | 所有實例共享同一份值 |
| 限制 | 靜態方法中 不可用 $this、self:: 僅限當前類、static:: 支援繼承多態 |
晚期靜態綁定 (LSB) 示例
<?php
class Model {
protected static string $table = 'base';
public static function getTable(): string {
return static::$table; // LSB: 取得實際調用類的屬性
}
}
class User extends Model {
protected static string $table = 'users';
}
echo Model::getTable(); // base
echo User::getTable(); // users (非 self::$table 會輸出 base)
const 類常數:不可變值
- 聲明時必須初始化,不可在建構子修改
- 不需要
$ 符號
- 訪問:
ClassName::CONST_NAME、self::CONST_NAME
- 介面也可定義常數
- PHP 8.1+ 支援
final const 禁止子類覆寫
<?php
class Config {
public const APP_NAME = 'MyApp';
public const VERSION = '1.0.0';
protected const INTERNAL_KEY = 'secret';
public static function getKey(): string {
return self::INTERNAL_KEY;
}
}
echo Config::APP_NAME; // MyApp
完整實戰範例:結合所有特性
<?php
abstract class Repository {
// 靜態快取,所有實例共享
private static array $cache = [];
// 常數配置
protected const CACHE_TTL = 300;
// 受保護屬性,子類可見
protected PDO $db;
// 私有屬性,僅本類可見
private string $table;
public function __construct(PDO $db, string $table) {
$this->db = $db;
$this->table = $table;
}
// final 公開 API,禁止覆寫
final public function find(int $id): ?array {
$key = "{$this->table}:$id";
// __isset 魔術方法檢查快取
if (isset(self::$cache[$key])) {
return self::$cache[$key];
}
$data = $this->doFind($id);
if ($data) self::$cache[$key] = $data;
return $data;
}
// 抽象方法,子類必須實現
abstract protected function doFind(int $id): ?array;
// 靜態工廠方法
public static function clearCache(): void {
self::$cache = [];
}
}
class UserRepository extends Repository {
public function __construct(PDO $db) {
parent::__construct($db, 'users');
}
protected function doFind(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}
}
// 使用
$repo = new UserRepository($pdo);
$user = $repo->find(1); // 含快取邏輯
UserRepository::clearCache();
最佳實踐總結
- 預設 private:只在必要時放寬到 protected/public
- 組合優於繼承:用 trait/interface 組合行為,避免深層繼承樹
- final 預設:類和關鍵方法加 final,明確開放擴展點
- static 謹慎用:引入全域狀態,難測試、難並發,優先用實例屬性 + DI
- const 替代 define:命名空間隔離、IDE 支援、型別提示
- 魔術方法慎用:增加隱性行為,降低可讀性,文檔說明清楚