ロゴ
HOME > PHPの便利な小技 > 親子関係にある配列をネスト(入れ子)してリスト表示する

親子関係にある配列をネスト(入れ子)してリスト表示する

2015年06月28日

社員リストやサイトマップなど、データベースにバラバラに登録されているデータを部署ごとに並べ替えかつ、親子関係になるよう多重連想配列にしたいケースが有ります。

サンプル

sqlのみだとlpadでスペースを入れて表現する方法であったり、WEB上でデザインするにはちょっと微妙な方法しか無いようです。

サンプルはSqliteで作った簡単なスタッフリストです。

DROP TABLE IF EXISTS "staff";
CREATE TABLE "staff" ("id" INTEGER PRIMARY KEY  NOT NULL ,"parent" INTEGER,"sort" INTEGER DEFAULT (null) ,"name" TEXT);
INSERT INTO "staff" VALUES(1,NULL,1,'山田一郎');
INSERT INTO "staff" VALUES(2,1,2,'山田二郎');
INSERT INTO "staff" VALUES(3,1,3,'山田三郎');
INSERT INTO "staff" VALUES(4,2,4,'山田四郎');
INSERT INTO "staff" VALUES(5,NULL,1,'田中一郎');
INSERT INTO "staff" VALUES(6,5,2,'田中二郎');
INSERT INTO "staff" VALUES(7,NULL,1,'佐藤一郎');
INSERT INTO "staff" VALUES(8,7,2,'佐藤二郎');
INSERT INTO "staff" VALUES(9,8,3,'佐藤三郎');
INSERT INTO "staff" VALUES(10,9,4,'佐藤四郎');
INSERT INTO "staff" VALUES(11,NULL,1,'鈴木一郎');

parentは親となる列のidをインサートします。一応sortも含める事で表示順序をソートしています。後は名前。

 データを呼び出す

  $stmt = $db->prepare("SELECT * FROM staff ORDER BY sort");
  $stmt->execute();
  $array = array();
  while ($row = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT )) { 
    $array[$row['id']] = $row;
  }

結果をループし、自分のIDをキーとした配列に組み替えます。

親子構造の配列にする

  $nests = array();
  foreach($array as $row) {
    //親idがあれば親の配列にchildrenというキーで加える
    if($row['parent']) {
      $array[$row['parent']]['children'][] = &$array[$row['id']];
    }
    //親の場合はそのまま
    else{
      $nests[$row['id']] = &$array[$row['id']];
    }
  }

parentに数値があれば、そのIDの配下にchildrenというキーで格納していきます。

この段階で階層構造で配列化されます。

配列をLIでリスト化する

/*
 * 配列をリストにする
 */
  $list = '';
  if($nests) {
    $list .= "<ul>";
    $list .= nest($nests);
    $list .= "</ul>";
  }
  
  echo $list; exit;

/*
 * 再帰処理
 */
   function nest($nest) {
    global $li;

    foreach($nest as $ns) {
      if(!empty($ns['children'])) {
        $li .= "<li>{$ns['name']}";
        $li .= "<ul>";
            nest($ns['children']);
        $li .= "</ul>";
        $li .= "</li>";
      }else{
        $li .= "<li>{$ns['name']}</li>";
      }
    }
    return $li;
  }

↑※Cryonはどうもタグを消したり微妙なのでクリックしてソース見て下さい。

関数nestを呼び出し、children(子配列)がある場合は親の下にULをセットしながら再帰します。

リスト化出来てしまえば、後はCSSレベルでデザインするだけ。

但し、SQLでループ、ネストする為にループ、リスト化する為にまたループ、とあまりスマートでは有りません。

array_walkを使ってどうにかシンプルに纏められないかと考えましたが、良い方法が見つかりませんでした。ループ処理が多いので、大量のデータ処理だと遅くなるかもしれませんが、300人程度の社員リストを処理しても殆ど一瞬でした。そもそもこの手のリストを作る際に、数千なんてレベルのデータを処理する事はあまり無いでしょうし、問題無いかと。

そうそう変化の無いデータであれば、キャッシュしてしまえば良いですし。