CakePHP5入門【CakePHP5応用編④】アップロード関係

A子
あくまでも試案と言うか、叩き台だけどね
(ここからさらにブラッシュアップしていけば良いんじゃないかな)

B美

A子
C菜と二人で「ああだ、こうだ」と結構時間をかけたよ

C菜
でも、良い経験になったと思います~

B美

A子
あんた私と同い年よね?
ま、まぁ良いわ
これがトップページの「index.php」よ!



C菜



A子
(CakePHP5基礎編⑤で習ったからね)


C菜

B美
あ、ついでに「PostsController.php」の「index」メソッドを変更しておきましょう
$this->Posts->find()->where(['delete_flag' => 0]);
$posts = $this->paginate($query, ['limit' => MAX_PAGE, 'order' => ['id' => 'desc']]); |

C菜
つまり、削除レコードの「delete_flag」には「0」以外(例えば「1」)をセットするわけですね~

B美
あと、並べ替えについては、「id」の降順でソートする…ってわけ

A子
つまり「bbsapp/config」ディレクトリ内の「const.php」にこの定義を追加すれば良い…ってことか
define("MAX_PAGE", 2); |

B美
定数化しておくと、あとになって変更するときにめっちゃ便利なのよね
あと、投稿番号の「背景色」とタイトルの「背景色」の両方についても定数化しておくと良いかもよ
(まぁ、別にやらなくても良いんだけど)

C菜
define("NUM_COLOR", "#b0e0e6"); //投稿番号の背景色
define("TITLE_COLOR", "#fff8dc"); //タイトルの背景色 |
を「const.php」に追記して…っと~


A子


C菜


A子



C菜

A子
あ、上から4行目の背景色指定の箇所については、さっきの定数で書き換えておいたから…



C菜


B美
予想以上の出来栄えと言っても過言ではないわね

A子
Controllerである「PostsController.php」なんだけど…

C菜
でも、ここから先が分かりません~


B美
ファイルとして保存するのだから、まずはその格納場所を決めましょう

C菜

B美

A子

B美

C菜
あ、だから「bbsapp/webroot」の中にCSSファイルを格納している「css」ディレクトリや画像ファイルを格納している「img」ディレクトリがあるんですね~

A子

B美
(学習の一環として…)
「MATE端末」を開いて、次のように入力してね
cd html/bbsapp/webroot[Enter]
mkdir files[Enter] chmod 757 files[Enter] |
これで「bbsapp/webroot」の下に「files」ディレクトリが生成されるわ


A子

B美
要するに、「アクセス権限」の設定ね
まぁ、あまり気にしなくても良いわ
(「webroot」の下にディレクトリを作るときに実行する「おまじない」みたいなものだと思っていればOK)
注:パーミッションについては、人によって異なる考え方があります
(ゆるくするか、厳しくするか…の違い)

A子
(データベース設計のときにそう言ってたし…)

B美
オリジナルのファイル名が日本語だったり、格納するファイル名が重複したりするとまずいからね
さて、どうする?

C菜
半角で20文字以上とかだったら、ファイル名が重複することはないと思うんですけど~

A子
(アップロードできるファイル数は、0個または1個なんだし…)

B美
ただし、C菜の方法では「重複しないことを担保する仕組み」が必要だし、A子の方法の場合「投稿番号をどのように取得するか」がポイントになるけどね

C菜

B美
その属性が付いている場合、データベース側で勝手に連番を付けてくれるのよ
(それが「投稿番号」ってわけ)

A子
だとすると、レコードを新規作成しないと「id」の値は決まらないのか…
あれ?
「filepath」に入れる文字列って、「id」の値が決まらないと付けられないわけだから、「ファイル名を投稿番号にする」案ってダメじゃん…

B美
まずは(アップロードするファイルが有ろうが無かろうが)「filename」と「filepath」フィールドに空文字列をセットしたレコードを新規作成します
(もちろん「タイトル」や「本文」、「削除フラグ」には値をセットしてね)
で、もしもファイルが有る場合には…
・「id」の値を取得し、その値に拡張子を付けたものをファイル名とし、ファイル保存
・次に、ファイル保存したパスを「filepath」フィールドに設定してレコードを更新
(もちろん「filename」フィールドにはオリジナルのファイル名をセットすること)

C菜
そっちのほうが簡単そうですし、A子社長の案でいきましょう~

B美
それじゃ、まずは全体の流れを説明するわね
1.もしもPOSTされたら「$this->request->getData('○○')」で送信されてきたデータを取得します
例えば
if ($this->request->is('post')) {
$○○ = $this->request->getData('○○'); ・・・ } |

A子

B美
例えば
$post = $this->Posts->newEmptyEntity();
$post->○○ = △△; |
…って感じね

C菜
$post = $this->Posts->newEmptyEntity();
$post->title = $title; $post->body = $body; $post->filename = ''; $post->filepath = ''; $post->delete_flag = 0; |
…ということでしょうか~?

B美
ただし、まだデータベースへの登録はできていないからね
そのためには…
3.「$this->Posts->save()」メソッドを呼び出して、戻り値が「true」だったら成功、「false」ならば失敗よ
(このメソッドへの引数は、さっきの「newEmptyEntity()」の戻り値を格納した変数を渡します)

A子
…ってことは
$this->Posts->save($post); |
で良いのかな?

C菜
if ($this->Posts->save($post) == true) {
} else { } |
…って感じで~

A子

