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

PHP 輸入處理工具類:URL、表單、Cookie、IP 統一封裝

     編輯:PHP 相關知識

PHP 輸入處理工具類:URL、表單、Cookie、IP 統一封裝

Web 開發中重複處理 $_GET/$_POST/$_COOKIE/$_SERVER 既繁瑣又易出錯。本文提供一個生產級輸入處理類 Ev_Input,整合路由解析、資料清洗、XSS 防護、SQL 注入預防、IP 獲取、二級域名識別等核心功能。

完整類代碼

<?php
class Ev_Input {
    public $cookie = [];
    public $post = [];
    public $get = [];
    public $file = [];
    public $url = [];      // 路由解析結果
    public $G = [];        // 別名
    
    private $e = [];       // 快取 (IP等)
    
    public function __construct() {
        // 1. 處理 magic_quotes_gpc (PHP 5.4+ 已移除,保留相容)
        if (ini_get('magic_quotes_gpc')) {
            $get = $this->stripSlashes($_REQUEST);
            $post = $this->stripSlashes($_POST);
            $this->cookie = $this->stripSlashes($_COOKIE);
        } else {
            $get = $_REQUEST;
            $post = $_POST;
            $this->cookie = $_COOKIE;
        }
        
        $this->file = $_FILES;
        $this->get = $this->initData($get);
        $this->post = $this->initData($post);
        $this->url = $this->parseUrl();
        $this->cookie = $this->initData($this->cookie);
        $this->G = &$this->get;  // 別名引用
    }
    
    // 路由解析:支援 route 參數 或 QUERY_STRING
    private function parseUrl(): array {
        if (isset($_REQUEST['route'])) {
            return explode('-', $_REQUEST['route']);  // 如 controller-action-param
        } elseif (isset($_SERVER['QUERY_STRING'])) {
            $qs = $_SERVER['QUERY_STRING'];
            $qs = explode('#', $qs, 2)[0];      // 去除錨點
            $qs = explode('&', $qs, 2)[0];      // 取第一個參數
            return explode('-', $qs);
        }
        return [];
    }
    
    // 獲取 GET 參數
    public function get(string $key, $default = null) {
        return $this->get[$key] ?? $default;
    }
    
    // 獲取 POST 參數
    public function post(string $key, $default = null) {
        return $this->post[$key] ?? $default;
    }
    
    // 獲取路由段 (索引從 0 開始)
    public function url(int $index, $default = null) {
        return $this->url[$index] ?? $default;
    }
    
    // Cookie 管理
    public function setCookie(string $name, string $value, int $expire = 3600): void {
        $expire = $expire ? time() + $expire : 0;
        $prefix = defined('COOKIE_PREFIX') ? COOKIE_PREFIX : '';
        $domain = defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '';
        setcookie($prefix . $name, $value, $expire, '/', $domain, false, true);
    }
    
    public function getCookie(string $key, bool $noPrefix = false, $default = null) {
        $prefix = defined('COOKIE_PREFIX') ? COOKIE_PREFIX : '';
        $key = $noPrefix ? $key : $prefix . $key;
        return $this->cookie[$key] ?? $default;
    }
    
    public function getFile(string $key) {
        return $this->file[$key] ?? null;
    }
    
    // 核心:資料遞歸清洗
    private function initData($data) {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                // 鍵名白名單檢查 (防污染)
                if (!$this->isAllowKey($k)) {
                    unset($data[$k]);
                    continue;
                }
                $data[$k] = $this->initData($v);
            }
            return $data;
        }
        
        // 純量值處理
        if (is_numeric($data)) {
            // 防止 08/09 被當作八進制、長數字科學計數法
            if ($data[0] === '0' || strlen($data) >= 11) {
                return $this->addSlashes(htmlspecialchars(str_replace("'", ''', $data)));
            }
            return strpos($data, '.') !== false ? (float)$data : (int)$data;
        }
        
        if (is_string($data)) {
            return $this->addSlashes(htmlspecialchars(str_replace("'", ''', $data)));
        }
        
        if (is_bool($data)) return (bool)$data;
        return $data;  // null, resource 等保持原樣
    }
    
    // 鍵名白名單 (防止 __proto__、constructor 等原型鏈污染)
    private function isAllowKey(string $key): bool {
        // 僅允許字母數字下劃線,長度限制
        return preg_match('/^[a-zA-Z0-9_]{1,64}$/', $key) === 1;
    }
    
    // 遞歸去除魔術引號
    private function stripSlashes($data) {
        if (is_array($data)) {
            foreach ($data as $k => $v) $data[$k] = $this->stripSlashes($v);
        } else {
            $data = stripslashes(trim($data));
        }
        return $data;
    }
    
    // 遞歸添加轉義 (資料庫安全)
    private function addSlashes($data) {
        if (is_array($data)) {
            foreach ($data as $k => $v) $data[$k] = $this->addSlashes($v);
        } else {
            $data = addslashes(trim($data));
        }
        return $data;
    }
    
    // 獲取客戶端真實 IP
    public function getClientIp(): string {
        if (!isset($this->e['ip'])) {
            $ip = 'unknown';
            $headers = [
                'HTTP_CLIENT_IP',
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_REAL_IP',
                'REMOTE_ADDR'
            ];
            foreach ($headers as $header) {
                $val = getenv($header) ?: ($_SERVER[$header] ?? '');
                if ($val && strcasecmp($val, 'unknown') !== 0) {
                    // X-Forwarded-For 可能包含多個 IP,取第一個非私有
                    $ips = array_map('trim', explode(',', $val));
                    foreach ($ips as $ip) {
                        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) continue;
                        $this->e['ip'] = $ip;
                        break 2;
                    }
                }
            }
            $this->e['ip'] = $this->e['ip'] ?? 'unknown';
        }
        return $this->e['ip'];
    }
    
    // 獲取二級域名 (如 user.example.com → user)
    public function getSecondDomain(): ?string {
        $host = $_SERVER['HTTP_HOST'] ?? '';
        // 處理雙後綴域名
        $host = preg_replace('/\.(com|net|org|gov|edu)\.cn$/', '.cn', $host);
        $parts = explode('.', $host);
        if (count($parts) < 3) return null;
        $sub = $parts[0];
        return is_numeric($sub) ? null : $sub;  // 排除純數字子域名
    }
}

