Z-Blog 的 Metas:不建表怎么给文章加自定义字段
Metas 与真实字段的存储差异、Intro 存错位置的典型坑、列表页批量取值与 JSON 结构化数据的用法。
数据库里没有给你预留字段的位置,但主题又需要「阅读时长」「封面图」「来源链接」这种信息——Z-Blog 的解法是 Metas:一张挂在主表上的键值表。用对了很省事,用错了就是一堆取不出来的空值。
一、Metas 到底存在哪
不新建列,而是用 log_Metas 字段存一段序列化数组。写法有两种,效果一样:
// 单个赋值
$article->Metas->Set('readtime', '8 分钟');
// 按数组写入
$article->Metas->readtime = '8 分钟';
$article->Save();
读取:
$t = $article->Metas->Get('readtime');
// 或
$t = $article->Metas->readtime;
底层是 Metas 类,本质一个带 __get / __set 的容器。没赋过值的键返回 null,不是空字符串。
二、和真正字段的区别
| 真字段(如 log_Intro) | Metas | |
|---|---|---|
| 存储 | 独立列 | 序列化在单列里 |
| 能否用于查询排序 | 可以 | 基本不行 |
| 模板写法 | {$article.Intro} | {$article.Metas.readtime} |
| 适合存什么 | 高频、需筛选 | 低频、只做展示 |
这条差异很实用:想让字段参与 where 条件或排序,老老实实加列;只做前台展示,Metas 更划算,不用动表结构,插件卸载也不会留垃圾列。
三、最常踩的坑:Intro 存进 Metas 但主题不读
文章摘要就是典型例子。用 API 或插件写入时传 Metas: {Intro: '...'},数据库里确实能看到这一段,但前台 <meta name="description"> 还是原创截断——因为主题读的是 $article->Intro,对应 log_Intro 列,跟 Metas 里的同名键毫无关系。
排查办法:直接看 zbp_post 表,log_Intro 是空的就说明写错地方了。修正要落到列上:
$article->Intro = '一句话摘要';
$article->Save();
判断标准:模板里写 {$article.XXX} 能取到的,XXX 是对象属性;写 {$article.Metas.XXX} 才是 Metas。两者互不覆盖。
四、在列表页批量取:别一个个查库
循环里对每篇文章调 Metas->Get() 不额外查库(数据已随主记录加载),这点可以放心。但如果你写的是「按某个 Metas 值筛选文章」,那就绕不开全表扫,正确做法是:
- 数量少(几十条)→ 取出数组后在 PHP 里过滤
- 数量大且要分页 → 建独立关联表,别硬塞 Metas
五、给分类、标签、用户也能挂
Metas 不是 Post 专属,Category、Tag、Member 都有各自的 Metas 字段。比如给分类加「栏目配色」:
$cate->Metas->Set('color', '#2a6df4');
$cate->Save();
模板侧栏渲染时 {$category.Metas.color} 直接可用。做多站点或栏目差异化样式时,比改主题配置项灵活。
六、JSON 结构也能塞
Metas 存的就是字符串,塞一段 JSON 没问题:
$article->Metas->Set('faq', json_encode([
['q' => '问题一', 'a' => '答案一'],
['q' => '问题二', 'a' => '答案二'],
], JSON_UNESCAPED_UNICODE));
前台解码后循环输出即可,是做 FAQ Schema、结构化数据的快捷通道,不用额外建表。唯一注意:写入前 json_encode,读出后必须 json_decode,忘了解码就会在前台看到一坨花括号。
小结
三句话:要筛选建列,只展示用 Metas;{$a.Metas.k} 和 {$a.k} 是两条路;空值返回 null 不是空串,判断用 !== null。