質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
foreach

foreachは、List・Collection・Arrayといったデータ構造の各要素に対して繰り返し処理を実行するために扱われる、制御構造の構文です。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

Q&A

解決済

1回答

1175閲覧

CakePHP foreachで特定のデータの表示

hiko_00001

総合スコア12

foreach

foreachは、List・Collection・Arrayといったデータ構造の各要素に対して繰り返し処理を実行するために扱われる、制御構造の構文です。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

0グッド

0クリップ

投稿2020/02/06 05:53

前提・実現したいこと

CakePHP3で出席管理システムを作成しています。
infoは1の場合出席、0の場合遅刻・早退、-2の場合欠席を表す。
subject_idが481052の時だけ選択し、日付を昇順にして1つ目のデータから2つ目のデータのように表示したいです。しかし、下記の試したことに記載している通りでしか表示できません。
2つ目の様に表示するためにどうしたらいいのか教えて頂けたら幸いです。

|id|perdon_id|subject_id|date|info|
|:--|:--:|--:|
|1|2160001|481052|2020-01-22|1
|2|2160001|481053|2020-01-22|1
|3|2160002|481052|2020-01-22|1
|4|2160003|481052|2020-01-22|0
|5|2160003|481053|2020-01-22|1
|6|2160001|481052|2020-01-15|1
|7|2160002|481052|2020-01-15|-2
|8|2160003|481052|2020-01-15|-2

person_id2020-01-15のinfo2020-01-22のinfo
216000111
2160002-21
2160003-20
### 発生している問題・エラーメッセージ
エラーメッセージ

該当のソースコード

php

1//contoroller 2{ 3 $first=$this->Attendances->find() 4 ->where(['Attendances.subject_id' => '481052']) 5 ->order(['Attendances.date' => 'ASC']) 6 ->contain(['Subjects']); 7 $this->set('first', $first); 8 9$second = $first; 10 $this->set('second', $second); 11 12} 13 14//veiw 15 16<table> 17<thead> 18<tr> 19 <th>学籍番号</th><th>2020-01-15のinfo</th><th>2020-01-22のinfo</th> 20</tr> 21</thead> 22 23<tr> 24<?php foreach($first->toArray() as $obj): ?> 25 <td><?=h($obj->person_id) ?></td> 26 27<?php foreach($second->toArray() as $sobj): ?> 28<?php if($sobj->person_id==$obj->person_id): ?> 29 <td><?=h($sobj->info) ?></td> 30<?php endif; ?> 31 32<?php endforeach; ?> 33 34 35</tr> 36<?php endforeach; ?> 37 38 39</table>

試したこと

上のソースコードで試したのですが以下のようになってしまいます。
上で示した2つ目の様に表示したいのですが無駄に同じ番号の情報が表示されてしまいます。

person_id2020-01-15のinfo2020-01-22のinfo
216000111
2160002-21
2160003-20
216000111
2160002-21
2160003-20

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

こまかい表示方式はさておき、何を表示したいのかといえば person_id別の各dateにおけるinfo ですね。
ということは、以下のようなデータ形式が取得できればよいはずです。

php

1[ 2 "{person_id}" => [ 3 "{date}" => "{info}", 4 ... 5 ], 6 ... 7] 8// 実際にはこんな感じ 9[ 10 "2160001" => [ 11 "2020-01-15" => 1, 12 "2020-01-22" => 1, 13 ], 14 "2160002" => [ 15 "2020-01-15" => -2, 16 "2020-01-22" => 1, 17 ], 18]

Tableのfindによって取得できる形は以下のようなものなので、これを変換します。

php

1// 本当はResultSet/Entityクラスの集合だが便宜的に配列として表記 2[ 3 ['id' => 6, 'person_id' => '2160001', 'subject_id' => '481052', 'date' => '2020-01-15', 'info' => 1], 4 ['id' => 7, 'person_id' => '2160002', 'subject_id' => '481052', 'date' => '2020-01-15', 'info' => -2]. 5 ... 6]

変換の方法は色々考えられるのですが、結果はResultSetなので Collection のメソッドが利用できます。なので、Collection::combine を利用して結果セットを整形するのが手っ取り早いでしょう。

php

1// In Controller 2$infoDataSet = $this->Attendances->find() 3 ->where(['Attendances.subject_id' => '481052']) 4 ->order(['Attendances.date' => 'ASC']) 5 // combineを利用して['{person_id}' => ['{date}' => '{info}']] の形式に整形する 6 ->combine('date', 'info', 'person_id'); 7 8$this->set('infoDataSet', $infoDataSet);

