ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Typecho主题开发实战:themeFields与themeConfig自定义字段全解析

Typecho主题开发实战:themeFields与themeConfig自定义字段全解析 最近在给一套 Typecho 企业站做主题改造客户给的需求很有代表性文章页要能单独设置头图、预计阅读时长、文章布局主题后台要能统一填写全站公告、页脚备案信息、控制侧边栏开关。拆开一看正好对应 Typecho 主题开发里的两个 API一个叫 themeFields网上经常被写成 themeFileds负责给文章和独立页面添加自定义字段另一个是 themeConfig负责主题全局设置。这两个 API 本身不复杂真正麻烦的是半路接手别人的主题或者主题升级时老文章没有新字段模板里取出来全是空。网上教程大多停留在“把官方示例抄一遍”字段类型怎么选、模板里如何安全取值、老文章没填字段怎么兜底这些细节几乎没人讲。所以这篇我直接按一次完整的实现过程来写从两个函数的边界到具体字段定义再到模板取值和排障最后顺带说几个能直接用的扩展思路。适合正在做 Typecho 自定义主题、或者想给博客文章页增加个性配置的朋友参考。1. 两个入口别搞混themeFileds 管文章themeConfig 管主题先花几分钟把两个函数的关系理清。很多新手第一次看主题 functions.php发现 themeFields 和 themeConfig 长得几乎一样都是function($form) { $form-addInput(...); }就容易搞混把主题设置写进了文章字段里。我最早也犯过这个错给“全站公告”字段加到了 themeFields结果后台“设置外观”里什么都没有反而是每篇文章的编辑页底部多了一个公告输入框。1.1 同样都是 addInput作用域完全不一样两段代码框架如下if (!function_exists(themeFields)) { function themeFields($form) { // $form-addInput(...) } } if (!function_exists(themeConfig)) { function themeConfig($form) { // $form-addInput(...) } }Typecho 检测到主题 functions.php 中的这两个函数后会在不同页面把$form实例注入进来调用。themeFields 的$form出现在文章和独立页面的编辑页themeConfig 的$form出现在后台“设置外观”页面。两者的区别可以整理成一张表对比维度themeFieldsthemeConfig入口位置文章/独立页面编辑页后台“控制台 - 外观 - 设置外观”数据归属单篇文章整个主题存储位置metas 表按 cid 关联options 表按theme:目录名存储模板读取$this-fields-字段名$this-options-字段名或Helper::options()-字段名典型用途封面图、阅读时长、文章布局全站公告、页脚信息、侧边栏开关理解了这张表两个入口就不会再用错。接下来所有的代码示例我都会按这个边界来写。1.2 为什么需要声明式地加字段而不是手动写 meta手动在文章编辑页新增自定义字段也就是填写字段名和值那种原始方式当然也能用但问题很多字段名要靠人记住填错一个字符模板里就取不出来没有类型约束数字字段能填任意文本团队协作时每个人维护一套自己的约定主题升级以后旧文章一堆裸字段没人敢清。themeFields 相当于把字段“注册”到了主题代码里。编辑页会渲染成带说明文字的表单控件值统一落到文章 meta 表模板读取规则固定。声明式写字段本质上是在维护一份“文章数据模型”主题可以迭代字段可以增删老文章只要处理默认值即可整体可控很多。2. themeFileds 实战给每篇文章加自定义字段的完整写法2.1 从最简单的一组字段开始先给文章加两个最常用的字段封面图和阅读时长。functions.php 里这样写if (!function_exists(themeFields)) { function themeFields($form) { $cover new Typecho_Widget_Helper_Form_Element_Text( cover, NULL, , _t(封面图地址), _t(填写图片 URL留空则使用主题默认头图) ); $form-addInput($cover); $readTime new Typecho_Widget_Helper_Form_Element_Text( read_time, NULL, 5, _t(预计阅读时长), _t(单位为分钟仅用于展示) ); $form-addInput($readTime); } }这里类名很长但参数顺序相对固定第一个参数是表单控件的 name也就是后续模板里$this-fields-cover中的 cover第二个参数是可选值Text 这类输入框传 NULL 就可以第三个是默认值第四个是字段的显示名称第五个是帮助文字。_t()是 Typecho 的翻译函数习惯上包一层方便以后做多语言主题。2.2 下拉、多选这类带选项的字段怎么定义给文章加一个“文章布局”下拉让每篇文章可以单独选择窄版还是全宽$layout new Typecho_Widget_Helper_Form_Element_Select( post_layout, array(boxed 窄版阅读, full 全宽展示), boxed, _t(文章布局), _t(当前文章单独选择布局模式) ); $form-addInput($layout);注意选项数组的键是保存到数据库里的实际值值是显示文本。所以键尽量用简短稳定的英文别用中文。否则以后想改文案旧文章里存的字段值也会跟着对不上。Radio 的写法和 Select 几乎一样只是渲染成单选按钮存储形态都是字符串。复选框是另一个容易写错的地方它的 name 必须带[]否则多选时表单只会提交最后一个值$features new Typecho_Widget_Helper_Form_Element_Checkbox( features[], array(recommend 首页推荐, sticky 文章置顶, hot 热门专题), array(recommend), _t(文章特性), _t(可多选模板中通过 in_array 判断) ); $form-addInput($features);这里第三个参数array(recommend)表示新文章默认勾选“首页推荐”。保存后这块数据的形态就跟另外几个字段完全不同了后面第 4 节专门讲。2.3 保存之后数据落在哪字段与文章 meta 的关系保存文章后字段不会进 content 表而是落到 metas 表表名由数据库前缀决定默认是typecho_metas每条记录对应cid name valuetype 固定为 individual。模板里$this-fields会把当前文章的 metas 组装成一个配置对象所以$this-fields-cover读到的就是 meta 表里那条 name 为 cover 的值。这里有个隐含规则同名字段会互相覆盖同一篇文章里不要定义两个相同 name 的自定义字段。另外如果你在后台手动建过一个叫 cover 的 meta再用 themeFields 定义 cover保存时同样是覆盖关系后提交的生效。3. themeConfig 实战主题级设置项的添加与读取3.1 常见设置项的字段组合主题设置项的写法和 themeFields 几乎一样只是函数名换成 themeConfig。我通常会把字段名加一个主题前缀比如 mt_notice、mt_footer_info减少和系统全局 options 撞名的概率。看一组实际配置if (!function_exists(themeConfig)) { function themeConfig($form) { $notice new Typecho_Widget_Helper_Form_Element_Textarea( mt_notice, NULL, , _t(全站公告), _t(显示在顶部公告栏留空不显示) ); $form-addInput($notice); $footer new Typecho_Widget_Helper_Form_Element_Textarea( mt_footer_info, NULL, Powered by Typecho, _t(页脚信息), _t(支持 HTML常用来放版权、备案信息) ); $form-addInput($footer); $sidebar new Typecho_Widget_Helper_Form_Element_Radio( mt_sidebar, array(1 开启, 0 关闭), 1, _t(侧边栏开关), _t(全站统一控制侧边栏展示) ); $form-addInput($sidebar); } }Textarea 适合多行文本比如公告、统计代码、备案号。Radio 在这里充当开关注意它的值存的是字符串1或0不是布尔值模板判断时一定要拿字符串比较。3.2 模板里读取主题配置的两条路后台设置保存后数据落在 options 表键名是theme:你的主题目录名值是 PHPserialize序列化后的数组。模板里读取有两条常见路径。第一条模板上下文里有$this比如 index.php、post.php 这类页面模板直接用$this-options-mt_notice?php if (!empty($this-options-mt_notice)): ? div classsite-notice?php echo $this-options-mt_notice; ?/div ?php endif; ?第二条在 functions.php 内部写的辅助函数里或者任何没有$this的地方用Helper::options()function theme_get_option($key, $default ) { static $options NULL; if (NULL $options) { $options Helper::options(); } return isset($options-$key) ! $options-$key ? $options-$key : $default; }这个函数用 static 缓存避免了在循环里反复实例化 Widget_Options性能上更稳。上面 Radio 开关的读取可以写成$sidebarOpen theme_get_option(mt_sidebar, 1) 1;注意是 1而不是 1因为存进去的是字符串。同样如果你在模板里用$this-options-mt_sidebar也要用 1判断。3.3 主题配置的存储与备份注意点主题设置存在数据库里跟着theme:前缀的 options 记录走。这意味着三件事第一备份主题源码不包含设置项换机器必须连带数据库一起备份否则设置全部重填第二如果需要把配置迁移到另一套主题直接改数据库里theme:开头的记录不太推荐后台重新填最稳第三多个主题之间的设置互不影响因为键名带目录名切回旧主题设置还在。这个设计其实挺方便不用担心换主题把原有配置弄丢。4. 模板输出与默认值保护字段取值的完整姿势4.1 字段在模板里的三种读取方式字段在模板里的读取方式是三种$this-fields-cover // 作为值使用 $this-fields-cover() // 直接输出 var_dump($this-fields); // 调试看当前文章所有自定义字段不加括号是取值适合赋值给变量、拼 URL、做判断加括号是输出Typecho 模板层会自动做 HTML 转义适合直接把内容 echo 到页面。开发阶段想看这个文章到底存了哪些字段在模板里临时写一行var_dump($this-fields);全部真相都出来了比一个个猜测字段名快得多。要注意cover()这类括号输出会转义 HTML。如果字段里存的是图片标签这类富内容要改用echo配合手动过滤不要让括号帮你转义。列表页的 while 循环里也能正常读取每个文章的 fields因为它们各自属于当前循环到的文章对象。4.2 老文章没有新字段兜底值怎么写老文章在字段还没填的时候$this-fields-cover是 null直接用可能输出空值很难看。我习惯做一层兜底$cover trim((string) $this-fields-cover); if ( $cover) { $cover $this-options-default_cover; // 主题设置里配的默认图 } $readTime (int) $this-fields-read_time; if ($readTime 0) { $readTime 5; } $layoutClass $this-fields-post_layout full ? layout-full : layout-boxed;字段里保存的值不一定是合法类型读出来先转型、再判空、给默认值是模板代码健壮的第一步。特别是 0/1 开关字段判断时不要用if ($this-fields-switch)因为字符串0在 PHP 里是 falsy明明开关是关闭状态条件判断会走到“未设置”分支用 1最明确。4.3 checkbox 多选字段的数组陷阱checkbox 字段在 meta 表里存的是序列化数组模板里拿到的是数组而不是字符串。常见错误是直接echo $this-fields-features结果页面输出一个 Array 或者直接报警告。正确写法是先判空再处理$features $this-fields-features; if (!is_array($features)) { $features array(); } if (in_array(recommend, $features)) { // 输出推荐标签 } if (in_array(sticky, $features)) { // 输出置顶标记 }注意老文章如果没有勾选任何一个 checkbox这个字段可能根本不存在所以is_array判断不能省。下拉框和单选没有这个问题它们保存的永远是一个字符串。5. 踩坑清单字段消失、命名冲突、缓存问题的排查链路5.1 函数没生效先查这个最常见的问题后台“设置外观”里什么都没有或者文章编辑页看不到自定义字段按这个顺序查确认 functions.php 里确实定义了对应函数并且文件名是 functions.php 而不是 function.php确认语法没报错可以在文件头部临时加一段 debug 输出或者直接看网站日志检查函数名拼写。PHP 函数名大小写不敏感但拼写必须对themeFields是 Fields 不是 Fileds网上很多文章写成 themeFileds复制下来是不会被 Typecho 识别的检查主题是否被正确启用编辑的是不是当前启用主题的 functions.php如果整个主题文件夹是从压缩包解压的确认有没有被权限或安全类插件拦截写入。这个坑排在前面的概率最高因为函数不生效通常不会报错而是静默吞掉特别容易让人怀疑是缓存问题。5.2 字段名与系统预留项冲突自定义字段的 name 一定避开系统预留给文章的字段cid、title、slug、created、modified、type、status、password、template、author、authorId、date、category、tags、commentsNum 这些。用了 template 会直接覆盖页面模板选择用 title 可能覆盖文章标题表现非常诡异很难联想到是字段冲突。主题设置项同样要避开 siteUrl、description、keywords、theme 这类系统 options 键。最稳妥的做法就是给全部字段加前缀比如我前面写的 mt_ 前缀。代价只是模板里多打几个字符换来的是全站配置项的隔离后续维护省心得多。5.3 后台改字段后前台没变化缓存链路排障后台保存设置后前台读不到新值先别怀疑代码。依次排除页面静态化插件TpCache、自定义 ob_start 缓存、Nginx 的 FastCGI Cache、CDN 边缘缓存、PHP OPcache。Typecho 自己是不缓存主题配置的所以“清空浏览器缓存”多数时候没用重点查服务端缓存。经验是改 functions.php 里的字段定义本身不需要清缓存改后台设置值后如果前台没变化才需要清。开了 Redis/Memcache 类缓存插件的还要去对应插件设置里刷新。5.4 文章编辑页看不到自定义字段怎么办themeFields 对文章和独立页面都生效但有些主题会在 functions.php 里自己套条件判断比如if (post $this-type)这种写法会把独立页面排除掉。如果只希望部分内容类型显示字段用条件判断可以否则不要加过滤让 Typecho 默认把它内部逻辑处理好的表单渲染出来。另外编辑页如果开了预览等分栏模式字段表单在“自定义字段”折叠面板里需要手动展开。这不叫字段消失是 UI 折叠状态第一次见到容易误会。6. 进阶扩展图片字段、联动控制与主题配置的备份迁移6.1 图片上传字段的现实做法首选的图片字段就是 Text 输入 URL。Typecho 原生没有给 themeFields 提供“图片选择器”控件如果网上某些主题实现了图片上传那都是自己写的 JS 调后台附件窗口。对大多数博客场景作者直接粘贴图片地址已经够用配合文章编辑器里的“插入图片”功能把编辑器生成的 URL 复制过来就行。如果真的要做上传按钮思路是利用 Typecho 的插件钩子在后台文章编辑页底部注入一段脚本把附件管理窗口里选中的图片 URL 回填到目标 input 里。这段 JS 不难写但要处理好窗口回调与多个 input 的对应关系属于产品级主题才值得投入的工程量。6.2 后台编辑页挂 JS 做联动显隐select 或 radio 字段的另一个常见需求是联动选了某个布局才显示相关字段。后台编辑页要挂脚本趁早放弃在模板 header 里引入的方式Typecho 后台不会加载主题模板。正确姿势是给 functions.php 注册一个钩子if (!class_exists(MyTheme_AdminHooks)) { class MyTheme_AdminHooks { public static function render() { echo script jQuery(function($){ $(document).on(change, select[name\post_layout\], function(){ var v $(this).val(); $([name\read_time\]).parents(.typecho-option).toggle(v full); }); }); /script; } } } Typecho_Plugin::factory(admin/write-post.php)-bottom array(MyTheme_AdminHooks, render);核心是select[namepost_layout]这个选择器它直接按表单 name 匹配不关心控件外层嵌在哪一层。然后用parents(.typecho-option)找到 Typecho 后台表单项的容器并控制显隐。这个钩子只在文章编辑页生效如果主题设置页也要联动再挂一个admin/options-theme.php的 bottom 即可。6.3 配置迁移与多主题切换时的注意事项最后说下配置迁移。因为 themeConfig 的设置项都存在数据库主题源码打包给别人时对方看不到任何你填好的配置反过来你从别人那里拿到一套预配置主题也不要以为解压缩就能直接用。数据库层面迁移时找到 options 表里theme:主题目录名那条记录用工具导出单行 JSON 也可以但不如后台手工重填稳妥因为字段结构可能随主题版本变化。我一般会在主题包里附带一份 config.txt列出所有 themeFields 和 themeConfig 定义了什么字段、类型、默认值、用途。这样接手的同事或用户不用开着后台一个个猜也方便后续版本对比字段变更。实际开发中我习惯先把所有字段集中到 functions.php 顶部的一份数组或注释清单里规定好 name 前缀和取值类型再往 themeFields 和 themeConfig 里填字段多了之后才不会乱。最后再分享一个调试技巧也是我每次做主题必用的开发阶段在模板里临时放一行var_dump($this-fields);浏览器里就能看到这篇文章所有自定义字段的原始值字段名拼错、类型不对、序列化异常全部一眼看穿。确认没问题之后再删掉这行排空字段的效率比逐个 var_dump 高太多了。
返回列表