註冊模式 提供一個集中儲存、查找物件實例的機制,避免在層層函數間傳遞依賴 (Tramp Data)。常與單例模式結合,成為應用程式的「服務定位器」。
<?php
class Registry {
private static ?self $instance = null;
private array $store = [];
// 單例模式:防止直接實例化
private function __construct() {}
private function __clone() {}
public static function getInstance(): self {
return self::$instance ??= new self();
}
// 核心操作
public function set(string $key, mixed $value): void {
$this->store[$key] = $value;
}
public function get(string $key): mixed {
return $this->store[$key] ?? null;
}
public function has(string $key): bool {
return isset($this->store[$key]);
}
public function remove(string $key): void {
unset($this->store[$key]);
}
public function clear(): void {
$this->store = [];
}
// 批次操作
public function setMultiple(array $values): void {
foreach ($values as $key => $value) {
$this->set($key, $value);
}
}
public function getMultiple(array $keys): array {
return array_intersect_key($this->store, array_flip($keys));
}
}
?>
<?php
// 啟動時註冊核心服務
$registry = Registry::getInstance();
// 1. 資料庫連線 (單例)
$registry->set('db', new PDO('mysql:host=localhost;dbname=app', 'user', 'pass'));
// 2. 配置物件
$registry->set('config', [
'app_name' => 'MyApp',
'debug' => true,
'cache_ttl' => 3600,
]);
// 3. 快取服務
$registry->set('cache', new RedisCache(new Redis()));
// 4. 日誌服務
$registry->set('logger', new FileLogger('/var/log/app.log'));
// 任意處存取
function getUser(int $id): ?User {
$db = Registry::getInstance()->get('db');
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetchObject(User::class) ?: null;
}
// 控制器中使用
$logger = Registry::getInstance()->get('logger');
$logger->info('User accessed: ' . $_SERVER['REQUEST_URI']);
?>
<?php
class TypedRegistry {
private array $store = [];
/** @var array */
private array $typeMap = [];
public function register(string $key, mixed $value, string $expectedType): void {
if (!$value instanceof $expectedType && gettype($value) !== $expectedType) {
throw new TypeError("Value for '$key' must be $expectedType, " . gettype($value) . " given");
}
$this->store[$key] = $value;
$this->typeMap[$key] = $expectedType;
}
/** @template T
* @param class-string $expectedType
* @return T|null
*/
public function get(string $key, string $expectedType): mixed {
if (!isset($this->store[$key])) return null;
$value = $this->store[$key];
if (!$value instanceof $expectedType && gettype($value) !== $expectedType) {
throw new TypeError("Stored value for '$key' is not $expectedType");
}
return $value;
}
public function has(string $key): bool {
return isset($this->store[$key]);
}
}
// 使用
$registry = new TypedRegistry();
$registry->register('db', new PDO(...), PDO::class);
$registry->register('config', ['debug'=>true], 'array');
$db = $registry->get('db', PDO::class); // IDE 自動完成、靜態分析通過
$config = $registry->get('config', 'array');
?>
| 特性 | 註冊模式 | DI 容器 |
|---|---|---|
| 物件建立 | 手動建立並註冊 | 容器自動建立 (反射/定義) |
| 依賴解析 | 手動 get() | 自動遞迴注入 |
| 生命週期管理 | 手控 (單例/原型) | 內建 (singleton, prototype, request...) |
| 型別安全 | 需自行實作 | 現代容器支援 (PHP-DI, Symfony DI) |
| 測試友善 | 易於替換 (set mock) | 支援覆寫、Mock 定義 |
| 複雜度 | 低 (幾十行代碼) | 高 (需學習容器配置) |
| 適用專案 | 中小型、舊專案遷移 | 中大型、新專案、微服務 |
<?php
// 測試前置
class TestCase extends PHPUnit\Framework\TestCase {
protected function setUp(): void {
// 重置註冊器
$registry = Registry::getInstance();
$registry->clear();
// 註冊 Mock 服務
$mockDb = $this->createMock(PDO::class);
$registry->set('db', $mockDb);
$mockLogger = $this->createMock(LoggerInterface::class);
$registry->set('logger', $mockLogger);
}
}
// 整合測試
public function testUserRegistration() {
$registry = Registry::getInstance();
$registry->set('email_service', new MockEmailService());
$controller = new UserController();
$controller->register('test@example.com', 'password');
// 驗證...
}
?>