Yii 內建 CForm 建構子簽名為 __construct($config, $model),若不傳 $model 直接拋出 Call to a member function isAttributeSafe() 致命錯誤。但實際開發中,許多場景(如動態配置生成的搜尋表單、純前端互動表單、API 參數測試介面)完全不需要後端模型驗證,強行建立 CFormModel 實例純屬多餘。
CFormInputElement,覆寫 renderLabel() 直接讀取配置陣列中的 label 鍵,不再依賴 $model->getAttributeLabel()。CForm,覆寫 render() 與 renderElement(),識別自定義 Input 元素並輸出包裝 HTML,同時跳過模型安全屬性檢查。UCFormInputElement & UCForm<?php
// protected/extensions/UCFormInputElement.php
class UCFormInputElement extends CFormInputElement {
public function renderLabel() {
// 優先使用配置中的 label,否則回退到 name
return isset($this->label) ? $this->label : $this->name;
}
}
// protected/extensions/UCForm.php
class UCForm extends CForm {
public function render() {
$out = $this->renderBegin();
foreach ($this->getElements() as $el) $out .= $el->render();
foreach ($this->getButtons() as $btn) $out .= $btn->render();
$out .= $this->renderEnd();
return $out;
}
public function renderElement($element) {
if (is_string($element)) {
if (($e = $this[$element]) === null && ($e = $this->getButtons()->itemAt($element)) === null)
return $element;
$element = $e;
}
if (!$element->getVisible()) return '';
// 關鍵:識別自定義 Input 元素
if ($element instanceof UCFormInputElement) {
$wrapClass = $element->type === 'hidden' ? 'style="visibility:hidden"' : 'class="row field_' . $element->name . '"';
return "<div $wrapClass>\n" . $element->render() . "</div>\n";
} elseif ($element instanceof CFormButtonElement) {
return $element->render() . "\n";
}
return $element->render();
}
}
class PlayerSearchController extends Controller {
public function actionIndex() {
$config = [
'class' => 'search-form',
'action' => '',
'elements' => [
'<br><br>', // 可直接混入原始 HTML
'username' => [
'label' => '用戶名稱',
'type' => 'text',
'maxlength'=> 32,
'value' => '',
],
'<br><br>',
'password' => [
'label' => '密碼',
'type' => 'password',
'maxlength'=> 32,
'value' => '',
],
],
'buttons' => [
'search' => [
'type' => 'submit',
'label' => '搜尋',
],
],
];
// 傳入一個空模型僅為滿足建構子簽名,實際不被使用
$model = new CFormModel();
$form = new UCForm($config, $model);
$this->render('index', compact('form'));
}
}
views/playerSearch/index.php<?php echo $form; ?>
若表單結構來自資料庫或 JSON 配置,可封裝工廠方法:
class FormFactory {
public static function create($configArray) {
$model = new CFormModel();
return new UCForm($configArray, $model);
}
}
// 使用
$form = FormFactory::create(json_decode($jsonConfig, true));
| 優點 | 缺點 |
|---|---|
| 完全脫離模型,適合動態/臨時表單 | 失去模型驗證、過濾、CSRF 等內建安全機制 |
| 配置即代碼,前後端協作友好 | 需自行處理輸入過濾與伺服端驗證 |
| 支援混入原始 HTML 片段 | 無法使用 activeLabel、activeTextField 等 ActiveField 助手 |
通過 UCFormInputElement + UCForm 雙重繼承,我們在保留 Yii 表單配置式聲明優勢的同時,徹底切斷了對 CModel 的強依賴。這是一個「以配置代碼」的典型實踐,適合特定場景下的快速開發與原型驗證。