PHP 5 重寫了對象模型,引入了魔術方法、自動加載、命名空間等現代 OOP 特性。本文系統梳理六大核心知識點,配合可運行的代碼示例,幫助開發者深入理解 PHP 對象機制的底層行為與最佳實踐。
通過魔術方法攔截不可訪問(不存在、private、protected)的屬性/方法訪問,實現「虛擬屬性」與「動態方法」。
| 方法 | 觸發時機 | 典型用途 |
|---|---|---|
__set($name, $value) | 寫入不可訪問屬性 | 數據代理、驗證、日誌、延遲加載 |
__get($name) | 讀取不可訪問屬性 | 計算屬性、關聯模型懶加載 |
__isset($name) | isset()/empty() 檢測不可訪問屬性 | 配合 __get 實現完整語義 |
__unset($name) | unset() 不可訪問屬性 | 清理代理數據 |
__call($name, $args) | 調用不可訪問實例方法 | 動態 API、方法委派、流式介面 |
__callStatic($name, $args) | 調用不可訪問靜態方法 | 工廠方法、查詢構建器鏈式調用 |
class DynamicEntity {
private array $data = [];
private array $relations = [];
public function __set(string $name, $value): void {
// 可在此加入驗證、事件觸發
$this->data[$name] = $value;
}
public function __get(string $name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
// 懶加載關聯
if (isset($this->relations[$name])) {
return $this->relations[$name];
}
// 動態計算屬性示例
if ($name === 'full_name') {
return ($this->data['first_name'] ?? '') . ' ' . ($this->data['last_name'] ?? '');
}
return null; // 或拋出異常
}
public function __call(string $name, array $args) {
// 動態查詢方法:findById、findByEmail 等
if (preg_match('/^findBy(.+)$/', $name, $m)) {
$field = lcfirst($m[1]);
return $this->repository->findBy([$field => $args[0]]);
}
throw new BadMethodCallException("Method $name not found");
}
}
$obj = new DynamicEntity();
$obj->email = 'test@example.com'; // 觸發 __set
echo $obj->email; // 觸發 __get
echo $obj->full_name; // 動態計算屬性
$obj->findById(123); // 觸發 __call
#[\AllowDynamicProperties] 或改用 ArrayAccess / __get/__set 統一管理。clone 執行淺拷貝:標量屬性逐個複製,引用類型屬性(對象、資源)仍指向同一實例。若需深拷貝,須在 __clone() 中手動遞歸複製。
class Address {
public string $city;
}
class User {
public string $name;
public Address $address; // 引用類型
public function __clone() {
$this->address = clone $this->address; // 深拷貝關聯對象
}
}
$u1 = new User();
$u1->name = 'Alice';
$u1->address = new Address();
$u1->address->city = 'Beijing';
$u2 = clone $u1;
$u2->name = 'Bob';
$u2->address->city = 'Shanghai';
echo $u1->address->city; // Beijing(深拷貝生效)
echo $u2->address->city; // Shanghai
| 運算符 | 比較邏輯 | 示例結果 |
|---|---|---|
== | 屬性鍵值對完全相等(遞歸比較) | $u1 == $u2 → false(name 不同) |
=== | 同一實例(引用同一對象) | $u1 === $u2 → false;$u1 === $u1 → true |
注意:== 會遞歸比較對象屬性,若存在循環引用會導致無限遞歸錯誤。
| 語句 | 找不到文件 | 重複包含 | 返回值 |
|---|---|---|---|
include | Warning,繼續執行 | 每次都包含 | 包含文件的 return 值 |
include_once | Warning,繼續執行 | 僅包含一次 | 同 include |
require | Fatal Error,腳本終止 | 每次都包含 | 無(終止) |
require_once | Fatal Error,腳本終止 | 僅包含一次 | 無 |
現代建議: 棄用手動 include,全面採用 **Composer PSR-4 自動加載**,由框架/入口文件統一引入 vendor/autoload.php。
__autoload() 已棄用(PHP 7.2+),推薦使用 spl_autoload_register() 支援多加載器鏈。
// 簡易 PSR-4 實現
spl_autoload_register(function (string $class) {
$prefix = 'App\\';
$baseDir = __DIR__ . '/src/';
if (strncmp($class, $prefix, strlen($prefix)) !== 0) return;
$relative = str_replace('\\', '/', substr($class, strlen($prefix)));
$file = $baseDir . $relative . '.php';
if (is_file($file)) require $file;
});
// 使用
$user = new App\Model\User(); // 自動加載 src/Model/User.php
生產環境直接使用 composer dump-autoload -o 生成優化的類映射表,性能最優。
namespace My\Project; 定義命名空間(必須為文件首個非註釋語句)use My\Project\ClassA; 導入類/介面/函數/常量use My\Project\ClassA as Alias; 別名use function My\Project\func; 導入函數use const My\Project\CONST; 導入常量\DateTimeApp 下 Service\User → App\Service\Usernamespace App\Service;
use App\Model\User;
use App\Repository\UserRepository as Repo;
use function App\Helpers\formatDate;
use const App\Config\MAX_ITEMS;
class UserService {
public function getUser(int $id): ?User {
$repo = new Repo();
$user = $repo->find($id);
if ($user) {
$user->formatted_created = formatDate($user->created_at);
}
return $user;
}
}
// 定義插件介面
namespace App\Plugin;
interface PluginInterface {
public function getName(): string;
public function execute(array $context): mixed;
}
// 動態發現並加載插件
class PluginManager {
private array $plugins = [];
public function discover(string $namespace, string $basePath): void {
$files = glob($basePath . '/*Plugin.php');
foreach ($files as $file) {
$class = $namespace . '\\' . basename($file, '.php');
if (class_exists($class) && in_array(PluginInterface::class, class_implements($class))) {
$this->plugins[] = new $class();
}
}
}
public function runAll(array $context): Generator {
foreach ($this->plugins as $plugin) {
yield $plugin->getName() => $plugin->execute($context);
}
}
}
// 插件示例:App\Plugin\LogPlugin.php
namespace App\Plugin;
class LogPlugin implements PluginInterface {
public function getName(): string { return 'Logger'; }
public function execute(array $ctx): void {
error_log('[' . date('c') . '] ' . ($ctx['message'] ?? 'no message'));
}
}
掌握動態屬性/方法、對象複製語義、自動加載機制與命名空間解析規則,是編寫高質量 PHP 現代應用、框架擴展、基礎設施庫的基石。實際開發中:
use 導入保持代碼清晰__clone 中處理引用屬性這些特性共同構成了 PHP 現代 OOP 的骨架,靈活運用將極大提升代碼的可維護性與擴展性。