先看一下实际效果:
我的实现方式是:
1. 功能点API
2. 具体实现
3. 相关实现代码
/* * 解析 URL 成菜单表中能够识别的 模块 + 子模块(或动作名称)名称 */ public function parseUrlToEazyName(){ $controller = $GLOBALS['controller']; $action = $GLOBALS['action']; $result = []; $names = []; $part = function($str = '' , $s_idx = 0) use (&$result , &$part , &$count){ // 首字母小写:避免第一个大写,被单独取出 $search = preg_match('/[A-Z]/' , $str , $rel , PREG_OFFSET_CAPTURE , $s_idx + 1); $str_len = mb_strlen($str); if ($search === 0) { $e_idx = $str_len; } else { $e_idx = $rel[0][1]; } $result[] = mb_substring($str , $s_idx , $e_idx); if ($str_len !== $e_idx) { $part($str , $e_idx); } return $result; }; // 控制器 $part($controller , 0); $names['controller'] = strtolower(implode('_' , $result)); // 动作 $result = []; $part($action , 0); $names['action'] = strtolower(implode('_' , $result)); return $names; } // 当前位置 public function getCurPos(){ $names = $this->parseUrlToEazyName(); $action = $names['action']; $data = []; $parent_list = []; // 找到当前元素所在 单元 $find = function($name = '' , Array $list = []){ $result = false; $do = function(Array $list = []) use(&$name , &$result , &$do){ foreach ($list as $v) { if (!empty($v)) { if ($v['name'] === $name) { $result = $v; return ; } else { if ($v['has_child']) { $do($v['child']); } } } } }; $do($list); return $result; }; // 通过 底层项 找到整个链条 $findParentTree = function(Array $item = [] , Array $list = []) use(&$data , &$find , &$parent_list){ $data = $item; $parent = $item['parent']; $parent_list[] = $item['name']; if (!empty($parent)) { $parent_list[] = $parent; } // 由于是最底层的项,所以即使其有子项,也必须清空,但是 has_child 不修改。 $data['child'] = []; $result = []; while ($parent) { $result = $find($parent , $list); if (!empty($result)) { $result['has_child'] = true; $result['child'] = $data; $data = $result; $item = $data; $parent = $data['parent']; $parent_list[] = $parent; } } return $data; }; // 通过给定的菜单名称,找到其所有的子元素,如果没有的话,返回空数组 $findTopModuleNextTree = function($name = '' , Array $list = []){ foreach ($list as $v) { if ($v['name'] === $name) { return $v['child']; } } return false; }; // 生成 HTML $genHTML = function(Array $list = []){ $html = ''; $gen = function(Array $item = []) use(&$html , &$gen){ $html .= "" . $item['cn_explain'] . "/"; if (!empty($item['child'])) { $gen($item['child']); } }; $gen($list); return $html; }; $result = $find($action , $GLOBALS['menu']); if ($result === false) { throw new \Exception('菜单中找不到当前动作路径'); } $result = $findParentTree($result , $GLOBALS['menu']); // 将当前选中的顶级模块赋值进模板 $this->assign('top_module' , $result); $top_module_next_tree = $findTopModuleNextTree($result['name'] , $GLOBALS['menu']); // 当前模块的对应的二级菜单模块 foreach ($parent_list as $v) { foreach ($top_module_next_tree as $v1) { if (!empty($v1)) { if (isset($v1['name']) && $v === $v1['name']) { // 将顶级模块的二级模块赋值进模板 $this->assign('c_menu' , $v); break; } } } } $html = $genHTML($result); $lc = mb_substr($html , mb_strlen($html) - 1); if ($lc === '/') { $html = mb_substr($html , 0 , -1); } return $html; }
上面的实现方式有几个缺陷:
由于不是使用 数字型,而是使用 文字型,导致在功能点命名上要求绝对不能有重复名称的。
如果API
链接上要求带上动态参数,则无法实现。
功能点多的情况下,显得好庞杂的样子(是采用存入数据库表的方式,还是直接像我一样另起一个文件保存??)
不知道大家是怎样实现这个功能的呢??
因为这实际上跟架构有很大的关系,劳烦有经验的大牛,亮出你的 style
,3q
感谢:BinotaLIU 的回答,补充说明下函数的相关作用...
1. parseUrlToEazyName 函数的作用:
由于我的 URL模式
是 PATHINFO
的,所以,他的表现形式是:index.php/Module/Controller/Action
,具体例子是:index.php/ControlPannel/rootIndex
,表示执行的是 ControlPannel类的rootIndex方法
,但需要实现 当前位置 的这个功能的时候,大家也看到了我的 menu.php
中的内容,他里面的命名方法是 control_pannel 或 root_index
这种的,所以,为了在 menu.php
中找到对应的项,需要对路径做一个解析。这就是 parseUrlToEazyName
的作用了。
2. getCurPos 函数的作用:
,他有多个细化的功能点:
1. $find 函数,从 menu.php 中查找到当前提供功能对应的项。 2. $findParentTree 函数,有两个作用:返回当前功能所在链条的顶级项($top_module) + 生成当前功能的所有父级项($parent_list) 3. $findTopModuleNextTree 函数,实际上就是获取当前项的所有子项 4. $genHTML ,生成的是当前位置的HTML:`活动管理\喵喵抢购\活动列表\产品列表`
$findParentTree 他的具体功效是,其一,是无论你深入到一个模块的哪一个层级,顶级模块始终会展开(返回$top_module):
$findParentTree 他的具体功效是,其二,由于左边的菜单属于二级菜单(不能无限极分类),所以需要找到顶级模块的下一级对应模块,我想到办法是,找到底层项的所有父级项($parent_list),找到顶级项的所有子项,在在子项看存在哪个父项(绝对只有一个存在),那个就是对应的二级菜单项了,选中:
可能因为我是前端,这部分由前端做的比较多。
如果是SPA(当然问题中并不是),跟前端路由匹配。
如果不同的controller对应不同的layout的话,根据action区分显示就行。
如果没有layout这个概念,就根据url地址做正则匹配,或是根据controller和action来显示
你的实现方法很有趣,但并不实际。
首先你写的代码实在很难读,问问题又不写清楚,并不是所有人都有时间慢慢读你的代码来理解你的问题。
或许可以先试着说明一下 1. 自己的代码做了哪些事情,2. 目前实现了哪些功能,3. 期望改进或增加什么功能
首先先来针对你提出的两个函数提出一些个人的想法,接着再说点整体的点评吧!
想了想,$GLOBALS['controller']
应该是一组小驼峰法的字符串。
既然都用正则来处理了,是否该用更有效率的方法呢?
public function parseUrlToEazyName()
{
$converCamelStr = function ($str) {
//若首字母大写会被转成 _ 开头的字符串,这时候 ltrim 可以清除开头的 _
return ltrim(strtolower(preg_replace('/([A-Z])/', '_$1', $str)), '_');
};
$names = [];
$names['controller'] = $convertCamelStr($GLOBALS['controller']);
$names['action'] = $convertCamelStr($GLOBALS['action']);
return $names;
}
你写的实在太复杂了,注解又不够多,看了好多遍还是不懂你在做什么。
你自己解释一下你的代码实现了什么。
我想这里出现了几个问题:
你是通过 parent
这项的字符串来找出父级元素的,这样其实很危险,因为若要修改父级元素的 name
,就必须把所有子项的 parent
都改掉,既然都使用了巢状结构的 array,其实并不需要再特地表示出 parent
,因为这并不是一个 RMDB 的数据库。
$findParentTree()
当中,如果 while($parent)
中的 $result
为空会怎么样呢?=> 没做例外处理
你不觉得你的代码会跑很多次回圈吗?
分工做的并不好,我的意思是说,你或许需要了解一下 SOLID 原则。各个方法间的耦合度太高了。
类似下列的代码:
foreach ($list as $i) {
if (!empty($i)) {
// 做点复杂的事情
}
}
其实是可以改成这样的:
foreach ($list as $i) {
if (empty($i)) continue;
// 做点复杂的事情
}
如此一来就可以避免一个 if 的 block 太过肥大,也可以避免最后出现太多层的花括号。
对 PHP 的原生函数或许还不熟,加油!
你们在定义 功能API(系统功能层级) 时,是采用存入数据库表的方式(增删功能点不是很方便),还是用一个文件存起来(方便快捷,就是庞杂,看起来心累)??
通常是存数据库的,这样才能在后台修改。改文件的话会有很多问题。
跑很多次回圈??指的是很多次循环吗??我看过好几遍了...没有多余的步骤啊....估计我眼睛蒙圈了,没发现....大写的 SOS。。。。。
意思是说,在 getCurPos 里有太多的 foreach 了,只是判断个链接,还需要把整个 $manu 遍历好多次,这样很没效率。
由于这些功能API定义的链接都是固定的,如果有些模块需要带上动态参数的,例如:活动管理喵喵抢购活动列表产品列表 中,产品列表 这个功能的链接中是需要带上参数的(活动ID),查看的是某个活动的产品列表,这个问题怎么破??
我想这个问题大部分都是交给 Framework 在处理的,依照你目前的架构也很难修改。建议你还是找点轻量级的 Framework 来研究一下。