在內容管理系統(CMS)中,文章列表的靈活調用是核心需求。本文展示一個功能完整的 Article 類,支援模板驅動渲染、多條件篩選、分頁排序、層級導航、圖文列表、預覽鏈接生成等企業級特性,可直接集成至項目或作為設計參考。
<?php
/**
* 文章調用核心類
* 僅支援 PHP 5.3+(建議 7.4+)
* 依賴:Database 單例類(需實現 Query、ExecuteQuery、Update、ResultArr 等)
* 常量:DB_TABLE_PRE、SITE_URL、SITE_FOLDER、ARTICLEURL、ARTICLEPATH、ISSUEFLAG、ARTICLE_FLAG
*/
class Article {
// === 公共配置屬性 ===
public $CateId = 0; // 分類 ID,支援 "12" 或 "12,13"
public $Count = 10; // 顯示條數
public $TitleCount = 20; // 標題截取長度(默認,模板可覆蓋)
public $BeginCount = 0; // 起始位置(分頁用)
public $OrderBy = 'id'; // 主排序字段
public $OrderSort = 'DESC'; // 主排序方向
public $OrderBy2 = ''; // 次排序字段
public $OrderSort2 = ''; // 次排序方向
public $Area = 0; // 顯示區域(對應 area1-area5 字段)
public $Flag = 2; // 文章狀態(2=已發布)
public $Pic = 0; // 僅有圖片 (1=是)
public $Video = 0; // 僅有視頻 (1=是)
public $notshowlist = 0; // 包含不顯示在列表 (1=是)
public $AndWhere = ''; // 額外 SQL WHERE 條件
public $Loop = 0; // 循環計數器初值
public $Template = ''; // 渲染模板字符串
public $IdList = ''; // 調用後生成的 ID 列表(輸出屬性)
// === 內部 SQL 片段 ===
protected $SqlCateId = '';
protected $SqlCateTitleId = '';
protected $SqlArea = '';
protected $SqlFlag = '';
protected $SqlNotShow = '';
protected $SqlPic = '';
protected $SqlVideo = '';
protected $SqlOrder = '';
protected $SqlLimit = '';
protected $SqlWhere = '';
public $SqlStr = ''; // 調試用:最終完整 SQL
// === 初始化 SQL 片段 ===
public function InitSql() {
// 分類條件
if (strpos($this->CateId, ',') !== false) {
$this->SqlCateId = ' AND `cateid` IN (' . $this->CateId . ')';
} elseif ($this->CateId > 0) {
$this->SqlCateId = ' AND `cateid` = ' . (int)$this->CateId;
} else {
$this->SqlCateId = '';
}
// 區域條件
if ($this->Area > 0) {
$this->SqlArea = ' AND `area' . (int)$this->Area . '` = 1';
} else {
$this->SqlArea = '';
}
// 狀態
$this->SqlFlag = ' AND `flag` = ' . (int)$this->Flag;
// 列表顯示
$this->SqlNotShow = ' AND `notshowlist` = ' . (int)$this->notshowlist;
// 圖片/視頻
$this->SqlPic = ($this->Pic == 1) ? ' AND (`pic1` <> "" OR `pic2` <> "")' : '';
$this->SqlVideo = ($this->Video == 1) ? ' AND `isvideo` = 1' : '';
// 額外條件
$this->SqlWhere = $this->AndWhere ? ' AND ' . $this->AndWhere : '';
// 排序
$this->SqlOrder = ' ORDER BY `' . $this->OrderBy . '` ' . $this->OrderSort;
if ($this->OrderBy2) {
$this->SqlOrder .= ', `' . $this->OrderBy2 . '` ' . $this->OrderSort2;
}
// 分頁
$this->SqlLimit = ' LIMIT ' . (int)$this->BeginCount . ', ' . (int)$this->Count . ';';
}
// === 重置屬性 ===
public function Clear() {
$this->CateId = 0;
$this->Count = 10;
$this->TitleCount = 20;
$this->BeginCount = 0;
$this->OrderBy = 'id';
$this->OrderSort = 'DESC';
$this->OrderBy2 = '';
$this->OrderSort2 = '';
$this->Area = 0;
$this->Flag = 2;
$this->Pic = 0;
$this->Video = 0;
$this->notshowlist = 0;
$this->AndWhere = '';
$this->Loop = 0;
$this->Template = '';
$this->IdList = '';
}
/**
* 根據模板獲取文章列表 HTML
* 模板語法:
* {html} 文章鏈接(優先 reurl)
* {title} 標題(可 {title|長度} 截取)
* {title2} 副標題(可 {title2|長度})
* {title_full} 完整標題
* {titlestrip} 去標籤標題
* {memo} 摘要(可 {memo|長度})
* {posttime} 發布時間(可 {posttime|Y-m-d H:i} 格式化)
* {hits} 點擊數
* {catetitle} 分類標題
* {catedir} 分類目錄
* {catedesc} 分類描述
* {pic} 主圖(優先 pic2)
* {pic1} / {pic2} 原始圖片字段
* {loop} 循環計數器
* {id} 文章 ID
*/
public function getArticleListByCateId(): string {
$this->InitSql();
// 解析模板中的長度/格式參數
preg_match('/\{title\|([\d.]+?)\}/i', $this->Template, $m); $titleCount = $m[1] ?? 0;
preg_match('/\{title2\|([\d.]+?)\}/i', $this->Template, $m); $title2Count = $m[1] ?? 0;
preg_match('/\{memo\|([\d.]+?)\}/i', $this->Template, $m); $memoCount = $m[1] ?? 0;
preg_match('/\{posttime\|(.+?)\}/i', $this->Template, $m); $timeFormat = $m[1] ?? '';
// 移除參數標記,保留基礎標籤供替換
$tpl = preg_replace('/\{title\|[\d.]+?\}/i', '{title}', $this->Template);
$tpl = preg_replace('/\{title2\|[\d.]+?\}/i', '{title2}', $tpl);
$tpl = preg_replace('/\{memo\|[\d.]+?\}/i', '{memo}', $tpl);
$tpl = preg_replace('/\{posttime\|.+?\}/i', '{posttime}', $tpl);
// 構建 SQL
$sql = 'SELECT * FROM `' . DB_TABLE_PRE . 'view_article` WHERE 1=1';
$sql .= $this->SqlNotShow . $this->SqlCateId . $this->SqlArea . $this->SqlFlag;
$sql .= $this->SqlPic . $this->SqlVideo . $this->SqlWhere . $this->SqlOrder . $this->SqlLimit;
$this->SqlStr = $sql;
// 重置次排序(避免影響後續調用)
$this->OrderBy2 = $this->OrderSort2 = '';
$db = Database::Get();
$db->SqlStr = $sql;
$result = '';
$loop = $this->Loop;
if ($db->Query()) {
foreach ($db->ResultArr as $row) {
$this->IdList .= $row['id'] . ',';
// 鏈接處理
$url = $row['reurl'] ?: (SITE_URL . SITE_FOLDER . ARTICLEURL . $row['html']);
// 圖片處理
$pic = $row['pic2'] ?: $row['pic1'];
// 字段處理
$title = $titleCount ? subString($row['title'], $titleCount) : $row['title'];
$title2 = $title2Count ? subString($row['title2'], $title2Count) : $row['title2'];
$memo = $memoCount ? subString($row['memo'], $memoCount) : $row['memo'];
$titleFull = $row['title'];
$titleStrip = strip_tags($row['title']);
// 標題顏色
if ($row['titlecolor']) {
$title = '' . $title . '';
}
// 時間格式化
$posttime = $timeFormat
? date($timeFormat, $row['posttime'])
: date('Y-m-d H:i', $row['posttime']);
// 模板替換
$search = [
'{id}', '{title_full}', '{title}', '{title2}', '{titlestrip}',
'{memo}', '{html}', '{hits}', '{posttime}',
'{catetitle}', '{catedir}', '{pic}', '{pic1}', '{pic2}',
'{loop}', '{catedesc}'
];
$replace = [
$row['id'], $titleFull, $title, $title2, $titleStrip,
$memo, $url, $row['hits'], $posttime,
$row['catetitle'], $row['catedir'], $pic, $row['pic1'], $row['pic2'],
$loop++, $row['catedesc']
];
$result .= str_replace($search, $replace, $tpl);
}
}
$this->Clear();
return $result;
}
// === 單篇內容獲取 ===
public static function getContentById(int $id): string {
$db = Database::Get();
$db->SqlStr = 'SELECT `content` FROM `' . DB_TABLE_PRE . 'view_article` WHERE `id` = ' . (int)$id;
return $db->Query() ? $db->ResultArr[0][0] : '';
}
public static function getUrlById(int $id): string {
$db = Database::Get();
$db->SqlStr = 'SELECT `reurl`, `html` FROM `' . DB_TABLE_PRE . 'view_article` WHERE `id` = ' . (int)$id;
if ($db->Query()) {
return $db->ResultArr[0]['reurl'] ?: ('article_details.php?id=' . $id);
}
return '';
}
public static function getHTMLById(int $id): string {
$db = Database::Get();
$db->SqlStr = 'SELECT `html` FROM `' . DB_TABLE_PRE . 'view_article` WHERE `id` = ' . (int)$id;
return $db->Query() ? $db->ResultArr[0][0] : '';
}
// === 點擊統計 ===
public static function UpdateHits(int $id): void {
$db = Database::Get();
$db->SqlStr = 'UPDATE `' . DB_TABLE_PRE . 'article` SET `hits` = `hits` + 1 WHERE `id` = ' . (int)$id;
$db->ExecuteQuery();
}
public static function GetHits(int $id): int {
$db = Database::Get();
$db->SqlStr = 'SELECT `hits` FROM `' . DB_TABLE_PRE . 'article` WHERE `id` = ' . (int)$id;
return $db->Query() ? (int)$db->ResultArr[0][0] : 0;
}
// === 導航生成 ===
public static function getCatePreviewUrl(int $cateid): string {
$db = Database::Get();
$db->SqlStr = 'SELECT `template_url` FROM `' . DB_TABLE_PRE . 'view_articlecate` WHERE `id` = ' . (int)$cateid . ' LIMIT 1';
return $db->Query() ? $db->ResultArr[0][0] : '';
}
public static function getPreviewUrl(int $id): string {
$db = Database::Get();
$db->SqlStr = 'SELECT `template_url` FROM `' . DB_TABLE_PRE . 'view_articlelist` WHERE `id` = ' . (int)$id . ' LIMIT 1';
return $db->Query() ? $db->ResultArr[0][0] : '';
}
// === 狀態顯示 ===
public static function printFlag(int $flag = 0): string {
return match($flag) {
0 => '未存 未發',
1 => '已存 未發',
2 => '已存 已發',
default => '未知狀態'
};
}
public static function printHtmlFlag(int $flag = 0): string {
return $flag ? '已生' : '未生';
}
public static function printVideoFlag(int $flag = 0): string {
return $flag == 1 ? '視' : '';
}
public static function printIssueFlag(int $flag = 0): string {
return match($flag) {
1 => '未審',
2 => '已審',
default => '未知狀態'
};
}
}
<?php
$Article = new Article();
$Article->Template = '<li><a href="{html}" title="{title}">{title|30}</a> [{catetitle}] <span>{posttime|Y-m-d}</span></li>';
$Article->CateId = 8; // 調用分類 8
$Article->Count = 15; // 15 條
$Article->OrderBy = 'posttime'; // 按發布時間
$Article->OrderSort = 'DESC'; // 倒序
$Article->Pic = 1; // 僅有圖片
echo '<ul class="article-list">' . $Article->getArticleListByCateId() . '</ul>';
?>
<?php
$Article = new Article();
$Article->Template = '<div class="item"><h3><a href="{html}">{title|40}</a></h3><p>{memo|100}</p></div>';
$Article->CateId = '8,9,10'; // 多個分類
$Article->Count = 20;
$Article->OrderBy = 'order'; // 按排序字段
$Article->OrderSort = 'ASC';
$Article->OrderBy2 = 'id';
$Article->OrderSort2 = 'DESC';
echo $Article->getArticleListByCateId();
?>
<?php
// 使用內建方法 getPartByCateId(需在類中實現)
$Article = new Article();
echo $Article->getPartByCateId(8); // 返回完整 DL/DT/DD 結構 HTML
?>
<?php
$page = max(1, (int)($_GET['page'] ?? 1));
$pageSize = 10;
$Article = new Article();
$Article->BeginCount = ($page - 1) * $pageSize;
$Article->Count = $pageSize;
$Article->CateId = 8;
$Article->Template = '...';
$listHtml = $Article->getArticleListByCateId();
// 總數查詢(需額外 SQL)
$db = Database::Get();
$db->SqlStr = 'SELECT COUNT(*) FROM `' . DB_TABLE_PRE . 'view_article` WHERE `cateid`=8 AND `flag`=2';
$db->Query();
$total = $db->ResultArr[0][0];
$totalPages = ceil($total / $pageSize);
// 輸出分頁 HTML...
?>
CREATE TABLE `mc_article` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cateid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '分類ID',
`title` varchar(255) NOT NULL COMMENT '標題',
`title2` varchar(255) DEFAULT NULL COMMENT '副標題',
`content` text COMMENT '內容',
`memo` text COMMENT '摘要',
`posttime` int(10) unsigned NOT NULL COMMENT '發布時間戳',
`flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '狀態:0草稿,1存檔,2發布',
`hits` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT '點擊數',
`pic1` varchar(255) DEFAULT NULL COMMENT '圖片1',
`pic2` varchar(255) DEFAULT NULL COMMENT '圖片2',
`reurl` varchar(255) DEFAULT NULL COMMENT '跳轉鏈接',
`html` varchar(255) DEFAULT NULL COMMENT '靜態頁路徑',
`order` tinyint(4) unsigned NOT NULL DEFAULT '99' COMMENT '排序權重',
`isvideo` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否視頻',
`notshowlist` tinyint(4) NOT NULL DEFAULT '0' COMMENT '不在列表顯示',
`titlecolor` varchar(7) DEFAULT NULL COMMENT '標題顏色',
`area` int(10) unsigned NOT NULL DEFAULT '0',
`area1`-`area5` tinyint(1) unsigned NOT NULL DEFAULT '0',
`userid` int(10) unsigned NOT NULL DEFAULT '0',
`author` varchar(255) DEFAULT NULL,
`from` varchar(255) DEFAULT NULL,
`keyword` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mc_article_cate` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`parentid` int(10) DEFAULT NULL,
`level` char(50) DEFAULT NULL COMMENT '層級編碼: 01, 0101, 010101',
`title` char(100) DEFAULT NULL,
`dir` char(100) DEFAULT NULL COMMENT '目錄名/路由',
`url` varchar(255) NOT NULL DEFAULT '',
`templateid` int(10) DEFAULT NULL,
`catetemplateid` int(10) DEFAULT NULL,
`kind` tinyint(4) DEFAULT '0',
`pagesize` tinyint(4) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE VIEW `mc_view_article` AS
SELECT a.*, c.`parentid`, c.`level`, c.`title` AS `catetitle`, c.`dir` AS `catedir`,
c.`templateid`, c.`catetemplateid`, c.`kind`, c.`pagesize`, c.`url` AS `curl`
FROM `mc_article` a
JOIN `mc_article_cate` c ON a.`cateid` = c.`id`;
getArticleListByCateId 中加入 Redis/Memcached 緩存,Key 由所有公共屬性哈希生成beforeQuery、afterQuery、beforeRender 事件點Pagination 類自動處理分頁邏輯與鏈接生成title_en、content_en,配合 gettext 實現 i18nsearch() 方法這個 Article 類展示了 CMS 核心組件的設計思路:配置屬性化、SQL 構建模組化、渲染模板化、功能靜態化。實際項目中可根據具體框架(Laravel、ThinkPHP、Yii 等)進行 ORM 遷移,但核心設計模式具有普適價值。