Yii 1.x 作為經典高性能 PHP 框架,雖已進入維護模式,但大量存量專案仍在運行。本文匯總開發實戰中高頻技巧,涵蓋配置優化、ActiveRecord 進階、緩存策略、安全加固、調試工具、部署運維等核心場景。
// main.php components.db
'db' => [
'class' => 'CDbConnection',
'connectionString' => 'mysql:host=localhost;dbname=test',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'schemaCachingDuration' => 3600, // 關鍵:緩存表結構 1 小時
'schemaCacheID' => 'cache', // 需配置 cache 組件
],
前提:必須配置 cache 組件(如 CFileCache/CMemCache/CRedisCache),否則 schemaCachingDuration 不生效。
// main.php log 組件 routes 添加
'routes' => [
// 網頁底部顯示 SQL 日志
['class' => 'CWebLogRoute', 'levels' => 'trace,info,error,warning', 'categories' => 'system.db.*'],
// FirePHP 輸出到瀏覽器控制台(需安裝插件)
['class' => 'CWebLogRoute', 'levels' => 'trace,info,error,warning', 'categories' => 'system.db.*', 'showInFireBug' => true],
// 代碼塊執行時間分析
['class' => 'CProfileLogRoute', 'levels' => 'error, warning'],
],
代碼中埋點:
Yii::beginProfile('blockID');
// 待測代碼段
Yii::endProfile('blockID');
// 獲取統計:執行語句數、總耗時
$stats = Yii::app()->db->getStats(); // [sqlCount, sqlTime]
'db' => [
'enableParamLogging' => true, // 日志中顯示綁定參數值
],
'routes' => [
['class' => 'CDbLogRoute', 'logTableName' => 'applog', 'connectionID' => 'db'],
],
表 applog 會自動創建,若失敗請參考 API 手動建表。
// index.php 入口文件
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', 10); // 堆疊深度
$criteria = new CDbCriteria();
$criteria->with = [
'author' => ['together' => true], // 強制單條 SQL(含 LIMIT 時)
'categories' => ['joinType' => 'INNER JOIN'], // 改變 JOIN 類型
];
$posts = Post::model()->findAll($criteria);
together=true:主表有 LIMIT/OFFSET 時仍生成單條 SQL,避免分頁記錄數不準$criteria->group = true 或 $criteria->distinct = true// 關聯定義
'user' => [self::BELONGS_TO, 'User', '', 'on' => 'name=userName', 'select' => 'TrueName'],
// 表名帶前綴的多對多
'categories' => [self::MANY_MANY, 'Category', '{{post_category}}(post_id,category_id)']
// Model 中定義
public function scopes() {
return [
'published' => ['condition' => 'status=1'],
'recent' => ['order' => 'create_time DESC', 'limit' => 10],
];
}
// 使用
Post::model()->published()->recent()->findAll();
// 批量插入(需自構建 SQL)
$command = Yii::app()->db->createCommand();
$command->insert('post', ['title' => 'A', 'content' => '...']);
// 或使用 DAO 批量
Yii::app()->db->createCommand()->execute("INSERT INTO post (title) VALUES ('A'),('B'),('C')");
// 刪除條件匹配
Post::model()->deleteAllByAttributes(['status' => 0]);
if ($this->beginCache('widget-sidebar', ['duration' => 3600, 'dependency' => $dependency])) {
// 昂貴渲染邏輯
$this->widget('RecentPostsWidget');
$this->endCache();
}
public function filters() {
return [
['COutputCache + index,list', 'duration' => 300, 'varyByParam' => ['page']],
];
}
$this->renderDynamic('$this->renderPartial("_user_panel", [], true)');
'varyByExpression' => 'Yii::app()->request->hostInfo',
'request' => ['enableCsrfValidation' => true],
表單必須使用 CHtml::form() 自動輸出隱藏 token 欄位。
'request' => ['enableCookieValidation' => true],
操作 Cookie 使用 CHttpCookie 類,自動簽名驗證。
$purifier = new CHtmlPurifier();
$purifier->options = ['HTML.Allowed' => 'p,b,strong,i,em,u,a[href],ul,ol,li'];
$clean = $purifier->purify($dirty_html);
// 或在視圖中
$this->beginWidget('CHtmlPurifier');
echo $user_content;
$this->endWidget();
// Controller
if ($model->save()) {
Yii::app()->user->setFlash('success', '保存成功');
$this->refresh(); // PRG 模式
}
// View
if (Yii::app()->user->hasFlash('success')) {
echo '' . Yii::app()->user->getFlash('success') . '';
}
'urlManager' => [
'urlFormat' => 'path',
'showScriptName' => false,
'rules' => [
'post/' => 'post/view',
'post/' => 'post/view', // 非數字標識
'category/' => ['post/index', 'caseSensitive' => false],
// matchValue=true 要求參數值也匹配正則
'archive//' => ['post/archive', 'matchValue' => true],
],
],
$url = $this->createUrl('post/view', ['id' => 123]); // /post/123
$url = Yii::app()->createAbsoluteUrl('post/view', ['id' => 123]); // 完整域名
'sourceLanguage' => 'en_us',
'language' => 'zh_cn', // 視圖自動尋找 views/zh_cn/
// 訊息翻譯
Yii::t('app', 'Hello {name}', ['{name}' => 'World']);
// 日期/數字格式化
Yii::app()->dateFormatter->format('yyyy-MM-dd', time());
Yii::app()->numberFormatter->format('#,##0.00', 12345.6);
'catchAllRequest' => ['site/maintenance'], // 所有請求導向維護頁
'session' => [
'cookieParams' => ['domain' => '.example.com', 'lifetime' => 0],
'timeout' => 3600,
],
'assetManager' => [
'linkAssets' => true, // 生產環境使用符號鏈接而非複製
'excludeFiles' => ['.svn', '.gitignore'],
],
'errorHandler' => ['errorAction' => 'site/error'],
// SiteController::actionError()
public function actionError() {
$error = Yii::app()->errorHandler->error;
if (Yii::app()->request->isAjaxRequest) echo $error['message'];
else $this->render('error', $error);
}
| 組件/類 | 用途 | 關鍵方法 |
|---|---|---|
| CActiveForm | 表單構建+AJAX 驗證 | validate(), enableAjaxValidation |
| CGridView | 後台數據表格 | columns, filter, dataProvider |
| CListView | 列表分頁顯示 | itemView, template, sortableAttributes |
| CDetailView | 詳情頁只讀顯示 | attributes |
| CBreadcrumbs | 麵包屑導航 | links, homeLink, separator |
| CAutoComplete | 自動完成輸入框 | url, multiple, htmlOptions |
| CCaptcha | 驗證碼 | captchaAction, showRefreshButton |
| CMarkdown | Markdown 渲染 | purifyOutput |
| CClipWidget | 內容片段捕獲復用 | id, renderClip |
| CStatePersister | 跨請求狀態持久化 | 文件/數據庫存儲 |
Yii::log($sql, 'trace', 'system.db.CDbCommand') 或開啟 CWebLogRouteYii::trace(CVarDumper::dumpAsString($var), 'my.category')Yii::app()->end() 觸發 onEndRequest 確保日志寫入(勿用 die/exit)showInFireBug=true 非侵入式調試Yii::beginProfile/endProfile + CProfileLogRoute$this->renderPartial('view', [], false, true) 才會輸出客戶端腳本(JS/CSS 註冊)addInCondition 空數組自動處理為 0=1,無需額外判斷CActiveRecord::exists():快速判斷記錄是否存在(主鍵/條件)with() 返回 CActiveFinder,調用 together() 強制單 SQLYII_DEBUG=false 時錯誤頁面不顯示堆疊,需配置 errorHandlerarray('attr1, attr2', 'required') 共用消息時需注意屬性佔位符 {attribute}ctype_* 函數族(ctype_digit 等)比正則更高效驗證純數字/字母CController::setPageState() 保存頁面狀態(跨請求表單數據)behaviors() 返回 ['BehaviorName' => 'path.to.Class']Yii::getPathOfAlias('moduleId') 獲取模塊基礎路徑Yii 1.x 憑藉組件化架構、ActiveRecord、緩存抽象、安全過濾器等特性,仍是中型專案的可靠選擇。核心建議:
CDbCriteria、with/together、scopes 優化數據庫交互catchAllRequest 維護模式)