• 締切済み

PHPでツリー表記したい

phpで掲示板を作ろうとしていまして、DBから取ったデータを木構造にしてツリー表示させたいです。 親のparent_idはnull, 子は親のidになります。 自己結合させてみたり、再帰処理を試そうとしているのですが自分のphpスキルが未熟で巧い実装ができません。表示方法を教えていただければと思います。 ↓理想です 親レコード1 └子レコード5 └子レコード4 └子レコード3 └子レコード2 └子レコード1

  • PHP
  • 回答数4
  • ありがとう数2

みんなの回答

  • Proof4
  • ベストアンサー率78% (151/192)
回答No.4

No.3の補足コメントに対して回答します。 こちらのロジック = public function display、データ構造 = 引数$treeに関するもの と解釈しましたが、質問の的を外していたらすみません。 $treeのデータ構造は、レコードの親子関係を表した下記のような配列です。処理が進むごとにTreeインスタンスの$treeそのものの構造が変化することはありません。 $tree = [  1448 => [   1449 => [],   1450 => [    1460 => [] // 3つ目の階層の一例   ],   ...  ],  1454 => [   1455 => [],   1456 => [],   ...  ] ] 質問文のデータベースの構造では親子関係による2つ目の階層までしかありませんが、id=1448にとって孫にあたるレコードの例としてid=1460のようなものも考えることができます。 No.2の回答のプログラムは階層がいくら増えても処理できるようになっています。これは再帰処理によるおかげです。上記の$treeを用いて簡単に処理を追ってみます。 $tree->display()でpublic function displayが呼ばれたとき$depth = 0であり、foreach ($tree as $i => $t) { ... }のループ処理では 1回目  $i = 1448  $t = [   1449 => [],   1450 => [    1460 => []   ]  ] の処理が行われます。 $i = 1448のデータを表示した後、$tの中身を表示するためdisplayメソッドを呼び出します。呼び出されたメソッドが終了するまで親の処理は進まないこと、親のメソッドと呼び出されたメソッドとでローカル変数の共有は一切ないことに注意してください。 呼び出されたdisplayメソッドでは$tree = $t、$depth = 1であり、ループ処理では 1回目  $i = 1449  $t = []  $i = 1449のデータを表示して終了 2回目  $i = 1450  $t = [   1460 => []  ]  $i = 1450のデータを表示した後、$tの中身を表示するためdisplayメソッドを呼び出し 呼び出されたdisplayメソッドでは$tree = $t、$depth = 2であり、ループ処理では 1回目  $i = 1460  $t = []  $i = 1460のデータを表示して終了 これで$depth = 2の処理が終了したので親の$depth = 1の処理に戻ります。 $depth = 1には残りの要素がないため、親の$depth = 0の処理に戻ります。 $depth = 0には次の要素id = 1454があるので同様にこの要素を処理していきます... といったように子要素があればどんどん深い階層へ潜っていき、末端の子要素に到達したら一つ上位の階層へ戻って残りの処理を続けています。$depthの数字に着目すると分かりやすいかもしれません。 やや長くなってしまいましたが、「データ構造がどのように変化するか」を「displayメソッドの引数がどのように変化するか」と捉えるならば、ある親$iを表示したときの子の構造を表す$tの階層を掘り下げていくように変化するといったところでしょうか。

  • Proof4
  • ベストアンサー率78% (151/192)
回答No.3

display関数に$depth引数を渡し、階層位置をもとに判別を行います。 // 木構造を再帰的に表示 public function display($tree = null, $depth = 0) {  // $treeの初期値セット  if ($tree === null) {   $tree = $this->tree;  }  echo '<table>';  foreach ($tree as $i => $t) {   $data = $this->data[$i];   // 罫線の設定   $line = $depth > 0 ? '└' : '';   // 内容の表示   echo <<<EOT   <tr>    <td><input type="checkbox"></td>    <td>{$line}{$i}</td>    <td><a href="">{$data['subject']}</a></td>    <td>{$data['post_user']}</td>    <td>{$data['created_at']}</td>   </tr> EOT;   // さらに下の階層があれば再帰的に表示   if (count($t) > 0) {    echo '<tr><td colspan="5">';    $this->display($t, $depth + 1);    echo '</td></tr>';   }  }  echo "</table>"; } ※OKWAVEの仕様上、インデントを全角スペースで表現しています。コピペの際はご注意ください。 最終的にどのようなHTMLの出力を希望されているのか把握できないため、個人的な理解にはなりますがtableが入れ子になるよう変更しました。 もし、tableをレイアウトのためだけに使用しているのならそれは避けるべきです。レイアウトはCSSで行いましょう(複数の要素を横並びにしたければgridレイアウトやflexレイアウトなどが利用できます。)

myprotein
質問者

補足

お世話になります! ご回答ありがとうございます! 無事希望通りのツリー表示ができ大変感謝いたします。 ここからは本題とは逸れてしまいますのでお手隙でしたらご回答を願いたいのですが、 こちらのロジックについては、処理が進む度にデータ構造はどのように変化していくのでしょうか?

  • Proof4
  • ベストアンサー率78% (151/192)
回答No.2

No.1の補足コメントに対して回答します。 id以外のデータも表示されるようNo.1のコードを改めて書き直しました。 コードの見通しが良くなるよう、すべての操作をTreeクラスにまとめ、disp_treeはdisplayメソッドとして定義しました。 class Tree {  private $data = [];  private $tree = [];  // データの整形と木構造の作成  public function __construct($data)  {   // id => 内容 となるようにデータ整形   foreach ($data as $d) {    $this->data[$d['id']] = $d;   }   // 木構造の作成   foreach ($data as $d) {    $hier = $this->getHierarchy($d['id']);    $tree_ref = &$this->tree; // $treeの参照    // 階層構造を上位から順にたどり、最後の階層に自身のidを追加    foreach ($hier as $h) {     if (!array_key_exists($h, $tree_ref)) {      $tree_ref[$h] = [];     }     $tree_ref = &$tree_ref[$h];    }   }  }  // あるidの親となるidを階層の浅い順から全て返す  private function getHierarchy($id)  {   $hierarchy = [$id];   do {    $parent_id = $this->data[$id]['parent_id'];    if ($parent_id == null) {     // 最上位階層に到達したら終了     break;    }    // 配列の先頭にidを追加    array_unshift($hierarchy, $parent_id);    $id = $parent_id;   } while (true);   return $hierarchy;  }  // 木構造を再帰的に表示  public function display($tree = null)  {   // $treeの初期値セット   if ($tree === null) {    $tree = $this->tree;   }   echo "<ol>";   foreach ($tree as $i => $t) {    $data = $this->data[$i];    echo "<li>";    // 内容の表示    echo <<<EOT     <ul>      <li>subject: {$data['subject']}</li>      <li>name: {$data['name']}</li>      <li>body: {$data['body']}</li>      <li>id: $i</li>     </ul> EOT;    // さらに下の階層があれば再帰的に表示    if (count($t) > 0) {     $this->display($t);    }    echo "</li>";   }   echo "</ol>";  } } $tree = new Tree($data); // 木構造の作成 $tree->display(); // 表示 ※OKWAVEの仕様上、インデントを全角スペースで表現しています。コピペの際はご注意ください。 順序付きリスト(ol)をネストすることで階層構造を表現しています。public function displayのEOTで囲まれた部分のHTMLは適宜編集してください。 現状では表示されていない項目(created_atなど)も、EOTの部分で{$data['created_at']}のようにすると出力されます。

myprotein
質問者

補足

再度回答いただきありがとうございます! そして再度補足をさせていただきたいのですが、display()のEOT周辺を変更してみたのですが、今のままですと質問時の理想通りにはいかず親レコードにも「└」がついてしまいます。子レコードだけに「└」がつくようにするにはどのように表記するのがよろしいのでしょうか? public function display($tree = null) { // $treeの初期値セット if ($tree === null) { $tree = $this->tree; } // echo "<ol>"; foreach ($tree as $i => $t) { $data = $this->data[$i]; echo "<table>"; // 内容の表示 echo <<<EOT <tr> <td><input type="checkbox"></td> <td>└{$i}</td> <td><a href="">{$data['subject']}</a></td> <td>{$data['post_user']}</td> <td>{$data['created_at']}</td> </tr> EOT; // さらに下の階層があれば再帰的に表示 if (count($t) > 0) { $this->display($t); } echo "</table>"; } // echo "</ol>"; } } 何度もすみません!ぜひよろしくお願いいたします!

  • Proof4
  • ベストアンサー率78% (151/192)
回答No.1

データベースが再帰クエリに対応しているかどうか(MySQLだと8~)で対応が大きく変わりますが、ここでは対応していないものとして回答します。 まず、データベースからのデータ取得を下記クエリで行います。 SELECT p2.* FROM posts p1 INNER JOIN posts p2 ON p1.id = p2.parent_id UNION ALL SELECT * FROM posts WHERE parent_id IS NULL ORDER BY parent_id ASC; (https://www.db-fiddle.com/f/vMdqv5DyXh6Hy2S1ChZmaA/2) その後、PHPで処理を行います。 上記のクエリでデータが $data = [  ['id' => 1448, 'parent_id' => null, 'name' => 'アメリカ', 'subject' => 'トランプ', 'body' => 'Thanks'],  ... ]; のように取得できているとき(一部カラム省略)、下記のコードで木構造として表示できます。 class Hierarchy {  private $data = [];  public function __construct($data)  {   // id => 内容 となるようにデータ整形   foreach($data as $d){    $this->data[$d['id']] = $d;   }  }  // あるidまでの親idを階層の浅い順から全て返す  public function get($id)  {   $hierarchy = [$id];   do {    $parent_id = $this->data[$id]['parent_id'];    if($parent_id == null){     // 最上位階層に到達したら終了     break;    }    // 配列の先頭にidを追加    array_unshift($hierarchy, $parent_id);    $id = $parent_id;   } while(true);   return $hierarchy;  } } function disp_tree($tree, $depth = 0){  $length = count($tree);  $cnt = 1;  foreach($tree as $i => $t){   $lines = '';   // 罫線表示   if($depth > 0){    $node = $cnt == $length ? '└' : '├';    $lines = str_repeat("│", $depth - 1) . " $node";    if($depth > 1){     $lines = ' ' . $lines;    }   }   echo "$lines $i\n";   $cnt++;   if($length > 0){    disp_tree($t, $depth + 1);   }  } } // 木構造の作成 $tree = []; $hierarchy = new Hierarchy($data); foreach($data as $d){  $hier = $hierarchy->get($d['id']);  $tree_ref = &$tree; // $treeの参照  foreach($hier as $h){   if(!array_key_exists($h, $tree_ref)){    $tree_ref[$h] = [];   }   $tree_ref = &$tree_ref[$h];  } } // 表示 disp_tree($tree); ※OKWAVEの仕様上、インデントを全角スペースで表現しています。コピペの際はご注意ください。 disp_treeをカスタマイズすることで投稿内容が表示できると思います。

myprotein
質問者

補足

ご回答いただきまして誠にありがとうございます! 返事が遅くなり申し訳ございません。色々いじったりしてみたのですが、 1行1行が何をされているのかが未だ理解できず、disp_treeをどのようにいじればid以外のデータ(nameやsubject)が表示されるようになるのかお教えいただけないでしょうか?

関連するQ&A

  • PHPとjQueryでツリー

    はじめまして。プログラムを独学で学んでいる者です。 今回初めて開発をしてみたのですが、どうしてもうまくいかないため こちらに投稿した次第です。 やりたいことは以下の通りです。 PHPとJQueryツリー構造 (完成図) 親 ■ツリー(1)  |  ■親データ(2)  | |  |  ∟■子データ(3)  |    |  |     ∟■孫データ(4)  |       |  |        ∟■孫2データ(5)  |  ■親データ  | |  |  ∟■子1データ  |  ■親データ  | |  |  ∟■子1データ  | |  |  | |   ∟■孫1データ  | |  |  ∟■子2データ  |  ■親データ  | |  |  ∟■子1データ  |    |  |     ∟■孫1データ  |       |  |        ∟■孫2データ  |  ■親データ 表示部分で親データの表示をだしつつ、子データのSQLをとばして子データの表示をだしつつ、孫データのSQLをとばす。 これを繰り返したい。 (1)まず親データのSQLをとばす。 (2)親データを一件取得したら、表示する。その親SQLで得られた結果をもとに子SQLをとばす。 (3)子データがあれば表示。(なければ2件目の親データを取得。) 子SQLで得られた結果をもとに、さらに子SQLを飛ばす。 (4)そこに結果があれば孫データとして表示。(なければ2件目の親データを取得) 得られた結果をもとに子SQLを飛ばす。 (5)結果があれば次の孫データとして表示。 結果が存在しなければ、親データに戻り・・・・繰り返し上記のように処理する。 自分で試行錯誤し、phpとjQueryでなんとか上図のように親--子--孫と出せるようになりました。 しかし、1件ごとに子・孫データが無いかSQLを飛ばして調べるので、非常に時間がかかります。 データが30件ほどあると表示するのに10分はかかります。 処理しきれずに途中で終わってしますこともシバシバ・・・・。 他の方法として考えたのが、親データがクリックされたら子SQLを飛ばして展開部分を表示するという方法です。 しかしクリックしたらメソッドを呼び出すという、phpの処理を分割することはできないとネットに乗っていました。 (私の調べ方に問題があったのかもしれませんが・・・) <ul> <li id=id>親データ   <ul>     <li id=id2>子データ      <ul>      <li id=id3>孫データ      </ul>  </ul> <li id=id>親データ2 ・ ・ ・ ↑のような構造で「<li id=id>親データ」がクリックされたときに、phpの子SQLをなげる仕組みなどは 可能なのでしょうか? それとも地道にSQLを投げて1件ずつ確認する方法しかないのでしょうか? プロの方の意見等、知恵をお借りしたいです。 ご教授よろしくお願い致します。

    • 締切済み
    • PHP
  • OracleのSQLでツリー表示取得

    いつもお世話になります。 SQLの質問なのですが、現在プロジェクトから離れていった方が書かれたSQLが違っていたので、いろいろと修正してみたのですがうまく値が取れないのでどこが間違っているかご相談したく 記述したSQL: SELECT * FROM XXXTBL WHERE keyID = 'aaa' START WITH PARENT_ID=0 CONNECT BY PRIOR PARENT_ID=ID ORDER BY ID; とりたい値:   id  項目名  PARENT_ID   0    親   null 2 A    0 3 B 0 4 B-1 3 とってきた値   id  項目名  PARENT_ID   0    親   null   0    親   null 2 A    0 3 B 0 となります。 いろいろと調べてみたのですが、何が違うのか判らず・・・・・ 現在は調査というレベルなので、テーブルには上記の4レコードのみが入っています。 keyIDは全レコードに同一のものが入っています。 もしかしたら、すごく基本なことなのかもしれませんが よろしくご教授お願いいたします。

  • PHPでの再帰を用いたツリー構造について

    PHPを勉強中の初心者です。PHPで、ツリー構造を再帰関数を用いて実装するプログラムを作成し、そのツリーを表示しようとているのですが、どうもよくわかりません。 このプログラムの挙動としては、以下でクリエイトしたTreeオブジェクトを、preorder(トップダウン、左側から)で出力させるもので、期待値は以下のとおりです。 (期待値) preorder: 1 2 4 5 3 6 7 (クリエイトされたオブジェクト) $myTree = new Tree(1, new Tree(2, new Tree(4), new Tree(5)), new Tree(3, new Tree(6), new Tree(7))); =========サンプルプログラム================================ #!/usr/bin/env php <?php class Tree { var $top; var $left; var $right;     #コンストラクタを定義はこれであっているでしょうか。 function Tree($top, $left, $right){ $this ->top = $top; $this ->left = $left; $this ->right= $right; }; function preorder( ){         #preorder() メソッドを実装方法がよくわかりません。 } } $myTree = new Tree(1, new Tree(2, new Tree(4), new Tree(5)), new Tree(3, new Tree(6), new Tree(7))); function printPreorder($tree) { echo "preorder:\n"; $tree->preorder(create_function('$v', 'echo "$v\n";')); } printPreorder($myTree); ?>

    • 締切済み
    • PHP
  • php5のコンストラクタをphp4仕様に変えたいです。

    今日からつかえるPHP5サンプル集を使ってphpの練習をしています。 この中にDBを使った掲示板のサンプルがあり、DBの出入力をクラスファイルで行っています。 そのphp5の__constract()を使っている部分を、php4に対応した形にしようとして躓いています。 以下、コードです。一部編集しています。 【Article.class.php】 class Article { //*この部分を追加 function Article() { $this->__construct(); }// function __construct(){ /* コンストラクタ */ } var $_id; …中略 function getId(){return $this->_id;} …中略 function setId($id){$this->_id=$id;} …中略 function getArticleInfo($cnt,$num){ $aryArt=array(); $db=DB::connect("mysql~"); $rs=$db->query("SELECT * FROM bbs_master WHERE parent=0 ORDER BY sdat DESC LIMIT ".$cnt.",".$num); while($row=$rs->fetchRow(DB_FETCHMODE_ASSOC)){ $objArt=new Article(); $objArt->setId($row['id']); …中略 //*これ以下を実行するとieで"ページを表示できません。"が出る $aryTmp=Article::getChildArticleInfo($objArt->getId()); $objArt->setChild($aryTmp); $aryArt[]=$objArt; } return $aryArt; } function getChildArticleInfo($id){ …各記事の子記事を抽出するプログラム } return $aryArt; } } PEARの読み込みやDB接続は成功しているのは確認済みです。 上記コード中の再帰的にArticle::getChildArticleInfo($objArt->getId());を呼び出しているところで実行できなくなるようです。 これをphp4でも動作するように変更できないでしょうか? よろしくお願いします。

    • ベストアンサー
    • PHP
  • PostgresSQL8.4でツリー上に取得したい

    PostgresSQL8.4でツリー上に取得したい こんにちわ。 PostgresSQLでツリー上にデータを取得したいのですが、思ったように取得できないのでご教授をお願いします画像の「取得したい順番」。 データはid,name,parentとの構造になっています。 データは親子構造になっており、親の下に子が並ぶようにしたいんです。 子は親のidをparentに持っています。 元のデータは画像の「元データ」になっています。 現状再起SQLでデータを取得しid or parentでソートを掛けているのですが、意図した通りに取得できません画像の「取得出来る順番」。 そもそも再起SQLでできるのか、相関サブクエリを使うのか・・・SQLのみでできるのかすら分からなくなってきました・・・。 何か画像の「取得したい順番」通りに取得する方法はないでしょうか。 ヒントだけでもいいので、ご教授をお願いします。

  • Perlでツリー構造を生成

    質問です。 Perlで以下のような処理をしたい場合どういうコードを書けばよいでしょうか 処理の内容を簡単に説明しますと、 店舗情報を格納しているDBがあるとします。 DBから取得した、X件の情報を、子がN店ずつのツリー構造にした上で、DBの取得してきたレコードに親の店舗IDなりを更新するスクリプトです。 親が1店舗からはじまり、子がN店の構成です、子はその下に孫としてまたN店もちます。 分かりづらいですね・・・もう少し細かく説明します。 1.DBから店舗情報を取得してくる 2.10件の店舗があったとする、IDがshop1,shop2,shop3...shop10と続く 3.その中からランダムで一件決める。今回はshop3とする 4.ランダムでshop3の子をN件決める。今回はshop3の子をshop1とshop2とする(N = 2とする) 6.今度はshop1とshop2の子を決める(残りのshop4~10より決める) 7.4の処理に戻り、孫世代、ひ孫世代を決定する 8.端数は一人っ子ができても問題なし、子なしに落ち着くまで繰り返す 9.すべての店をチェックして親をレコードに更新する。 といったような流れの処理になります。 処理の流れは浮かんでいるのですが、Perlでどう書けばいいのか四苦八苦しています。 (自分のIDと親のIDを結びつけつつ保持し続けておくのがとくにわかりません・・・) 以上になりますがよろしくおねがいします

  • 掲示板用のDB設計

    PHPとMySQLを組み合わせた掲示板を作成しています。 ただ単にデータを挿入し、投稿日が新しい順に表示するだけではなく、各投稿に対して投稿、返信が出来る掲示板のDB設計に悩んでいます。 (一般的に言う ツリー型 です。) 各レコードにIDを割り振り、あるレコードに対し返信すると、そのレコードのIDが返信レコードに割り振られます。 ただ、この場合、返信に対する返信に元のレコードのIDを割り振ることは出来ず、困っています。 また、ツリー式の掲示板はどのようにしてデータをツリー形式で最新順にSELECTしているのでしょうか?

    • ベストアンサー
    • MySQL
  • phpでの再帰関数を利用したツリー表示

    ・やりたいこと 一つのIDの下にいくつの階層があるかカウントする。 コードは既にできているのですが、いざ運用しようとしたときに Fatal error: Maximum function nesting level of '100' reached, > aborting! と出てしまいました。 色々調べた結果、phpでは100を超える再帰呼び出しはできないというのを見つけました。 本当にできないのでしょうか?他にやる方法はあるのでしょうか? php側の設定で何とかなるもんなんでしょうか? ~~~以下DB構造~~~~~~~~~~~~~~~~~~~~~~ id(int) | lid(int) | rid(int) ~~~以下コード~~~~~~~~~~~~~~~~~~~~~~ Class tree_show { var $i_ = 0; var $ar_ = array(); function findid2($colm) { foreach($colm as $value){ $value = $this->findid($value); } } function findid($id) { if(!empty($id)) { $rs = mysql_query("select * from tree_all where id='".$id."'"); $row = mysql_fetch_array($rs); $dim = $row['id']; $this->i_++; $this->addarray($dim); $arr = array($row['lid'],$row['rid']); $this->findid2($arr); } } function printid() { return $this->i_; } function addarray($dim) { array_push($this->ar_,$dim); } function printarray() { return $this->ar_; } } $id= $_REQUEST[id]; $db = mysql_connect(); $sql = "select * from tree_all where id = '".$id."'"; $rs = mysql_query($sql,$db); $row = mysql_fetch_array($rs); $class = New tree_show(); $class->findid($row['lid']); echo $left = $class->printid(); print_r($class->printarray()); echo "<br>"; $class2 = New tree_show(); $class2->findid($row['rid']); echo $right = $class2->printid(); echo "<br>"; print_r($class2->printarray());

    • ベストアンサー
    • PHP
  • phpについて

    phpのfor文だと思うのですが、エラー(そもそも表示されない)ので教えてください。 sqlでデータを取得し 今まで下記のようにしていましたが、10こ表示したいです。 a タグの中身についても変化させたくて、  ループ1回目はa href="1.php" ループ2回目はa href="2.php" ループ3回目はa href="3.php" いこう10まで (ファイル名は通し番号になっていて、1~10.phpになっています。) phpのプログラムは <?php // 変数の初期化 $db = null; $sql = null; $res = null; $row = null; $db = new SQLite3("test.db"); // データの取得 $sql = 'SELECT * FROM human where country="japan"'; $res = $db->query($sql); <?php while( $row = $res->fetchArray() )(これ10回ループさせたいを) { echo '<ul>' . '<a href="ここを変化させたい。" target=_brank>'. '<li>' . $row[0] . '<span>' , $row[1].'<span>', $row[2].'<span>',$row[3].'<span>', $row[4]. '<span>', $row[5].'<span>' , '<img src="$row[6]">'.'<span>' . '</li>'. '</a>' . '</ul>'; } よろしくお願いいたします。

    • ベストアンサー
    • PHP
  • Access2000によるツリー的なデータベース

    現在、IDと親IDのようなフィールドを設けて親子関係を作っています。 親データをクリックすると子データが表示される、と言う感じです。 データベースは以下のような感じになっています。 id|p_id|name 1| 0|aaa 2| 1|bbb 3| 1|ccc 4| 2|eee 以上のデータベースを使って、コンボボックスにツリー上で表示させたいのですが、何か良い方法はありますでしょうか aaa +bbb ++eee +ccc 見たいな感じです。

専門家に質問してみよう