如何用CakePHP 3生成关联技术详情评分的分类树形结构
先明确你的两张数据表结构:
ecategory(id, name, parent_id):分类表,通过parent_id字段构建父子层级关系technical_detail(id, tech_interview_id, category_id):技术详情表,category_id作为外键关联分类表的id
表结构截图


注意你现有的代码是CakePHP 2的写法,CakePHP 3已经不再使用recursive这类方式了,我们用官方推荐的TreeBehavior和ORM关联来实现需求,具体步骤如下:
第一步:配置模型的关联与树形行为
首先在分类表的模型文件src/Model/Table/EcategoryTable.php中,启用专门处理树形结构的TreeBehavior,同时建立和技术详情表的关联:
namespace App\Model\Table; use Cake\ORM\Table; use Cake\ORM\Behavior\TreeBehavior; class EcategoryTable extends Table { public function initialize(array $config): void { parent::initialize($config); // 启用TreeBehavior,自动处理父子分类的层级关系 $this->addBehavior('Tree'); // 关联技术详情表:一个分类对应多条技术详情 $this->hasMany('TechnicalDetail', [ 'foreignKey' => 'category_id' ]); } }
然后在技术详情表的模型src/Model/Table/TechnicalDetailTable.php中配置反向关联:
namespace App\Model\Table; use Cake\ORM\Table; class TechnicalDetailTable extends Table { public function initialize(array $config): void { parent::initialize($config); // 关联分类表:一条技术详情属于一个分类 $this->belongsTo('Ecategory', [ 'foreignKey' => 'category_id' ]); } }
第二步:控制器中查询树形分类并关联评分数据
在控制器(比如EcategoriesController.php)里,用find('threaded')获取嵌套的树形结构,同时包含技术详情数据,还可以直接计算每个分类的评分(这里假设统计平均评分,你可以根据实际字段调整):
namespace App\Controller; use App\Controller\AppController; class EcategoriesController extends AppController { public function index() { // 查询顶级分类(parent_id=0)的树形结构,同时包含技术详情并计算平均评分 $ecategories = $this->Ecategory->find('threaded') ->select([ 'Ecategory.id', 'Ecategory.name', 'Ecategory.parent_id', // 子查询计算当前分类的平均评分,这里的score替换成你实际的评分字段 'average_score' => $this->Ecategory->TechnicalDetail->find() ->select(['avg' => 'AVG(score)']) ->where(['category_id = Ecategory.id']) ->limit(1) ]) ->contain(['TechnicalDetail']) // 关联技术详情数据 ->where(['Ecategory.parent_id' => 0]) ->all(); $this->set(compact('ecategories')); } }
find('threaded')会自动返回嵌套数组,每个分类对象里的children属性就是它的子分类,非常方便处理树形结构。
第三步:视图中渲染树形结构和评分
在视图文件Template/Ecategories/index.ctp里,我们用递归元素来渲染层级结构:
<h1>分类树形结构及技术详情评分</h1> <?= $this->element('category_tree', ['categories' => $ecategories]) ?>
然后创建一个元素文件Template/Element/category_tree.ctp,递归渲染分类、评分和对应的技术详情:
<ul> <?php foreach ($categories as $category): ?> <li> <strong><?= h($category->name) ?></strong> <?php if (isset($category->average_score)): ?> <span style="margin-left:10px;">平均评分:<?= number_format($category->average_score, 2) ?></span> <?php endif; ?> <!-- 展示当前分类的所有技术详情 --> <?php if (!empty($category->technical_detail)): ?> <ul style="margin-top:5px;"> <?php foreach ($category->technical_detail as $detail): ?> <li> 技术详情ID:<?= h($detail->id) ?> | 关联面试ID:<?= h($detail->tech_interview_id) ?> </li> <?php endforeach; ?> </ul> <?php endif; ?> <!-- 递归渲染子分类 --> <?php if (!empty($category->children)): ?> <?= $this->element('category_tree', ['categories' => $category->children]) ?> <?php endif; ?> </li> <?php endforeach; ?> </ul>
对你现有代码的小说明
你之前的代码是CakePHP 2的写法,CakePHP 3已经废弃了recursive属性,改用contain来关联数据,而且手动查询分类ID再查技术详情的方式不仅繁琐,效率也不如ORM的关联查询高,上面的方法更符合CakePHP 3的最佳实践。
内容的提问来源于stack exchange,提问作者Kosar Latif

