分类父子关系怎么取,URL 到底怎么生成
Z-Blog 的分类对象看着字段挺全,但一半是魔术属性,每次访问现算。分清哪些是存的、哪些是算的,才不会写出「改了没生效」的代码。这篇把 Level、Root、Parent、AllCount 逐条核了一遍,顺带说清分页地址为什么不能手拼。
写钩子那个系列收尾的时候(《给插件加个后台页:Config 存条数与标题》那篇)我说过想聊分类和 URL,后来真去做面包屑,踩了几个坑,今天记下来。
先说结论:Z-Blog 的分类对象看着字段挺全,但有一半是魔术属性,每次访问现算,不是库里存的字段。分不清哪些是存的、哪些是算的,就会写出「改了没生效」或者「循环里查了三十次数据库」的代码。
Level 是从 0 开始数的
我一开始写 for ($i = 1; $i < $c->Level; $i++) 想缩进,结果顶级分类输出空循环,二级只缩一层。翻源码,Level 在 __get 里返回 GetDeep($this),而那个方法第一行就是:
if ($object->ParentID == 0) {
return $deep; // 默认 0
}
顶级分类的 Level 是 0,不是 1。判断「是不是顶级」写 $c->Level == 0,别写 == 1。
另外 Level 每取一次都要沿父链递归往上走,是 O(深度)。列表页对几十个分类挨个取,影响不大;但在循环里同时取 Root 和 Parent,就是三层递归叠着跑。真在意性能,循环前先算好存进数组。
SubCategories、ChildrenCategories,还有个拼错的
| 属性 | 含义 | 是否含子孙 |
|---|---|---|
SubCategories | 直接子分类 | 否 |
SubCategorys | 同上,拼写错误的别名 | 否 |
ChildrenCategories | 所有子孙分类,扁平数组 | 是 |
SubCategorys 是历史遗留的错拼,源码里用引用指向 SubCategories,改一个另一个跟着变。新代码用 SubCategories 就行。
ChildrenCategories 是扁平的,不含层级信息。想要带层级输出一棵树,得自己递归 SubCategories,别指望它。
RootID 是存的,Root 是算的
这对最容易搞混:
RootID是真实字段,存在表里,保存分类时由Save()里的GetRoot($this->ParentID)算好写进去。可以直接当 SQL 条件用。Root是魔术属性,返回$zbp->categories_all[$this->RootID]这个对象。用来取名字、取链接可以,别塞进查询条件。
同理 Parent 返回对象,ParentID 才是字段。写查询时永远用带 ID 后缀的那个。
Count 和 AllCount 差在哪
做分类列表常要显示「本篇数」。Count 是字段,只统计挂在本分类下的文章,不含子分类。想让父分类显示「含子孙总数」,用 AllCount:
if ($name == 'AllCount') {
$i = $this->Count;
foreach ($this->ChildrenCategories as $c) {
$i += $c->Count;
}
return $i;
}
所以 AllCount 是每次现算的,遍历一遍所有子孙。分类树不深就无所谓,深的话同样建议缓存。
URL 到底怎么生成的
取 $c->Url 时,__get 里走 UrlRule,把 {%id%} 和 {%alias%} 替换掉再 Make()。规则来自你后台「静态管理中心」里的分类正则,本站是:
{%host%}{%alias%}_{%page%}/
所以顶级分类是 /zixun/,第二页是 /zixun_2/。第一页不带页码,页码为 1 时那段会被整段吃掉——这是 UrlRule 的行为,不是分隔符没配对。
这里有个我踩过的坑:手拼分页链接写成 $c->Url . '?page=2',伪静态模式下这个参数不生效,永远停在第 1 页。正确做法是用 PageBar,或者手动 new UrlRule($zbp->option['ZC_CATEGORY_REGEX']) 再把 {%page%} 填进去。别手工拼分类的分页地址。
还有 alias 为空的情况。源码里是:
$u->Rules['{%alias%}'] = rawurlencode_without_backslash(
$this->Alias == '' ? $this->$backAttr : $this->Alias
);
别名空着时会回退取 ZC_ALIAS_BACK_ATTR 指定的字段,默认是 Name。所以中文分类名也能生成地址,只是会被 URL 编码成一串 %E6%8A%80...,难看但能访问。想让地址干净,就把别名填上。
面包屑怎么写
拼起来就三行,FindParents 已经封装好了:
$crumbs = array();
if ($c->ParentID > 0) {
$c->IsParents($c->ParentID); // 或直接用下面的数组
}
$parent = $c->Parent;
while ($parent) {
array_unshift($crumbs, $parent);
$parent = $parent->Parent;
}
$crumbs[] = $c;
注意 Parent 到顶级时返回 null(源码里直接 return;),所以 while ($parent) 能正常收尾,不会死循环。
小结
记三条:
Level从 0 起,顶级是 0;- 查条件只用带
ID后缀的字段,Parent/Root/Level/AllCount都是现算的; - 分页地址别手拼,交给
UrlRule或PageBar。
下一篇想写伪静态本身——规则逐条拆开,{%page%} 为 1 时那一段为什么整段消失、尾斜杠什么时候该带,Nginx 和 Apache 两套配置各写一份。