CakePHP5入門【WebAPI編④】管理用ページ①
B美
|
1.管理用ページのコントローラーとビューを作成
2.BASIC認証を導入 3.Excelファイルのインポート処理を実装 |
…って感じね
A子
まずは「MATE端末」上でbakeするよ
|
cd html/numapp[Enter]
bin/cake bake controller admin[Enter] |
出来上がった「src/Controller/AdminController.php」の中身についても「index」メソッドを残して全削除ね
C菜
(「templates/Top/index.php」を流用です~)
B美
| http://192.168.1.205/numapp/admin |
まだ認証機構を作ってないから、普通にアクセスできるわね
A子
C菜
| bin/cake bake middleware HttpBasicAuth[Enter] |
C菜
あ、「process」メソッドの中身については、前回のチャットアプリのやつを流用しましたよ~
(メソッド単位で認証可否を切り分ける必要がないので、少し簡単になりました~)
A子
こういうときは以前のソースをカンニングだね(苦笑)
(「authapp」プロジェクトの「src/Controller/UsersController.php」を参照するよ)
・・・
うん、わかった
「src/Controller/AdminController.php」の中に「initialize」メソッドを追加するのと、「namespace」の下に「use」の一文を追加だね
C菜
A子
絶対に憶えてられないよ(苦笑)
B美
さて、それじゃ最後の一つ「Excelファイルのインポート」を実装するよ
C菜
B美
管理用ページ(index.php)の中に直接フォームを記述して良いわよ
(だって、これ以外の機能を実装しないからね)
A子
・・・
こんな感じでどうかな?
(インポート処理については「import」メソッドを実装する予定)
↓
C菜
B美
とりあえずは、POST送信されたものを受け取る記述を書いてみなさい
(以前のソースの流用で良いから…)
A子
|
public function import()
{ if ($this->request->is('post')) { $upload = $this->request->getData('upload'); //オリジナルのファイル名を取得 $original_filename = $upload->getClientFilename(); $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); //拡張子 if ($extension == 'xlsx') { //拡張子チェックOK } } } |
B美
では、Excelファイルを取り扱うためのクラスを教えるわ
それが『Spreadsheet』クラスよ
C菜
B美
|
cd html/numapp[Enter]
composer require phpoffice/phpspreadsheet[Enter] |
もちろん、上記を「MATE端末」上で実行してね
んで、use文は以下の通り
|
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date; |
これを「AdminController.php」の先頭に記述します
A子
『IOFactory』クラスと『Date』クラスだよね
B美
読み込みだけなら『IOFactory』クラスになるの
(ちなみに、『Date』クラスはシリアル値であるExcelの日付型データを変換するために必要)
C菜
B美
(下の赤字が追加した箇所ね)
|
$original_filename = $upload->getClientFilename();
if ($original_filename == '') { $this->Flash->error('ファイルが選択されていません。'); return $this->redirect(['action' => 'index']); } $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); //拡張子 |
で、Excelファイルの読み込みについては、こうするの
|
$reader = IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true); $tmpPath = $upload->getStream()->getMetadata('uri'); $spreadsheet = $reader->load($tmpPath); |
そうしたら、まずはアクティブなシートを指定してから、それを配列に変換しましょう
(ほかの方法もあるけど、配列化するのが一番簡単だと思う)
|
$sheet = $spreadsheet->getActiveSheet();
$data = $sheet->toArray(null, true, true, true); |
ここで注意してほしいのが、toArrayメソッドの第4引数(上記の赤字部分)ね
・true → 列番号をキーとした連想配列
・false → 普通の配列(ゼロスタート)
だから、上記の例だと['A']や['B']みたいな形になるわ
(falseならば[0]や[1]ってこと)
A子
B美
これを普通の日付表示に変更するのに『Date』クラスを使うのよ
例えば、「$lottery_date」という変数にシリアル値が入っているとしましょう
まず、数値であるかどうかのチェックを入れて、あとは『Date』クラスの「excelToDateTimeObject」メソッドを呼び出せばOK
|
if (is_numeric($lottery_date)) {
$lottery_date = Date::excelToDateTimeObject($lottery_date); $lottery_date_str = $lottery_date->format('Y年m月d日'); } |
あ、このとき注意しなければいけないのが、「$lottery_date」に入っている値が整数値でなければならない…ってこと
(文字列を引数に渡すとエラーになるわよ)
C菜
|
public function import()
{ if ($this->request->is('post')) { $cnt = 0; //処理件数 $upload = $this->request->getData('upload'); //オリジナルのファイル名を取得 $original_filename = $upload->getClientFilename(); if ($original_filename == '') { $this->Flash->error('ファイルが選択されていません。'); return $this->redirect(['action' => 'index']); } $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); //拡張子 if ($extension == 'xlsx') { //Excelファイルの読み込み $reader = IOFactory::createReader('Xlsx'); $reader->setReadDataOnly(true); $tmpPath = $upload->getStream()->getMetadata('uri'); $spreadsheet = $reader->load($tmpPath); //配列に変換して処理 $sheet = $spreadsheet->getActiveSheet(); $data = $sheet->toArray(null, true, true, true); foreach ($data as $row) { $lottery_time = intval($row['A']); //実施回 $lottery_date = intval($row['B']); //抽選日(intvalで数値化しないとexcelToDateTimeObjectでエラーになる) if (is_numeric($lottery_date)) { $lottery_date = Date::excelToDateTimeObject($lottery_date); //Excelのシリアル値を日付型に変換 $lottery_date_dt = $lottery_date->format('Y/m/d'); //抽選日(日付型) $lottery_date_str = $lottery_date->format('Y年m月d日'); //抽選日(文字列型) $lottery_date_year = intval($lottery_date->format('Y')); //抽選日の年 $lottery_date_month = intval($lottery_date->format('m')); //抽選日の月 $lottery_date_day = intval($lottery_date->format('d')); //抽選日の日 $lottery_week_int = 0; //抽選日の曜日(整数型) $lottery_week_str1 = ''; //抽選日の曜日1(文字列型) $lottery_week_str2 = ''; //抽選日の曜日2(文字列型) $lottery_rokuyo_int = 0; //抽選日の六曜(整数型) $lottery_rokuyo_str = ''; //抽選日の六曜(文字列型) } $num3_str = $row['C']; //ナンバーズ3の当選番号(文字列型) $num3_int = intval($num3_str); //ナンバーズ3の当選番号(整数型) $num3_place1 = intval($num3_int % 10); //ナンバーズ3の当選番号の一の位 $num3_place10 = intval($num3_int % 100 / 10); //ナンバーズ3の当選番号の十の位 $num3_place100 = intval($num3_int / 100); //ナンバーズ3の当選番号の百の位 $num4_str = $row['D']; //ナンバーズ4の当選番号(文字列型) $num4_int = intval($num4_str); //ナンバーズ4の当選番号(整数型) $num4_place1 = intval($num4_int % 10); //ナンバーズ4の当選番号の一の位 $num4_place10 = intval($num4_int % 100 / 10); //ナンバーズ4の当選番号の十の位 $num4_place100 = intval($num4_int % 1000 / 100); //ナンバーズ4の当選番号の百の位 $num4_place1000 = intval($num4_int / 1000); //ナンバーズ4の当選番号の千の位 $cnt++; } } $this->Flash->success($cnt.'件の処理を行いました。'); return $this->redirect(['action' => 'index']); } } |
私が書いたのは、上の赤字部分だけですけどね~(苦笑)
あと、曜日と六曜の求め方が分からないので、あとで教えてください~
B美
| $lottery_date = intval($row['B']); |
よく整数型への変換を忘れなかったわね
C菜
B美
あー、ただ一点だけ残念な点があるわ
| $lottery_date_dt = $lottery_date->format('Y/m/d'); |
の部分だけど
| $lottery_date_dt = $lottery_date->format('Y-m-d'); |
にしたほうが良いわよ
だって、その変数の値って、MySQLのdatetime型項目に入れる値になるから…
('Y/m/d'でもうまくいくかもしれないけど、ちょっと怪しいのよね)
A子
あ、あとさ
それぞれの変数にきちんと正しい値が入ったかどうかって、簡単に調べられないかな?
ビューファイル(templates/Admin/import.php)をわざわざ作らなきゃダメかな?
B美
同じページにリダイレクトしているから、フラッシュメッセージを出すのが一番簡単じゃないかな
(「import.php」を作るなら「$this->set()」すれば良いんだけど…)
C菜
|
$this->Flash->success('実施回:'.$lottery_time);
$this->Flash->success('抽選日(日付型):'.$lottery_date_dt); $this->Flash->success('抽選日(文字列型):'.$lottery_date_str); $this->Flash->success('抽選日の年:'.$lottery_date_year); $this->Flash->success('抽選日の月:'.$lottery_date_month); $this->Flash->success('抽選日の日:'.$lottery_date_day); $this->Flash->success('抽選日の曜日(整数型):'.$lottery_week_int); $this->Flash->success('抽選日の曜日1(文字列型):'.$lottery_week_str1); $this->Flash->success('抽選日の曜日2(文字列型):'.$lottery_week_str2); $this->Flash->success('抽選日の六曜(整数型):'.$lottery_rokuyo_int); $this->Flash->success('抽選日の六曜(文字列型):'.$lottery_rokuyo_str); $this->Flash->success('ナンバーズ3の当選番号(文字列型):'.$num3_str); $this->Flash->success('ナンバーズ3の当選番号(整数型):'.$num3_int); $this->Flash->success('ナンバーズ3の当選番号の一の位:'.$num3_place1); $this->Flash->success('ナンバーズ3の当選番号の十の位:'.$num3_place10); $this->Flash->success('ナンバーズ3の当選番号の百の位:'.$num3_place100); $this->Flash->success('ナンバーズ4の当選番号(文字列型):'.$num4_str); $this->Flash->success('ナンバーズ4の当選番号(整数型):'.$num4_int); $this->Flash->success('ナンバーズ4の当選番号の一の位:'.$num4_place1); $this->Flash->success('ナンバーズ4の当選番号の十の位:'.$num4_place10); $this->Flash->success('ナンバーズ4の当選番号の百の位:'.$num4_place100); $this->Flash->success('ナンバーズ4の当選番号の千の位:'.$num4_place1000; |
これを「import」メソッドの最後らへんに書きました~
でも、これではExcelファイルの最後の行(第6929回目)の値だけしか出ませんけどね~(苦笑)
A子
(6929件全てを表示するのは現実的じゃないし…)
んじゃ、さっそくテストしてみよう
B美の作ったExcelファイルを読み込ませって…っと
↓
C菜
これで「曜日」や「六曜」の値についても、あとで確認できますね~
B美
(データベース登録については次回)
昔(CakePHP4以前)は、以下のようにcomposerでインストールできるライブラリがあったんだけどね
|
cd html/numapp[Enter]
composer require japanese-date/japanese-date[Enter] |
てか、今でもあるんだけど、残念ながらCakePHP5では使えないの
(あるメソッド…具体的には「formatLocalized」メソッド…の呼び出しでエラーになる)
A子
B美
C菜
B美
要は、便利なライブラリが無いのなら自分で作っちゃえ…ってこと
それじゃ、「MATE端末」上で以下のコマンドを打ち込んでね
|
cd html/numapp[Enter]
bin/cake bake component SixSevenWeek[Enter] |
クラス名(SixSevenWeek)は何でも良いんだけど、六曜と七曜を取得するって意味で適当に付けたわ
(「src/Controller/Component」ディレクトリの中に「SixSevenWeekComponent.php」という名前で生成されるから)
↓
B美
まずはフィールドね
|
private static $rokuyo = ['大安', '赤口', '先勝', '友引', '先負', '仏滅'];
private static $week_long = ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日']; private static $week_short = ['日', '月', '火', '水', '木', '金', '土']; ・・・ private static $rokuyo_data = [ '1994-01-01' => 1, '1994-01-02' => 2, '1994-01-03' => 3, ・・・ '2099-12-30' => 0, '2099-12-31' => 1, '2100-01-01' => 2, ]; |
あ、$rokuyo_dataについては末尾に記述してるわよ
(超・長いから…具体的には、38,717行)
・・・
・・・
A子
その大量のデータ(「$rokuyo_data」という連想配列)は何なのよ
C菜
おそらく「$rokuyo」という配列のインデックスに対応してるとみました~
B美
六曜を計算で求めることもできなくはないんだけど、正確な結果を取得するにはかなり複雑な計算が必要なのよ
あと、それでもズレる可能性があるという…(苦笑)
A子
力技だね(苦笑)
B美
でも、これが一番確実よ
んじゃ、次はメソッドね
|
//六曜データの取得
public static function getSix($ymd) { $index = self::$rokuyo_data[$ymd]; return [ 'rokuyo_int' => $index, //0〜5までの数値 'rokuyo_str' => self::$rokuyo[$index] //文字列 ]; } //七曜(曜日)データの取得 public static function getSeven($y, $m, $d) { $timestamp = mktime(12, 0, 0, $m, $d, $y); $week_int = date('w', $timestamp); return [ 'week_int' => $week_int, //0〜6までの数値 'week_str_long' => self::$week_long[$week_int], //長い文字列 'week_str_short' => self::$week_short[$week_int] //短い文字列 ]; } |
C菜
なんとなく言いたいことは分かりますけどね~
A子
「$this->」がインスタンス化されたオブジェクトのフィールドにアクセスするんだから、「self::」はインスタンス化されていないクラスのフィールドやメソッドにアクセスする方法と見た
B美
正解よ
キーワードとして「static」がフィールドやメソッドの先頭に付いているでしょ?
それが(インスタンス化が要らない)クラス固有の機能…ってことになるのよ
C菜
(うっかり間違えそうです~)
B美
「src/Controller/AdminController.php」の「initialize」メソッドに以下の一文を追加してね
| $this->loadComponent('SixSevenWeek'); |
あとは、こんな感じでメソッドを呼び出せるわ
|
$seven = $this->SixSevenWeek::getSeven($lottery_date_year, $lottery_date_month, $lottery_date_day);
$lottery_week_int = $seven['week_int']; //抽選日の曜日(整数型) $lottery_week_str1 = $seven['week_str_long']; //抽選日の曜日1(文字列型) $lottery_week_str2 = $seven['week_str_short']; //抽選日の曜日2(文字列型) $six = $this->SixSevenWeek::getSix($lottery_date_dt); $lottery_rokuyo_int = $six['rokuyo_int']; //抽選日の六曜(整数型) $lottery_rokuyo_str = $six['rokuyo_str']; //抽選日の六曜(文字列型) |
あ、ついでに一点だけ修正しておいたからね
「Y年m月d日」だった箇所を「Y年n月j日」に…
(01月01日が違和感あるから、1月1日になるように…)
C菜
A子
メソッドって、一個だけしか値を返せないと思ってたけど、こんな風に複数個の値を返せるんだね
B美
ただの「配列」だろうが(上記の例のように)「連想配列」であろうが、戻り値として使えるわよ
A子
コンポーネントって(普通のクラスみたいに)インスタンス化して使うようにはできないの
B美
「static」を付けない場合は「self::$rokuyo_data[$ymd]」ではなく
「$this->rokuyo_data[$ymd]」になるだけね
あと、呼出し側(AdminController)でも「$this->SixSevenWeek::getSix($lottery_date_dt)」ではなく
「$this->SixSevenWeek->getSix($lottery_date_dt)」になるわ
C菜
複数のコントローラーからアクセスされる(可能性のある)コンポーネントは、静的(static)な存在にしていたほうが良いってことでしょうか~?
B美
「static」を使う例題として使っただけよ
A子
ま、まぁ良いでしょう
んじゃ、実行確認してみよう
C菜
ネット上にある六曜カレンダーの値とも比較してみましたけど、2026年2月27日は「金曜日」の「大安」で間違いないです~
A子
だったら、まかせてよ
B美に頼らずにできるはず…C菜が!
B美


