PHP常用函數 無限級菜單/許可權樹設計與實現

  • 2019 年 12 月 24 日
  • 筆記

導語

在開發中我們經常會遇到:導航菜單、部門菜單、許可權樹、評論等功能。 這些功能都有共同的特點:

  1. 有父子關係
  2. 可無限遞歸

以導航菜單為例, 將導航菜單設置為動態的, 即從動態載入菜單數據。

資料庫設計

CREATE TABLE `SuperUserMenus` (    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',    `pid` int(11) NOT NULL COMMENT '父級ID',    `order` int(11) NOT NULL DEFAULT '0' COMMENT '菜單排序',    `title` varchar(100) NOT NULL COMMENT '菜單標題',    `controller` varchar(100) DEFAULT NULL COMMENT '控制器名稱',    `method` varchar(100) DEFAULT NULL COMMENT '方法名稱',    `ishidden` int(1) NOT NULL DEFAULT '0' COMMENT '是否隱藏:0正常顯示,1隱藏',    `status` int(1) NOT NULL DEFAULT '0' COMMENT '狀態:0正常,1禁用',    PRIMARY KEY (`id`)  ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

在這裡用作分級的表示欄位就是pid,用作查找對應父ID,一個菜單一方面自己可以具有父ID,可以有一個父級菜單,另一方面可以用作父級,子級來定義該父級ID,這樣就可以設計無限級菜單,這樣設計好處是可以父子級別菜單同表存儲,便於遍歷顯示,但是存儲在表中的數據只有對應邏輯,不好在資料庫中維護及查看,需要寫一下演算法進行可視化遍歷。

數據封裝

使用演算法進行封裝讀取之後,使得父子關係一目了然,包含關係,如下顯示:

array(8) {    [0] => array(9) {      ["id"] => int(1)      ["pid"] => int(0)      ["order"] => int(0)      ["title"] => string(18) "超級用戶管理"      ["controller"] => string(0) ""      ["method"] => string(0) ""      ["ishidden"] => int(0)      ["status"] => int(0)      ["children"] => array(1) {        [0] => array(8) {          ["id"] => int(3)          ["pid"] => int(1)          ["order"] => int(0)          ["title"] => string(18) "超級用戶列表"          ["controller"] => string(14) "admin"          ["method"] => string(5) "index"          ["ishidden"] => int(0)          ["status"] => int(0)        }      }    }  }

演算法轉換

在這裡使用ThinkPHP5這個框架來進行編寫,雖然語言及框架不同,但是思路及使用演算法函數都是一樣的,首先將對應用戶下菜單json存儲數組讀取出並進行索引處理:

/**   * 動態菜單顯示操作   * @return string   * @throws DataNotFoundException   * @throws ModelNotFoundException   * @throws DbException   */  public function index()  {      $Super_id = 1;      $menus = false;      $role = Db::table('SuperUser')->where('Super_id', $Super_id)->select();      $role = $role[0];      if ($role) {          $role['rights'] = (isset($role['rights']) && $role['rights']) ? json_decode($role['rights'], true) : [];      }      if ($role['rights']) {          $menus = Db::query('select * from SuperUserMenus where id in(' . implode(',', $role['rights']) . ') and ishidden=0 and status=0');            $menus = $this->_array_column($menus, null, 'id');          $menus && $menus = $this->gettreeitems($menus);      }        return json_encode($menus);  }

之後將ID作為二維數組中的唯一索引,這裡使用array_column函數,由於這個函數只支援PHP5.5+版本,低版本不支援,我將此函數放在此處:

/**   * PHP5.5+ array_column函數   * @param null $input   * @param null $columnKey   * @param null $indexKey   * @return array|bool|null   */  public function _array_column($input = null, $columnKey = null, $indexKey = null)  {      // Using func_get_args() in order to check for proper number of      // parameters and trigger errors exactly as the built-in array_column()      // does in PHP 5.5.      $argc = func_num_args();      $params = func_get_args();      if ($argc < 2) {          trigger_error("array_column() expects at least 2 parameters, {$argc} given", E_USER_WARNING);          return null;      }      if (!is_array($params[0])) {          trigger_error(              'array_column() expects parameter 1 to be array, ' . gettype($params[0]) . ' given',              E_USER_WARNING          );          return null;      }      if (!is_int($params[1])          && !is_float($params[1])          && !is_string($params[1])          && $params[1] !== null          && !(is_object($params[1]) && method_exists($params[1], '__toString'))      ) {          trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);          return false;      }      if (isset($params[2])          && !is_int($params[2])          && !is_float($params[2])          && !is_string($params[2])          && !(is_object($params[2]) && method_exists($params[2], '__toString'))      ) {          trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);          return false;      }      $paramsInput = $params[0];      $paramsColumnKey = ($params[1] !== null) ? (string)$params[1] : null;      $paramsIndexKey = null;      if (isset($params[2])) {          if (is_float($params[2]) || is_int($params[2])) {              $paramsIndexKey = (int)$params[2];          } else {              $paramsIndexKey = (string)$params[2];          }      }      $resultArray = array();      foreach ($paramsInput as $row) {          $key = $value = null;          $keySet = $valueSet = false;          if ($paramsIndexKey !== null && array_key_exists($paramsIndexKey, $row)) {              $keySet = true;              $key = (string)$row[$paramsIndexKey];          }          if ($paramsColumnKey === null) {              $valueSet = true;              $value = $row;          } elseif (is_array($row) && array_key_exists($paramsColumnKey, $row)) {              $valueSet = true;              $value = $row[$paramsColumnKey];          }          if ($valueSet) {              if ($keySet) {                  $resultArray[$key] = $value;              } else {                  $resultArray[] = $value;              }          }      }      return $resultArray;  }

最後將數組進行樹形分類,將同屬於一個父級ID的子元素歸類至children下:

/**   * 子節點分級顯示   * @param $items   * @return array   */  private function gettreeitems($items)      {          $tree = array();          foreach ($items as $item) {              if (isset($items[$item['pid']])) {                  $items[$item['pid']]['children'][] = &$items[$item['id']];              } else {                  $tree[] = &$items[$item['id']];              }          }          return $tree;      }

結語

無限級菜單/許可權樹設計原理就是使用pid來進行區分父子關係,就是將二維數組進行樹形劃分來實現。