php

1// in template 2<table> 3<thead> 4<tr> 5 <th>学籍番号</th><th>2020-01-15のinfo</th><th>2020-01-22のinfo</th> 6</tr> 7</thead> 8 9<tr> 10<?php foreach ($infoDataSet as $personId => $row) : ?> 11 <td><?= h($personId) ?></td> 12 <?php foreach ($row as $date => $info) : ?> 13 <td><?= h($info) ?></td> 14 <?php enfforeach; ?> 15<?php enfforeach; ?> 16</tr> 17</table>

ただし、上記は全てのperson_idについてdateの欠落がない前提でのみ正しく表示できます。
person_id=2160001 のみが date=2020-01-08 のデータを持つような状態だと日付のズレが発生します。
それを解消するには、取得したデータセットの日付リストを取得して紐付ければよいでしょう。

php

1// In Controller / Model 2$resultSet = $this->Attendances->find() 3 ->where(['Attendances.subject_id' => '481052']) 4 ->order(['Attendances.date' => 'ASC']) 5 ->all(); 6 7// extractを利用して日付リストを取得する 8$dateKeys = array_unique($resultSet->extract('date')->toArray()); 9 10// combineを利用して['{person_id}' => ['{date}' => '{info}']] の形式に整形する 11$infoDataSet = $resultSet 12 ->combine('date', 'info', 'person_id') 13 // さらに dateKeys を利用して日付の抜けがないようにする 14 ->map(static function ($rows) use ($dateKeys) { 15 $formattedRows = []; 16 foreach ($dateKeys as $date) { 17 $formattedRows[$date] = isset($rows[$date]) ? $rows[$date] : ''; 18 } 19 20 return $formattedRows; 21 });; 22 23$this->set(compact('infoDataSet', 'dateKeys'));

php

1// in template 2<table> 3<thead> 4<tr> 5 <th>学籍番号</th> 6 <?php foreach ($dateKeys as $date) : ?> 7 <th><?= h($date) ?>のinfo</th> 8 <?php enfforeach; ?> 9</tr> 10</thead> 11 12<tr> 13<?php foreach ($infoDataSet as $personId => $row) : ?> 14 <td><?= h($personId) ?></td> 15 <?php foreach ($dateKeys as $date) : ?> 16 <td><?= h($row[$date]) ?></td> 17 <?php enfforeach; ?> 18<?php enfforeach; ?> 19</tr> 20</table>

なお、レコード件数によっては、データベース側でサブクエリなどを用いて希望の形式の形式を取得するSQLを発行したほうがよい場合もあります。上記はあくまでデータ件数がそこまで多くない場合の方法と考えてください。


追記 combineで日付オブジェクトを取り扱う

date フィールドが日付型だったということで、 combine('date', 'info', 'person_id') でIllegal offset typeエラーが生じていたようなのでその対処法です。combineは引数に関数を指定できます。これを利用してdateフィールド値を文字列として返します。

php

1combine(static function ($entity) { 2 // dateフィールドの型を判定して日時型であれば文字列フォーマットして返す 3 if ($entity->date instanceof \DateTimeInterface) { 4 return $entity->date->format('Y-m-d'); 5 } 6 7 return (string)$entity->date; 8}, 'info', 'person_id')

投稿2020/02/10 10:09

編集2020/02/17 05:41
nojimage

総合スコア959

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hiko_00001

2020/02/11 13:39

nojimage様 とても丁寧な回答ありがとうございます。 >>上記は、全てのperson_idについてdateの欠落がない前提でのみ正しく表示できます。 こちらのソースコードで試してみたのですが、 Illegal offset type Unable to emit headers とエラーが出てしまいます。 また、学籍番号だけが表示されておりinfoは表示されていません。 エラーについて調べてみて、自分で実行したのですが同じエラーがずっと表示されます。 もしよろしかったら、どういった原因が考えられるのか教えて頂きたいです。
nojimage

2020/02/12 06:01 編集

Illegal offset typeは配列のキー指定が正しくない場合に出ます。エラーメッセージに該当の行とスタックトレースが表示されているはずですので、そこから間違っている個所を特定しましょう。 想像するに、dateフィールドがFrozenTimeで返ってきているからかなと思いますがどうでしょうか。
hiko_00001

2020/02/17 05:08

nojimage様 返信が遅くなってしまい申し訳ありません。 dateフィールドををdateではなく、intに変えたらできました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問