隨著專案增長,控制器間會出現大量共同邏輯:權限檢查、語系初始化、模板載入、資料準備、日誌記錄... 若每個控制器重複撰寫,維護成本極高。CodeIgniter 透過 MY_Controller 機制支援多層次繼承,本文示範標準三層架構設計。
CI_Controller (核心)
│
▼
MY_Controller (應用通用基礎)
│
├──────────────┬──────────────┐
▼ ▼ ▼
AdminBase FrontBase ApiBase
(後台基類) (前台基類) (API基類)
│ │ │
▼ ▼ ▼
Admin_* Front_* Api_*
控制器 控制器 控制器
application/core/MY_Controller.php
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Controller extends CI_Controller {
protected $data = []; // 視圖共用資料
protected $current_user = null; // 當前登入用戶
public function __construct() {
parent::__construct();
// 1. 載入通用輔助函數/類庫
$this->load->helper(['url', 'form', 'html', 'date', 'text']);
$this->load->library(['session', 'pagination']);
// 2. 多語系初始化
$lang = $this->session->userdata('lang') ?: config_item('language');
$this->config->set_item('language', $lang);
$this->lang->load('common', $lang);
// 3. 共用視圖資料
$this->data['site_name'] = config_item('site_name');
$this->data['current_url'] = current_url();
$this->data['csrf_token'] = $this->security->get_csrf_hash();
// 4. 記錄請求日誌 (開發環境)
if (ENVIRONMENT === 'development') {
log_message('debug', 'Request: ' . $this->input->server('REQUEST_METHOD') . ' ' . current_url());
}
// 5. 註冊關閉函數:清理資源、記錄回應時間
register_shutdown_function([$this, '_shutdown']);
}
/** 統一視圖渲染入口 */
protected function _render($view, $data = [], $layout = 'main') {
$data = array_merge($this->data, $data);
$data['content_view'] = $view;
$data['content_data'] = $data;
$this->load->view("layouts/{$layout}", $data);
}
/** 輸出 JSON 回應 */
protected function _json($data, $status = 200) {
$this->output
->set_status_header($status)
->set_content_type('application/json', 'utf-8')
->set_output(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
/** 關閉處理 */
public function _shutdown() {
// 可在此記錄慢查詢、回應時間、釋放鎖等
}
}
application/core/AdminBase.php
class AdminBase extends MY_Controller {
protected $admin_user = null;
protected $permissions = [];
public function __construct() {
parent::__construct();
// 1. 強制 HTTPS (生產環境)
if (ENVIRONMENT === 'production' && !$this->input->is_https_request()) {
$this->_json(['error' => 'HTTPS required'], 403);
exit;
}
// 2. 管理員登入驗證
$this->_auth_check();
// 3. 權限載入
$this->_load_permissions();
// 4. 後台專用視圖資料
$this->data['admin_user'] = $this->admin_user;
$this->data['menu_active'] = $this->router->fetch_class(); // 側邊欄高亮
$this->data['breadcrumbs'] = $this->_build_breadcrumbs();
}
/** 登入驗證核心 */
private function _auth_check() {
$admin_id = $this->session->userdata('admin_id');
if (!$admin_id) {
// AJAX 請求回傳 401,普通請求導向登入頁
if ($this->input->is_ajax_request()) {
$this->_json(['error' => 'Unauthorized', 'redirect' => site_url('admin/login')], 401);
} else {
$this->session->set_userdata('login_redirect', current_url());
redirect('admin/login');
}
exit;
}
$this->admin_user = $this->Admin_model->get_by_id($admin_id);
if (!$this->admin_user || $this->admin_user->status !== 1) {
$this->session->sess_destroy();
redirect('admin/login');
}
// 更新最後活動時間
$this->Admin_model->update_last_activity($admin_id);
}
/** 載入權限清單 */
private function _load_permissions() {
$this->permissions = $this->Admin_model->get_permissions($this->admin_user->role_id);
$this->data['permissions'] = $this->permissions;
}
/** 權限檢查助手 */
protected function _check_perm($perm_key) {
if (!in_array($perm_key, $this->permissions)) {
if ($this->input->is_ajax_request()) {
$this->_json(['error' => 'Permission denied'], 403);
} else {
show_error('權限不足:' . $perm_key, 403);
}
exit;
}
}
/** 建立麵包屑導航 */
private function _build_breadcrumbs() {
$class = $this->router->fetch_class();
$method = $this->router->fetch_method();
$crumbs = [['title' => '首頁', 'url' => site_url('admin/dashboard')]];
// 可根據路由映射標題
$titles = [
'users' => '會員管理',
'orders' => '訂單管理',
'products' => '商品管理',
];
if (isset($titles[$class])) {
$crumbs[] = ['title' => $titles[$class], 'url' => site_url("admin/{$class}")];
}
return $crumbs;
}
/** 後台統一佈局 */
protected function _render($view, $data = []) {
parent::_render($view, $data, 'admin'); // 使用 layouts/admin.php
}
}
application/core/FrontBase.php
class FrontBase extends MY_Controller {
public function __construct() {
parent::__construct();
// 1. 前台專用載入
$this->load->model(['Product_model', 'Category_model', 'Cart_model']);
$this->load->library('cart');
// 2. 會員登入狀態 (非強制)
$this->_load_member();
// 3. 購物車/收藏數量
$this->data['cart_count'] = $this->Cart_model->get_count($this->member_id ?? 0);
$this->data['wishlist_count'] = $this->Wishlist_model->get_count($this->member_id ?? 0);
// 4. SEO 基礎資料
$this->data['seo'] = [
'title' => config_item('site_name'),
'description' => config_item('site_description'),
'keywords' => config_item('site_keywords'),
'og_image' => base_url('assets/images/og-default.jpg'),
];
}
private function _load_member() {
$member_id = $this->session->userdata('member_id');
if ($member_id) {
$this->member = $this->Member_model->get_by_id($member_id);
$this->data['member'] = $this->member;
}
}
/** 前台統一佈局 */
protected function _render($view, $data = []) {
parent::_render($view, $data, 'front'); // 使用 layouts/front.php
}
}
application/core/ApiBase.php
class ApiBase extends MY_Controller {
protected $api_user = null;
public function __construct() {
parent::__construct();
// 1. 關閉 CSRF、視圖、佈局
$this->config->set_item('csrf_protection', FALSE);
// 2. 統一 JSON 輸出
$this->output->set_content_type('application/json', 'utf-8');
// 3. API 版本控制 (從 Header 或 URL)
$this->api_version = $this->input->get_request_header('X-API-Version') ?: 'v1';
// 4. 簽名驗證 / Token 驗證
$this->_auth_check();
// 5. 速率限制
$this->_rate_limit();
}
private function _auth_check() {
$token = $this->input->get_request_header('Authorization');
if ($token && strpos($token, 'Bearer ') === 0) {
$token = substr($token, 7);
$this->api_user = $this->Api_token_model->validate($token);
}
// 公開 API 可不強制驗證,由具體控制器決定
}
private function _rate_limit() {
$key = 'api_rate:' . ($this->api_user->id ?? $this->input->ip_address());
$count = $this->cache->increment($key);
if ($count === 1) {
$this->cache->save($key, 1, 60); // 1 分鐘視窗
}
if ($count > 100) { // 每分鐘 100 次
$this->_json(['error' => 'Rate limit exceeded'], 429);
exit;
}
}
/** 成功回應 */
protected function _success($data = [], $message = 'success') {
$this->_json(['code' => 0, 'message' => $message, 'data' => $data]);
}
/** 失敗回應 */
protected function _error($message, $code = 400, $data = []) {
$this->_json(['code' => $code, 'message' => $message, 'data' => $data], $code);
}
}
// 後台:application/controllers/admin/Users.php
class Users extends AdminBase {
public function __construct() {
parent::__construct();
$this->_check_perm('users.view'); // 權限檢查
$this->load->model('User_model');
}
public function index() {
$this->_check_perm('users.list');
$page = (int)$this->input->get('page') ?: 1;
$per_page = 20;
$users = $this->User_model->get_paginated($page, $per_page);
$this->data['users'] = $users;
$this->_render('admin/users/index');
}
public function edit($id) {
$this->_check_perm('users.edit');
// ...
}
}
// 前台:application/controllers/Product.php
class Product extends FrontBase {
public function detail($id) {
$product = $this->Product_model->get_detail($id);
if (!$product) show_404();
$this->data['product'] = $product;
$this->data['seo']['title'] = $product->name . ' - ' . config_item('site_name');
$this->data['seo']['og_image'] = $product->image_url;
$this->_render('product/detail');
}
}
// API:application/controllers/api/v1/Orders.php
class Orders extends ApiBase {
public function index() {
if (!$this->api_user) return $this->_error('Unauthorized', 401);
$orders = $this->Order_model->get_by_user($this->api_user->id);
$this->_success($orders);
}
}
application/
├── core/
│ ├── MY_Controller.php # 第1層:通用基礎
│ ├── AdminBase.php # 第2層:後台基類
│ ├── FrontBase.php # 第2層:前台基類
│ ├── ApiBase.php # 第2層:API基類
│ └── MY_Model.php # 模型基類
├── controllers/
│ ├── admin/ # 後台控制器 (繼承 AdminBase)
│ │ ├── Dashboard.php
│ │ ├── Users.php
│ │ └── Login.php # 登入頁不需繼承 AdminBase
│ ├── front/ # 前台控制器 (繼承 FrontBase)
│ │ ├── Home.php
│ │ ├── Product.php
│ │ └── Cart.php
│ └── api/ # API 控制器 (繼承 ApiBase)
│ └── v1/
│ ├── Orders.php
│ └── Products.php
└── views/
├── layouts/
│ ├── main.php # 通用佈局
│ ├── admin.php # 後台佈局
│ │ └── front.php # 前台佈局
├── admin/
├── front/
└── errors/
| 原則 | 實踐 |
|---|---|
| 單一職責分層 | MY_Controller 只做通用初始化,業務邏輯下沉到具體基類 |
| 組合優於繼承 | 複雜共用邏輯 (如活動檢查、優惠計算) 做成 Service/Library 注入,而非繼承 |
| 建構子鏈完整 | 每層 parent::__construct() 必須呼叫,否則父類初始化失效 |
| 登入頁/公開頁不繼承基類 | Login、Register、Password Reset 直接繼承 CI_Controller 或 MY_Controller,避免無限重導 |
| 視圖資料標準化 | 統一使用 $this->data 陣列,_render() 合併資料,避免變數散落 |
CodeIgniter 控制器繼承架構的核心在於:**MY_Controller 奠定通用基礎,業務基類 (AdminBase/FrontBase/ApiBase) 封裝領域邏輯,具體控制器專注業務流程**。三層架構清晰分離關注點,新增功能只需決定繼承哪個基類,自動獲得對應的權限、佈局、驗證、速率限制等能力,大幅降低重複代碼與維護成本。