组合模式应从 Menu 和 MenuItem 实际需求出发:MenuItem 存单条数据(含 parent_id、permission_code),Menu 仅提供 add()、hasPermission()、toArray() 方法;权限校验必须落在叶子节点,用 DFS 递归穿透至末级 permission_code 字段。

组合模式在 PHP 中怎么写才不绕晕自己
组合模式不是设计模式考试题,是为了解决「菜单有父子、权限要递归校验」这种真实需求。核心就一条:让单个菜单项和整个菜单树用同一套接口处理。
常见错误是硬套 UML 类图,把 Component 抽得太抽象,结果加个新字段就要改三四个类。实际项目里,直接从 Menu 和 MenuItem 开始写更稳。
-
MenuItem存单条数据(如「用户管理」),带parent_id和permission_code -
Menu是容器,只负责add()、hasPermission($code)、toArray()这几个方法 - 不要给
MenuItem加addChild()—— 它不是容器,强行加会导致类型混淆
树形菜单权限校验该在哪一层做
权限判断必须落在「叶子节点」,但触发点往往在顶层。比如访问 /admin/user/list,系统要检查当前用户是否有 user:list 权限 —— 这个码通常存在最末级 MenuItem 的 permission_code 字段里,而不是父级「用户管理」上。
容易踩的坑是把权限挂在目录节点(如「系统设置」),导致子菜单全开或全关。真实业务中,权限粒度至少到按钮级,所以校验逻辑必须穿透到叶子。
立即学习“PHP免费学习笔记(深入)”;
- 递归校验时,用深度优先(DFS)比广度优先(BFS)更自然,便于提前
return true - 缓存建议按用户+菜单 ID 组合键存,别缓存整棵树再遍历 —— 一次请求通常只查 1–2 个权限码
- 如果用了 Laravel,别在
Gate::define()里现场查数据库树,先用Menu::forUser($userId)->getFlatPermissions()预加载进内存
PHP 数组递归转树结构的坑
从数据库查出的扁平数据(id + parent_id)转树,90% 的问题出在引用丢失和空父节点处理上。
典型错误代码:$tree[$item['parent_id']][] = $item —— 这会漏掉根节点(parent_id 为 null 或 0),且没处理多级嵌套时的引用断裂。
- 先用
foreach建立所有节点的引用映射:$itemsById[$item['id']] = &$item - 再扫一遍,遇到
parent_id > 0就往对应父节点的children数组里push引用 - 最后挑出
parent_id == 0 || is_null(parent_id)的作为根节点返回 - 注意 MySQL 中
parent_id是INT NULL还是TINYINT DEFAULT 0,判空逻辑得跟着变
Laravel + Vue 场景下权限同步怎么避免前后端对不上
后端返回的菜单树里写了 permission_code: "order:export",前端按钮却一直灰着?大概率是 Vue 模板里写的 v-permission="'order.export'" —— 冒号和点号不统一。
这不是风格问题,是协议断裂。前后端必须约定分隔符,且全程一致。推荐用冒号 :,因为更贴近 RESTful 路由习惯,也方便后端生成策略名(Gate::allows('order:export'))。
- 后端 API 返回菜单时,确保每个
MenuItem的permission_code字段非空、格式统一(如全小写、无空格) - 前端指令或组件内部,别自己拼接权限码,直接透传后端字段值
- 开发期加个简单校验:启动时比对后端返回的所有
permission_code和前端定义的按钮列表,输出缺失项
permission_code 字段。