C菜
if ($this->Posts->save($post)) {
} else { } |
これでどうでしょう~?

B美
4.画像ファイルが指定されたかどうかを判別するには、「$upload->getClientFilename()」メソッドの戻り値が空文字列かどうかで分かるの
(これによってオリジナルのファイル名を取得できるわ)

C菜
$original_filename = $upload->getClientFilename();
if ($original_filename != '') { (ファイルがあるときの処理) } |

B美
5.さっきデータベース登録(レコード挿入)したことで主キーの値が決まったから、その値を知るには「$post->id」でOKよ

A子
if ($original_filename != '') {
$new_id = $post->id; } |

B美
んじゃ、次!
6.ファイル名を決めるには「拡張子」が必要だから、それを取得しましょう
「pathinfo($original_filename, PATHINFO_EXTENSION)」関数を呼べば、その戻り値が「gif」や「png」って感じになるわよ
あ、ただし、人によっては「大文字」で拡張子を付けてるかもしれないから注意してね

C菜

B美
一般的には「小文字」に統一する人のほうが多いかな

A子
if ($original_filename != '') {
$new_id = $post->id; $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); $filepath = $new_id.".".$extension; } |
…で、どうよ

B美
このWebアプリのドキュメントルートの下の「files」ディレクトリ内に格納したい場合、「$filepath」はこうなるの
$filepath = ".".DS."files".DS.$new_id.".".$extension; |
ちなみに、先頭の「.」はカレントディレクトリって意味なんだけど、ここでは「ドキュメントルート」って意味になるわ

C菜

B美
定数になっている理由は、実行環境(Webサーバ)がLinuxでもWindowsでもうまく動くように…
(Linuxでは「/」だけど、Windowsでは「\」が区切り文字だからね)
さぁ、いよいよファイルの保存を行います
7.「$upload->moveTo()」メソッドを呼べば、指定した場所に指定した名前で保存されるわ
(このメソッドの引数には、ファイルの「フルパス」を渡すってこと)

A子
if ($original_filename != '') {
$new_id = $post->id; $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); $filepath = ".".DS."files".DS.$new_id.".".$extension; $upload->moveTo($filepath); } |
そんなに難しくないかな

B美
($original_filenameを「filename」フィールドに、$filepathを「filepath」フィールドにセットしてから更新しないとね)
レコード更新の手順も割と簡単よ
8.「$this->Posts->get(主キー番号)」の戻り値を変数へ代入し、その変数を使って値をセットしていくだけ

C菜
$new_post = $this->Posts->get($new_id);
$new_post->filename = $original_filename; $new_post->filepath = $filepath; |
もしかして、このあとsaveメソッドを呼ぶんですか~?

B美
どうするか分かる?

C菜
$new_post = $this->Posts->get($new_id);
$new_post->filename = $original_filename; $new_post->filepath = $filepath; if ($this->Posts->save($new_post)) { } else { } |
…って感じでしょうか~?

B美
あ、でも失敗したときだけ何らかの処理をすれば良いから
if ($this->Posts->save($new_post) == false) {
} |
または
if (!$this->Posts->save($new_post)) {
} |
…って感じかな

A子

B美
論理演算子の「not」を意味する記号よ
(要するに、真偽を逆転させるってこと)

C菜

B美
例えば「$this->Flash->success('成功時のメッセージ')」は成功時のメッセージで、「$this->Flash->error('失敗時のメッセージ')」が失敗時のメッセージよ
あと、リダイレクトというのは、別のページへ移動すること
例えば「return $this->redirect(['action' => 'index']);」を実行した瞬間、トップページである「index」に遷移するってわけ

A子
if (!$this->Posts->save($new_post)) {
$this->Flash->error('画像ファイル情報のデータベース登録に失敗しました。'); return $this->redirect(['action' => 'index']); } |

B美

C菜
if ($this->request->is('post')) {
$title = $this->request->getData('title'); $body = $this->request->getData('body'); $upload = $this->request->getData('upload'); $post = $this->Posts->newEmptyEntity(); $post->title = $title; $post->body = $body; $post->filename = ''; $post->filepath = ''; $post->delete_flag = 0; if ($this->Posts->save($post)) { $original_filename = $upload->getClientFilename(); if ($original_filename != '') { $new_id = $post->id; $extension = mb_strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); $filepath = ".".DS."files".DS.$new_id.".".$extension; $upload->moveTo($filepath); $new_post = $this->Posts->get($new_id); $new_post->filename = $original_filename; $new_post->filepath = $filepath; if (!$this->Posts->save($new_post)) { $this->Flash->error('画像ファイル情報のデータベース登録に失敗しました。'); return $this->redirect(['action' => 'index']); } } } else { $this->Flash->error('新規投稿のデータベース登録に失敗しました。'); return $this->redirect(['action' => 'index']); } $this->Flash->success('新たな投稿を登録しました。'); return $this->redirect(['action' => 'index']); } |

B美
よくまとまっているわ
それじゃ、実際に上記のコードを打ち込んでから、実行確認してみてね

A子
ちなみに、コメントも入れてみた



C菜


A子
なんだかあっさりと成功したよ
(B美先生のおかげなんだけど…(苦笑))


B美

C菜


A子


B美
なかなか良いんじゃないかしら
さて、それじゃ今回はここまでにしておきましょう
次回は、アクセスログ記録用テーブルに対する操作をやっていきます
(とは言っても、全然難しくはないからね)

A子