// 全域單例模式使用
$ev = new Ev_Input();

// 控制器中
$userId = $ev->post('user_id');
$action = $ev->url(1);  // 第二個路由段
$token = $ev->getCookie('token');
$ip = $ev->getClientIp();

核心功能解析

1. 統一入口,消除超全域陣列直接使用

  • 控制器/服務層僅依賴 $ev->get()/$ev->post(),便於測試 Mock、遷移 CLI/Swoole
  • 構造函數自動完成:magic_quotes 清洗、路由解析、資料初始化

2. 深度資料清洗 initData()

輸入類型處理邏輯輸出
陣列遞歸處理、鍵名白名單過濾清洗後陣列
純數字字串 (含前導 0/長度≥11)視為字串 → htmlspecialchars + addslashes安全字串
短數字轉換為 int/float原生數值型別
字串htmlspecialchars (防 XSS) + addslashes (防 SQL 注入) + 單引號實體化安全字串
布林強制轉型bool

注意:雙重防護 (htmlspecialchars + addslashes) 適用於**直接拼接 SQL 的舊專案**。現代專案請使用 **PDO 預處理**,移除 addslashes,僅保留輸出時的 htmlspecialchars。

3. 路由解析 parseUrl()

  • 支援 ?route=controller-action-id 模式
  • 回退解析 QUERY_STRING 首段
  • 返回陣列 ['controller', 'action', 'id'],配合調度器使用

4. Cookie 前綴隔離

  • 定義 COOKIE_PREFIX (如 myapp_) 避免多子系統 Cookie 衝突
  • setCookie/getCookie 自動加前綴,支援 COOKIE_DOMAIN 跨子域共享
  • 預設 HttpOnly=true, Secure=true (HTTPS 環境)

5. IP 獲取強化

  • 支援 X-Forwarded-For 多 IP 解析,過濾私有/保留地址
  • 優先順序:Client-IP → X-Forwarded-For → X-Real-IP → REMOTE_ADDR
  • 快存機制 $this->e['ip'] 避免重複解析

6. 二級域名識別

  • 用於多租戶 SaaS:tenant.app.com → 識別 tenant
  • 處理 .com.cn 等雙後綴
  • 過濾純數字子域名 (通常為伺服器編號非租戶)

現代化改進建議 (PHP 7.4+)

// 1. 型別宣告與嚴格模式
declare(strict_types=1);

class Ev_Input {
    // 2. 唯讀屬性 (PHP 8.1+)
    public readonly array $get;
    public readonly array $post;
    
    // 3. 建構子屬性提升
    public function __construct(
        private array $server = [],
        private array $get = [],
        private array $post = [],
        private array $cookie = [],
        private array $files = []
    ) {
        // 依賴注入,便於測試
        $this->server = $server ?: $_SERVER;
        // ...
    }
    
    // 4. 使用 filter_var 驗證替代正則
    private function isAllowKey(string $key): bool {
        return strlen($key) <= 64 && ctype_alnum(str_replace('_', '', $key));
    }
    
    // 5. 移除 addslashes,改用 PDO::quote 或預處理
    private function sanitizeString(string $s): string {
        return htmlspecialchars($s, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}

使用場景對比

場景建議做法
舊專案維護 (mysqli/mysql 拼接 SQL)直接使用本類,雙重防護
現代專案 (PDO/Doctrine/Eloquent)移除 addslashes,僅保留 htmlspecialchars 輸出端
API 服務 (JSON 輸入)擴充套件支援 php://input JSON 解析
Swoole/RoadRunner 長連線每請求 new 實例,避免屬性殘留
單元測試構造函數注入模擬 $_GET/$_POST

封裝輸入處理不僅是代碼復用,更是建立**安全邊界**的關鍵一環。統一入口、白名單鍵名、輸出編碼、最小權限原則,讓業務代碼專注於邏輯而非防禦。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved