PHPでDBから読み出したデータを、HTMLの表(table)出力するスマートな方法
解決済
回答 3
投稿
- 評価
- クリップ 4
- VIEW 9,869
前提・実現したいこと
要約すると「DBから取得した配列データを加工・集計して使いたいが、何度もループで回したり再度配列に入れ直して処理するのは避けたい」
PHPでDBから読みだしたデータを、HTMLのtableに吐き出します。
この際、HTMLのテーブルで縦に同項目があった場合はrowspanで結合するようにしたいです。(下記ソースの「大分類」を同じ名前で結合)
また、縦一列全ての欄が空白の場合は、その列ごと表示しないようにしたいです。(下記ソースの「備考」列を消す)
以下、行数・列数を簡素化したデータになります。
<table>
<thead>
<tr>
<th>大分類</th>
<th>小分類</th>
<th>品名</th>
<th>価格</th>
<th>備考</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">分類A</td>
<td>小分類1</td>
<td>商品A</td>
<td>10,000</td>
<td></td>
</tr>
<tr>
<td>小分類2</td>
<td>商品B</td>
<td>30,000</td>
<td></td>
</tr>
<tr>
<td>大分類B</td>
<td>小分類3</td>
<td>商品C</td>
<td></td>
</tr>
</tbody>
</table>
発生している問題・エラーメッセージ
通常ですと、読みだしたデータをforeachループで一行ごと読みだしながら処理を行いますが、今回rowspanで結合するという事で、数を数える必要性があります。
1ループで処理できるスマートな方法が、考え付きません。全データをもう多次元配列に入れ直してその列の全項目を再度上から下までチェックさせる方向の処理は避けたいです。(中学生の頃はfor文二つ重ねてループさせたりしてましたが )
※見た目を気にする案件で、同じ項目が縦に並んだり「〃」等の記号での表示はできないものとお考え下さい。
現在の単純な処理
実際はこれより列が多くここにそれぞれの値を変換したり処理が追加されます。
$html = '';
foreach($arrData as $data) {
$line = '<tr>';
$line .= '<td>' . $data['CATEGORY1'] . '</td>';
$line .= '<td>' . $data['CATEGORY2'] . '</td>';
$line .= '<td>' . $data['PRICE'] . '</td>';
$line .= '<td>' . $data['BIKO'] . '</td>';
$html .= $line . '</tr>'; // $htmlに1行分追加
}
$html .= '<table>' .$html. '</table>';
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+2
値が同じ項目を探すループと出力するループの2つは必要だと思います。コードは適当ですが、考え方の参考になれば。
// $arrData = 元データ;
$tmpArray = [];
$checkData = [ 'a' => false, 'b' => false, 'c' => false ]; // 行フラグ保存用
$countData = [ 'a' => [ '', 0 ], 'b' => [ '', 0 ], 'c' => [ '', 0 ] ]; // 「何個同じ項目があるか」を数える用
// 逆順にする
$reversedArray = array_reverse( $arrData );
// データを数えながら作成する
foreach( $reversedArray as $data ) {
$tmp = [];
$a_text = '';
$a_count = $countData[ 'a' ][ 1 ];
if ( isset( $data[ 'a' ] ) ) {
$a_text = $data[ 'a' ];
if ( $countData[ 'a' ][ 0 ] === $a_text ) {
$a_count += 1;
} else {
$a_count = 0;
}
$checkData[ 'a' ] = true; // 1個でも入っていたら行を表示
}
$countData[ 'a' ][ 0 ] = $a_text;
$countData[ 'a' ][ 1 ] = $a_count;
$tmp[ 'a' ] = [ $a_text, $a_count ]; // [ 値, 同じ値のカウント ]
/*
略
*/
$tmpArray[] = $tmp;
}
echo '<table>';
array_reduce(
array_reverse( $tmpArray ) // 戻す
, function ( $carry, $key ) use ( $checkData ) {
echo '<tr>';
// 行にデータがあって、前の行のa_countが「0」のものを出力する
if ( $checkData[ 'a' ] && $carry[ 'a' ] === 0 ) {
$tmp = '<td';
if ( $key[ 'a' ][ 1 ] > 0 ) {
$tmp .= ' rowspan="'.( $key[ 'a' ][ 1 ] + 1 ).'"';
}
echo $tmp.'>'.$key[ 'a' ][ 0 ].'</td>';
}
/*
略
*/
echo '</tr>';
$carry[ 'a' ] = $key[ 'a' ][ 1 ];
return $carry;
}
, [ 'a' => 0, 'b' => 0, 'c' => 0 ]
);
echo '</table>';
// 未テスト
【PHP: array_reverse - Manual】
http://php.net/manual/ja/function.array-reverse.php
【PHP: array_reduce - Manual】
http://php.net/manual/ja/function.array-reduce.php
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
checkベストアンサー
0
DB 定義、サンプルデータ
CREATE TABLE `sample` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`category` int(11) NOT NULL DEFAULT '0',
`sub_category` int(11) DEFAULT NULL,
`name` varchar(32) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `sample` (`id`, `category`, `sub_category`, `name`)
VALUES
(1,1,11,'name1'),
(2,1,12,'name2'),
(3,1,13,'name3'),
(4,2,21,'name4'),
(5,2,22,'name5'),
(6,3,30,'name6');
サンプルコード
<?php
ini_set('display_errors', true);
error_reporting(E_ALL);
$dsn = 'mysql:host=localhost;dbname=sample;charset=utf8;';
$username = 'root';
$password = 'password';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$oPdo = new PDO($dsn, $username, $password, $options);
$sql = 'SELECT `id`, `category`, `sub_category`, `name` ';
$sql .= 'FROM `sample` ';
$sql .= 'WHERE 1 ';
$sql .= 'ORDER BY `category` ASC, `sub_category` ASC ';
$stmt = $oPdo->prepare($sql);
$stmt->execute();
$rows = $stmt->fetchAll();
$formattedArray = [];
foreach ($rows as $row) {
$category = $row['category'];
$formattedArray[$category][] = $row;
}
?>
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #CCC;
}
</style>
</head>
<body>
<div>
<table>
<thead>
<tr>
<th>category</th>
<th>subcategory</th>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<?php foreach ($formattedArray as $category => $rows) : ?>
<tr>
<td rowspan="<?= count($rows); ?>"><?= $category ?></td>
<?php foreach ($rows as $i => $row) : ?>
<?php if ($i > 0) : ?>
<tr>
<?php endif; ?>
<td><?= $row['sub_category'] ?></td>
<td><?= $row['id'] ?></td>
<td><?= $row['name'] ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</body>
</html>
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
kei344さんのarray_reverse
を使うという発想が目から鱗だったので、練習がてらしてみました。
$arrData = [["c"=>"分類1","v"=>"値1"],["c"=>"分類1","v"=>"値2"],["c"=>"分類2","v"=>"値3"]];
$reversedArray = array_reverse( $arrData );
$beforeCategory=null;
$rowspan=1;
$tmp="";
foreach($reversedArray as $data){
if($beforeCategory!=null){
if(strcmp($beforeCategory, $data['c'])!=0){
$tmp = "<tr>".($rowspan>1 ? "<td rowspan=\"".$rowspan."\">" : "<td>").$beforeCategory."</td>".$beforeLine."</tr>\n".$tmp;
$rowspan=1;
}else{
$tmp = "<tr>".$beforeLine."</tr>\n".$tmp;
$rowspan++;
}
}
$beforeCategory = $data['c'];
$beforeLine ="<td>".$data['v']."</td>";
}
$tmp = "<tr>".($rowspan>1 ? "<td rowspan=\"".$rowspan."\">" : "<td>").$beforeCategory."</td>".$beforeLine."</tr>\n".$tmp;
echo "<table>\n";
echo $tmp;
echo "</table>";
また、縦一列全ての欄が空白の場合は、その列ごと表示しないようにしたいです。(下記ソースの「備考」列を消す)
これに関しては「全てのデータを確認せずに全てのデータで空欄かどうかを判別する」というのがそもそも無理な話なので、結局確認用と出力用で2回ループを回すのが一番スマートな方法だと思います。
(データ取得時にSELECT句で副問い合わせなどして強引に何とかする手があるかもしれませんが・・・)
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
2017/03/10 11:28
試した事のなかった方法で、他の箇所でも活用させていただきました。