内容管理系统里,栏目一般都带有启用或禁用的状态标记。PHP在输出栏目数据之前,如果没有检测栏目状态,被管理员禁用的栏目依然会展示在前台,这在实际项目中是经常被忽略的问题。判断栏目状态本身并不复杂,关键在于把判断逻辑写得规范、可复用,并且兼顾查询效率。下面结合几种常见的实现方式,逐一分析它们的写法和适用场景。

一、通过数据库status字段判断栏目状态
最基础的做法是在栏目表中设计一个状态字段,常见命名是status或is_enabled,用1表示启用,0表示禁用。查询时直接把这个字段查出来,再用条件语句判断即可。
以MySQL为例,假设栏目表结构如下:
CREATE TABLE `category` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(50) NOT NULL COMMENT '栏目名称', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0禁用' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP查询并判断的写法:
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'root', '123456');
$stmt = $pdo->prepare("SELECT id, name, status FROM category WHERE id = ?");
$stmt->execute([$_GET['id']]);
$category = $stmt->fetch(PDO::FETCH_ASSOC);
if ($category && $category['status'] == 1) {
echo '栏目【' . $category['name'] . '】已启用';
} else {
echo '栏目不存在或已被禁用';
}
这里有一个细节需要注意:用== 1而不是=== 1,是因为PDO取出来的值有时可能是字符串类型,严格比较会判断失败。除非在PDO连接后设置了PDO::ATTR_EMULATE_PREPARES为false并指定了整型参数,否则建议先做intval()转换再比较,代码会更稳妥:
<?php
// 先转成整数再严格比较,避免类型陷阱
$status = intval($category['status']);
if ($status === 1) {
// 栏目启用,正常输出
}
另外一种思路是把状态判断直接放到SQL语句里,用WHERE status = 1过滤,这样查不到数据就说明栏目不可用,PHP端只需要处理查询结果为空的情况。这种写法查询和判断一步完成,逻辑更简洁,适合只需要展示启用栏目的场景。
二、封装公共函数统一处理判断逻辑
当项目中多处需要判断栏目状态时,把判断逻辑散落在各个页面里会导致维护困难。比如以后状态字段从0和1改成枚举值,或者增加一个“待审核”状态,就要改很多处代码。比较规范的做法是封装一个公共函数。
<?php
/**
* 判断栏目是否启用
* @param array $category 栏目数据数组
* @return bool 启用返回true,禁用返回false
*/
function isCategoryEnabled($category)
{
if (empty($category)) {
return false;
}
// 兼容字段缺失的情况
$status = isset($category['status']) ? intval($category['status']) : 0;
return $status === 1;
}
// 使用示例
if (isCategoryEnabled($category)) {
include 'template/category_show.php';
} else {
header('HTTP/1.1 404 Not Found');
include 'template/404.php';
}
这个函数的好处是判断规则集中在一处,规则变化时只改函数内部即可。还可以进一步扩展,比如加入对栏目删除标记is_deleted的检查,或者对子栏目继承父栏目状态的判断:
<?php
/**
* 递归检查栏目及其父栏目是否全部启用
*/
function isCategoryChainEnabled($categoryId, PDO $pdo)
{
$stmt = $pdo->prepare("SELECT id, parent_id, status FROM category WHERE id = ?");
$stmt->execute([$categoryId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row || intval($row['status']) !== 1) {
return false;
}
// 没有父栏目,检查结束
if (intval($row['parent_id']) === 0) {
return true;
}
// 递归检查父栏目
return isCategoryChainEnabled($row['parent_id'], $pdo);
}
递归判断在层级栏目系统中很实用。很多系统只检查当前栏目的状态,结果父栏目被禁用后子栏目内容依然可以访问,形成管理上的漏洞。递归方式虽然每次要多查一次数据库,但栏目层级通常不深,性能影响可控。
三、循环列表中批量过滤与缓存优化
前台导航栏往往需要输出一批栏目,这时不适合对每个栏目单独查询判断,应该在一条SQL里就完成过滤,或者在PHP层用array_filter批量处理。
<?php
// 方式一:SQL直接过滤,只取启用的栏目
$rows = $pdo->query("SELECT id, name FROM category WHERE status = 1 ORDER BY sort")
->fetchAll(PDO::FETCH_ASSOC);
// 方式二:数据已查出,PHP层过滤
$allCategories = $pdo->query("SELECT id, name, status FROM category ORDER BY sort")
->fetchAll(PDO::FETCH_ASSOC);
$enabledCategories = array_filter($allCategories, function ($item) {
return intval($item['status']) === 1;
});
两种方式的选择要看业务需求。如果禁用栏目在页面上完全不出现,用SQL过滤最高效;如果页面上需要显示禁用栏目的灰色样式,提示“暂未开放”,那就要把全部数据查出来在PHP层区分展示。
对于访问量较大的站点,每次请求都查询栏目表会造成不必要的数据库压力。栏目数据变化频率很低,非常适合使用缓存。下面是一个基于文件的简单缓存示例:
<?php
function getEnabledCategories(PDO $pdo)
{
$cacheFile = __DIR__ . '\cache\category_enabled.php';
$expire = 600; // 缓存10分钟
if (file_exists($cacheFile) && time() - filemtime($cacheFile) < $expire) {
return include $cacheFile;
}
$rows = $pdo->query("SELECT id, name FROM category WHERE status = 1 ORDER BY sort")
->fetchAll(PDO::FETCH_ASSOC);
file_put_contents($cacheFile, '<?php return ' . var_export($rows, true) . ';');
return $rows;
}
使用缓存要注意一个问题:管理员在后台修改了栏目状态后,前台缓存如果没及时失效,就会出现状态不同步的情况。解决办法是在后台保存栏目时主动删除缓存文件,或者缩短缓存过期时间,也可以用Redis的键过期机制配合后台操作时删除对应键。无论采用哪种缓存方案,都要保证“后台修改、前台立即可见”这个基本体验。
总体来说,判断栏目是否启用是个小功能,但涉及的点不少:字段类型一致性、父子栏目状态继承、列表批量过滤、缓存同步等。小项目直接用SQL的WHERE status = 1就够了,栏目层级复杂、访问量大的系统则建议封装函数配合缓存来处理,把判断规则收敛到一处,后期维护会轻松很多。