首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 全域變數管理最佳實踐:從 global 關鍵字到登記模式(Registry Pattern)

     編輯:PHP 綜合知識

為什麼要避免 global?

global 關鍵字會建立隱性耦合,導致:

  • 難以重用: 函式依賴特定全域變數,無法在不同環境移植。
  • 難以除錯: 變數可能在任意處被修改,追蹤極其困難。
  • 難以測試: 單元測試需模擬全域狀態,污染測試隔離性。
  • 難以維護: 專案增大時,全域變數數量失控,代碼變成「義大利麵」。

方案演進對比

1. 函式參數傳遞(簡單情況)

<?php
$db = new PDO($dsn, $user, $pass);
$settings = new Settings();
$user = new User();

function process($db, $settings, $user) { /* ... */ }
process($db, $settings, $user);

✅ 明確依賴、易測試。❌ 參數過多時呼叫繁瑣、新增依賴需改所有簽名。

2. 單例模式

<?php
class DBConnection {
    private static $instance;
    private function __construct() {}
    
    public static function getInstance(): self {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

// 使用
$db = DBConnection::getInstance();

✅ 全域存取點單一。❌ 隱性依賴、難以 Mock、無法多實例、違反單一職責。

3. 登記模式 —— 推薦方案

中央容器管理所有共享物件,依賴明確、支援多實例、易測試、易擴展。

<?php
/**
 * 登記器:中央物件容器
 */
class Registry {
    private static $instance;
    private $objects = [];
    
    private function __construct() {}
    
    public static function getInstance(): self {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function set(string $name, $object): void {
        $this->objects[$name] = $object;
    }
    
    public function get(string $name) {
        return $this->objects[$name] ?? null;
    }
    
    public function has(string $name): bool {
        return isset($this->objects[$name]);
    }
    
    public function remove(string $name): void {
        unset($this->objects[$name]);
    }
}

// === 應用啟動時註冊 ===
$registry = Registry::getInstance();
$registry->set('db', new PDO($dsn, $user, $pass));
$registry->set('config', new Config('config.ini'));
$registry->set('user', User::fromSession());
$registry->set('logger', new Logger('/var/log/app.log'));

// === 任何處取用 ===
function handleRequest() {
    $registry = Registry::getInstance();
    
    $db = $registry->get('db');
    $user = $registry->get('user');
    $logger = $registry->get('logger');
    
    $logger->info('Processing request for ' . $user->name);
    // ...
}

4. 進階:PSR-11 Container 介面(現代框架標準)

<?php
// 使用 league/container 或 php-di/php-di
require 'vendor/autoload.php';

use League\Container\Container;

$container = new Container();

// 註冊共享實例
$container->addShared('db', function() {
    return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
});
$container->addShared('config', function() {
    return parse_ini_file('config.ini', true);
});

// 註冊工廠(每次新實例)
$container->add('form_validator', function() {
    return new FormValidator();
});

// 取用
$db = $container->get('db');
$validator = $container->get('form_validator');

請求封裝器:統一處理 $_GET/$_POST/$_COOKIE

<?php
class Request {
    private $data;
    
    public function __construct() {
        $this->data = [
            'get'    => $_GET,
            'post'   => $_POST,
            'cookie' => $_COOKIE,
            'server' => $_SERVER,
            'files'  => $_FILES,
        ];
    }
    
    public function get(string $key, $default = null, string $source = 'post') {
        $source = strtolower($source);
        return $this->data[$source][$key] ?? $default;
    }
    
    public function all(string $source = 'post'): array {
        return $this->data[strtolower($source)] ?? [];
    }
    
    public function input(string $key, $default = null) {
        // 依序檢查 POST > GET > COOKIE
        foreach (['post', 'get', 'cookie'] as $source) {
            if (isset($this->data[$source][$key])) {
                return $this->data[$source][$key];
            }
        }
        return $default;
    }
}

// 註冊到 Registry
Registry::getInstance()->set('request', new Request());

// 使用
$request = Registry::getInstance()->get('request');
$name = $request->input('name'); // 自動從 POST/GET/COOKIE 取值
$email = $request->get('email', '', 'post'); // 強制從 POST 取

現代框架整合建議

框架容器實作取用方式
LaravelIlluminate\Containerapp('db'), resolve(User::class)
SymfonyDependencyInjection服務自動注入、$this->get('service')
CodeIgniter 4CodeIgniter\Servicesservice('db'), model('UserModel')
Yii 2yii\di\ContainerYii::$container->get()
Slim 4PHP-DI / League\Container建構子注入、$container->get()

總結:最佳實踐檢查清單

  1. ☑ 禁用 global 關鍵字(除非極少數 legacy 維護)。
  2. ☑ 使用容器/登記器 管理共享服務(DB、Config、Logger、Auth)。
  3. ☑ 依賴注入(DI) 取代服務定位器:建構子注入 > 方法注入 > 容器拉取。
  4. ☑ 請求資料封裝 為 Request 物件,避免直接用超級全域變數。
  5. ☑ 設定檔集中化,環境區分(.env.local/.env.prod)。
  6. ☑ 單元測試 時用 Mock 物件取代容器內真實服務。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved