Zend_Db_Table 實現 Table Data Gateway 模式,封裝單表 CRUD、關聯查詢、Row/Rowset 物件映射。本文示範:資料庫設定、Model 建立、Controller 實作增刪改查、View 顯示與分頁,並說明 Zend_Db_Select 進階查詢技巧。
resources.db.adapter = PDO_MYSQL resources.db.params.host = localhost resources.db.params.username = root resources.db.params.password = secret resources.db.params.dbname = zf1demo resources.db.params.charset = utf8 resources.db.isDefaultTableAdapter = true
Bootstrap 會自動初始化 Zend_Db_Table::setDefaultAdapter()。
class Message extends Zend_Db_Table_Abstract {
protected $_name = 'message';
protected $_primary = 'id';
protected $_rowClass = 'Message_Row'; // 可選:自定義 Row 類
}
對應資料表:
CREATE TABLE message (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
class IndexController extends Zend_Controller_Action {
public function init() {
$this->view->baseUrl = $this->_request->getBaseUrl();
}
// 列表 + 分頁
public function indexAction() {
$table = new Message();
$page = (int)$this->_getParam('page', 1);
$perPage = 10;
$select = $table->select()->order('created_at DESC');
$paginator = Zend_Paginator::factory($select);
$paginator->setCurrentPageNumber($page)
->setItemCountPerPage($perPage)
->setPageRange(5);
$this->view->paginator = $paginator;
}
// 新增
public function addAction() {
if ($this->getRequest()->isPost()) {
$data = $this->_getAllParams();
// 簡易過濾
$filter = new Zend_Filter_StripTags();
$data = array_map([$filter, 'filter'], $data);
$table = new Message();
$table->insert([
'title' => $data['title'],
'content' => $data['content'],
]);
$this->_helper->flashMessenger('新增成功');
$this->_redirect('/');
}
// GET 顯示表單
}
// 編輯
public function editAction() {
$id = (int)$this->_getParam('id');
$table = new Message();
if ($this->getRequest()->isPost()) {
$data = $this->_getAllParams();
$filter = new Zend_Filter_StripTags();
$data = array_map([$filter, 'filter'], $data);
$where = $table->getAdapter()->quoteInto('id = ?', $id);
$table->update([
'title' => $data['title'],
'content' => $data['content'],
], $where);
$this->_helper->flashMessenger('更新成功');
$this->_redirect('/');
} else {
$row = $table->find($id)->current();
if (!$row) { $this->_redirect('/'); }
$this->view->row = $row;
}
}
// 刪除
public function deleteAction() {
$id = (int)$this->_getParam('id');
if ($id > 0) {
$table = new Message();
$where = $table->getAdapter()->quoteInto('id = ?', $id);
$table->delete($where);
$this->_helper->flashMessenger('刪除成功');
}
$this->_redirect('/');
}
}
| ID | 標題 | 建立時間 | 操作 |
|---|---|---|---|
| id; ?> | escape($row->title); ?> | created_at; ?> | 編輯 刪除 |
$select = $table->select()
->from($table, ['id', 'title', 'created_at'])
->where('title LIKE ?', '%關鍵字%')
->order('created_at DESC')
->limitPage($page, $perPage); // 等同 limit/offset
// 關聯查詢 (假設有 user 表)
$select->setIntegrityCheck(false) // 允許 JOIN 產生非 Table 行物件
->joinLeft('user', 'message.user_id = user.id', ['username' => 'name']);
$row = $table->find($id)->current() 取得單筆 Zend_Db_Table_Row,可直接 $row->title = '新標題'; $row->save();$rowset = $table->fetchAll($select) 取得 Zend_Db_Table_Rowset,可 foreach ($rowset as $row) 迭代。toArray()、setFromArray()、delete()。| 問題 | 原因 | 解決 |
|---|---|---|
| SQL 注入風險 | 直接拼接字串 | 永遠使用 quoteInto()、Zend_Db_Select 參數綁定 |
| 更新/刪除影響 0 行 | Where 條件型別不符 (字串 vs 整數) | 確保 (int)$id、quoteInto('id = ?', $id) |
| 分頁總筆數慢 | 大表 COUNT(*) 全掃 | 建立覆蓋索引、使用近似計數、或快取總數 |
| 關聯查詢無法存回 | JOIN 產生唯讀 Rowset | setIntegrityCheck(false) 僅供讀取,寫入請操作原 Table |