「在 Windows 開發、Linux 部署」是 PHP 專案常態。若程式碼到處硬編碼相對路徑(../config.php、./lib/db.php),一旦目錄結構調整、檔案移動、或跨平台部署,立刻爆出 failed to open stream。本文說明為何應統一使用絕對路徑常數,並提供一套可直接落地的初始化架構。
| 路徑類型 | 定義 | 風險 |
|---|---|---|
| 相對路徑 | 相對「目前執行腳本」或「include_path」 | 隨著入口檔案位置、include 深度改變而失效 |
| 絕對路徑 | 相對檔案系統根目錄(/ 或 C:\) | 固定不變,跨平台、跨目錄皆可用 |
__FILE__:目前檔案的完絕對路徑(含檔名),符號連結已解析。__DIR__ (PHP 5.3+):dirname(__FILE__) 的簡寫,取得目前檔案所在目錄。DIRECTORY_SEPARATOR:系統目錄分隔符(Windows \、Linux/Unix /),常縮寫為常數 DS。建立 initialize.php 放在專案根目錄,所有入口(index.php、api.php、cli.php、cron.php)第一行只需 require_once __DIR__ . '/initialize.php';。
<?php
// initialize.php
// 1. 定義核心路徑常數(絕對路徑)
defined('DS') ? null : define('DS', DIRECTORY_SEPARATOR);
defined('SITE_ROOT') ? null : define('SITE_ROOT', __DIR__); // 專案根目錄
defined('LIB_PATH') ? null : define('LIB_PATH', SITE_ROOT . DS . 'includes');
defined('CONFIG_PATH')? null : define('CONFIG_PATH', SITE_ROOT . DS . 'config');
defined('PUBLIC_PATH')? null : define('PUBLIC_PATH', SITE_ROOT . DS . 'public');
defined('VENDOR_PATH')? null : define('VENDOR_PATH', SITE_ROOT . DS . 'vendor');
// 2. 載入 Composer 自動載入(若使用)
if (file_exists(VENDOR_PATH . DS . 'autoload.php')) {
require_once VENDOR_PATH . DS . 'autoload.php';
}
// 3. 載入核心設定檔
require_once CONFIG_PATH . DS . 'config.php'; // 資料庫、快取、第三方金鑰等
require_once CONFIG_PATH . DS . 'database.php'; // 可選分離
// 4. 載入核心函式庫
require_once LIB_PATH . DS . 'functions.php'; // 專案通用 helper
// 5. 初始化核心服務(依賴順序:Session → DB → Model/Service)
require_once LIB_PATH . DS . 'session.php';
require_once LIB_PATH . DS . 'database.php'; // 建立 PDO/mysqli 連線
// 6. 載入領域模型/服務類別(可改用自動載入)
require_once LIB_PATH . DS . 'user.php';
require_once LIB_PATH . DS . 'order.php';
// ...
<?php // public/index.php (Web 入口) require_once __DIR__ . '/../initialize.php'; // 這裡開始寫業務邏輯,所有 require 皆用常數 require_once LIB_PATH . DS . 'controller' . DS . 'UserController.php'; $ctrl = new UserController(); $ctrl->handleRequest();
<?php // cli/cron_job.php (CLI 入口) require_once __DIR__ . '/../initialize.php'; // CLI 專用邏輯 require_once LIB_PATH . DS . 'task' . DS . 'SyncOrderTask.php'; $task = new SyncOrderTask(); $task->run();
composer.json 設定自動載入:
{
"autoload": {
"psr-4": {
"App\\": "includes/",
"Config\\": "config/"
}
}
}
執行 composer dump-autoload 後,initialize.php 僅需:
require_once VENDOR_PATH . DS . 'autoload.php'; require_once CONFIG_PATH . DS . 'config.php'; // 其餘類別自動載入,零 require
| 錯誤寫法 | 問題 | 正確寫法 |
|---|---|---|
require '../config.php'; | 相對路徑,移動檔案即失效 | require CONFIG_PATH . DS . 'config.php'; |
require dirname(__FILE__) . '/../lib/db.php'; | 硬編碼層級,重構時易錯 | require LIB_PATH . DS . 'db.php'; |
define('ROOT', $_SERVER['DOCUMENT_ROOT']); | 僅限 Web 環境,CLI 下為空 | define('SITE_ROOT', __DIR__); (在根目錄檔案中) |
'/var/www/html/lib/' | 硬編碼絕對路徑,無法跨機器部署 | 使用 SITE_ROOT 衍生 |
__DIR__ 衍生所有路徑常數。initialize.php 統一定義、載入順序、環境差異。DIRECTORY_SEPARATOR 吸收 /、\ 差異。require_once 攻城獅。遵循此架構,專案可在 Windows 開發、Linux 容器、Serverless 函式、多入口(Web/CLI/Queue)間零修改遷移,大幅降低部署風險與維護成本。