註冊器模式提供全局對象存取中心,解決「跨層級、跨模塊共享對象實例」的問題。結合單例模式確保全進程唯一容器,配合 ArrayObject 實現原生數組式訪問體驗。本文從核心實現、使用場景、擴展方向全方位講解。
<?php
/**
* 註冊器:全局對象容器 (單例 + ArrayObject)
*/
class Registry extends ArrayObject {
/** @var Registry|null 單例實例 */
private static $_instance = null;
/** 獲取單例實例 */
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
// echo "new register object!"; // 調試用
}
return self::$_instance;
}
/** 設置值:Registry::set('key', $value) */
public static function set($name, $value) {
self::getInstance()->offsetSet($name, $value);
}
/** 獲取值:$value = Registry::get('key') */
public static function get($name) {
$instance = self::getInstance();
if (!$instance->offsetExists($name)) return null;
return $instance->offsetGet($name);
}
/** 檢查是否存在:Registry::isRegistered('key') */
public static function isRegistered($name) {
return self::getInstance()->offsetExists($name);
}
/** 刪除:Registry::remove('key') */
public static function remove($name) {
self::getInstance()->offsetUnset($name);
}
// 繼承 ArrayObject 後,實例亦可直接用數組語法操作 (但建議統一靜態調用)
// $reg = Registry::getInstance();
// $reg['key'] = $val; // offsetSet
// $val = $reg['key']; // offsetGet
// isset($reg['key']); // offsetExists
// unset($reg['key']); // offsetUnset
}
<?php
// Test.class.php
class Test {
public function hello() {
echo "Hello World";
}
}
<?php
require_once "Registry.class.php";
require_once "Test.class.php";
// 1. 實例化業務對象
$test = new Test();
// 2. 註冊到容器 (鍵: 'testclass')
Registry::set('testclass', $test);
// 3. 任意位置獲取並使用
$t = Registry::get('testclass');
$t->hello(); // 輸出: Hello World
// 4. 其他操作
var_dump(Registry::isRegistered('testclass')); // true
Registry::remove('testclass');
var_dump(Registry::get('testclass')); // null
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Registry (註冊器) | 統一管理、延遲加載、運行時動態註冊、可攔截/日志/代理 | 隱式依賴、難測試、全局狀態污染 | Legacy 遷移、插件系統、第三方庫實例共享、臨時原型開發 |
| 全局變量 $GLOBALS | 極簡 | 命名衝突、無封裝、極難維護 | 極小腳本/臨時調試 |
| 靜態類屬性 | 類級封裝 | 耦合死板、難擴展、單測痛苦 | 工具類/配置類 |
| 依賴注入 (DI 容器) | 顯式依賴、易測試、生命週期管理、AOP 支持 | 引入框架複雜度、學習曲線 | 現代應用首選 (Laravel/Symfony/ThinkPHP 內置) |
class Registry extends ArrayObject {
private static $_factories = []; // 存儲工廠閉包
public static function bind($name, callable $factory) {
self::$_factories[$name] = $factory;
}
public static function get($name) {
$instance = self::getInstance();
if ($instance->offsetExists($name)) return $instance->offsetGet($name);
// 自動實例化
if (isset(self::$_factories[$name])) {
$obj = self::$_factories[$name]();
$instance->offsetSet($name, $obj);
return $obj;
}
return null;
}
}
// 使用:綁定工廠,首次 get 時才創建
Registry::bind('db', function() {
return new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
});
$db = Registry::get('db'); // 此處才真正連接數據庫
public static function set($namespace, $name, $value) {
self::getInstance()->offsetSet($namespace . ':' . $name, $value);
}
public static function get($namespace, $name) {
return self::getInstance()->offsetGet($namespace . ':' . $name);
}
// 使用
Registry::set('service', 'user', new UserService());
Registry::set('service', 'order', new OrderService());
$userSvc = Registry::get('service', 'user');
private static $_frozen = [];
public static function freeze($name) {
self::$_frozen[$name] = true;
}
public static function set($name, $value) {
if (isset(self::$_frozen[$name])) {
throw new Exception("Registry key '$name' is frozen, cannot overwrite.");
}
self::getInstance()->offsetSet($name, $value);
}
use Psr\Container\ContainerInterface;
class Registry extends ArrayObject implements ContainerInterface {
public function get($id) {
if (!$this->offsetExists($id)) throw new NotFoundExceptionInterface($id);
return $this->offsetGet($id);
}
public function has($id) {
return $this->offsetExists($id);
}
// offsetSet/offsetGet/offsetExists/offsetUnset 由 ArrayObject 提供
}
module.service 格式 (如 db.connection, cache.redis),避免衝突Registry::remove() 或重置單例,防止測試間污染註冊器模式 = 單例容器 + ArrayObject 數組訪問 + 靜態門面。它以極低複雜度解決全局對象共享問題,但引入隱式依賴,降低可測試性。現代 PHP 開發中,依賴注入容器是正規軍,註冊器是游擊隊——理解其原理,在合適場景 (插件系統、第三方庫適配、快速原型) 輕量級使用,其餘場景堅持 DI。