不要建立多個獨立 PHP 檔案 (home.php, about.php, contact.php),改用:
// index.php
$page = $_GET['page'] ?? 'home';
$allowed = ['home', 'about', 'contact', 'products'];
if (!in_array($page, $allowed)) $page = 'home';
include "pages/{$page}.php";
優點:集中控制、易於權限檢查、共用配置/資料庫連線、利於重構。
$action = $_REQUEST['action'] ?? 'default';
// 取代分開寫 $_GET['action'] 和 $_POST['action']
注意:$_REQUEST 合併順序受 request_order 影響 (預設 GP),Cookie 會覆蓋 GET/POST,安全敏感操作請明確指定來源。
<pre><?php var_dump($variable); ?></pre>
// 或封裝
function dd($var) { echo '<pre>'; var_dump($var); echo '</pre>'; die; }
配合 Xdebug 遠端除錯更強大 (IDE 斷點、變數觀察、堆疊追蹤)。
include 'view.php' 並在視圖中只用 echo $var核心原則:控制器準備資料,視圖只負責顯示。
// config.php
return [
'db' => ['host'=>'localhost', 'name'=>'app', 'user'=>'root', 'pass'=>'secret'],
'app' => ['debug'=>true, 'timezone'=>'Asia/Taipei', 'url'=>'https://example.com'],
'cache' => ['driver'=>'redis', 'host'=>'127.0.0.1', 'port'=>6379],
];
// 使用
$config = require 'config.php';
$pdo = new PDO("mysql:host={$config['db']['host']};dbname={$config['db']['name']}", $config['db']['user'], $config['db']['pass']);
避免在程式碼中硬編碼、到處散落 global $db。
// index.php (入口)
define('APP_STARTED', true);
// pages/contact.php (內部檔案)
if (!defined('APP_STARTED')) {
http_response_code(403);
exit('Direct access forbidden');
}
保護邏輯檔案、類別檔案不被直接造訪。
class Database {
private $pdo;
public function __construct(array $cfg) {
$this->pdo = new PDO("mysql:host={$cfg['host']};dbname={$cfg['name']};charset=utf8mb4", $cfg['user'], $cfg['pass']);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function query(string $sql, array $params = []): array {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function execute(string $sql, array $params = []): int {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->rowCount();
}
}
統一錯誤處理、防注入、可測試、易維護。
// user.php (輸入處理)
require 'UserController.php';
$action = $_GET['action'] ?? 'list';
$ctrl = new UserController();
switch ($action) {
case 'list': $ctrl->list(); break;
case 'show': $ctrl->show($_GET['id'] ?? 0); break;
case 'create': $ctrl->create($_POST); break;
default: http_response_code(404);
}
// UserController.php (業務邏輯)
class UserController {
public function list() { /* ... */ }
public function show($id) { /* ... */ }
public function create($data) { /* ... */ }
}
// 參數化查詢 + 明確欄位 + 別名
$sql = "SELECT p.id, p.title, u.username AS author
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.status = ? AND p.created_at > ?
ORDER BY p.created_at DESC LIMIT 10";
$stmt = $db->prepare($sql);
$stmt->execute(['published', '2024-01-01']);
$posts = $stmt->fetchAll();
class ServiceContainer {
private static $instances = [];
public static function get(string $key, callable $factory) {
if (!isset(self::$instances[$key])) {
self::$instances[$key] = $factory();
}
return self::$instances[$key];
}
}
// 使用
$smarty = ServiceContainer::get('smarty', fn() => {
$s = new Smarty();
$s->setTemplateDir('/templates');
$s->setCompileDir('/templates_c');
return $s;
});
$db = ServiceContainer::get('db', fn() => new Database($config['db']));
避免重複建立昂貴資源,全域單一實例。
| 新手常見 | 進階實務 |
|---|---|
| 每頁一個 .php | 單一入口 + 路由 |
| 直接 $_GET/$_POST | 輸入驗證 + 白名單 |
| echo 除錯 | var_dump + Xdebug |
| HTML 混 PHP | 模板引擎 / 關注點分離 |
| 到處 global $db | Config + 依賴注入 |
| 直接 include 內部檔 | 常數保護 + 自動載入 |
| SQL 字串拼接 | Prepared Statement + ORM |
| 每次 new Database() | 單例 / 服務容器 |