CakePHP3で画像などのファイルをアップロード機能を付けたい
ファイルアップロード機能を付けたいときありますよね。
自分のサービスは画像主体なので、欠かせなかったです。
意外とハマる部分でもありますので、説明していきたいと思います。
【自分の環境】
macOS Catalina
PHP7.4.2
CakePHP3.8
MAMP5.7
Apache2.2
MySQL5.7
まずはカラムの確認
DBにおけるカラムの確認です。
imageカラムはありますか?
これはすなわち画像名です。
自分の場合はPhotosテーブルだったのでnameカラムでしたが、この画像名が必要になります。
ここで初学者の方のために念のため説明すると、誤解しがちなのですが、画像自体をDBに格納するわけではありません。
DBに保存するのは画像名です。
肝心の画像はwebrootのimgディレクトリに保存されているので、パスを記載してそれを呼び出すイメージです。
画像名カラムがなかったら、マイグレーションでカラム追加して作ってあげてください。
ビューテンプレートにファイルアップロードフォームを追加
ここは意外とハマりがちです。
ファイルアップロードフォーム(「画像を選択してくださいボタン」)を追加したいページに以下を記載します。
(フォームを付けたいビューテンプレート)
<div class="image_add"> <?php $photo = NULL ?> <?= $this->Form->create($photo,['type'=>'file']) ?> // ここが大事 <fieldset> <p class="title">〜画像を選択〜</p> <?php echo $this->Form->file('name',['class'=>'file']); // ここを追加 ?> </fieldset> <?= $this->Form->button(__('写真を登録する!'),['class'=>'button']) ?> <?= $this->Form->end() ?> </div>
ここでは「$this->Form->create($photo,['type'=>'file'])」のようなフォームヘルパーというヘルパーを使ってフォームを追加しています。
ヘルパーは書くことは、ただのHTMLより個人的にはめんどくさいのですが、安全に機能を記述できる手段です。
ここが大事と書いた部分の何が大事かと言うと、「['type'=>'file']」です。
これは普通のHTMLで言う「“multipart/form-data” の enctype属性」です。
これがないとファイルアップデートできません。
また肝心のフォームはここを追加と書いた部分の「echo $this->Form->file('name',['class'=>'file']);」。
ここもフォームヘルパーで書いています。
これをたまにechoし忘れる人がいるのですが、echoしなければ表示されないみたいです。
echoではなくてForm->file('name',['class'=>'file']); ?>でもOKです。
次にコントローラの処理を書いていきます。
一番難しいコントローラの処理
コントローラは若干複雑な処理になります。
今回はPhotosテーブルのnameカラム(画像名カラム)に値を入れるという体裁でやっていきます。
Photosテーブルのカラムはid,name(画像名)というシンプルな構成とします。
本当は例外処理(あらかじめエラーと分かる部分はエラーメッセージを出すなどすること)やトランザクション(失敗したら処理を巻き戻して画像を消したりするのと、成功したら処理を確定すること)も書きたいのですが、ややこしくなるので省略します。
画像名が被ることがないように「アップロードした日時+画像名」という画像名にします。
(フォームのコントローラのアクションのファイルアップロード処理の部分)
// POST送信時の処理 if($this->request->isPost()){ // 画像ファイル名称の先頭に時間をつけて/webroot/imgに移動させる $upload_file = $this->request->getData('name'); // nameを取得して$upload_fileに格納する $filePath = WWW_ROOT.'img/'.date("YmdHis").$upload_file['name']; // ファイルを移動させたい先のパスを$filePathに格納する if(is_uploaded_file($upload_file['tmp_name']) && move_uploaded_file($upload_file['tmp_name'],$filePath)){ // ファイルを確認して、かつそのファイルを$filePathに移動させる // 【ここからDB用の処理】変数photoにフォームの送信内容を反映 $data = [ 'name' => date("YmdHis").$upload_file['name'], ]; $photo = $this->Photos->newEntity($data); // photoに保存する if($this->Photos->save($photo)){ // 成功時のメッセージ $this->Flash->success(__('写真登録に成功しました!ありがとうございます!')); // トップページ(index)に移動 return $this->redirect(['action'=>'index']); } } // 値をビューテンプレートに渡す $this->set(compact('photo')); }
省略しても長い処理になってしまいますね。
それぞれの行にコメントをつけたので、読んでもらえると助かります。
ちなみにWWW_ROOTとはパス変数で、CakePHP3であらかじめ定義された変数です。
自分でパスを書くのは大変なので、こういうものが1個1個path.phpで定義されていて、それを利用しています。
その他、理解できなければコメントで知らせていただきたいです。
ビューテンプレートに表示させる
最後に表示させます。
これはあっさりかと思います。
ちなみにこのビューテンプレートもケースバイケースなので、今回はmy_photosというPhotosテーブルのデータが入った配列を受け取ったビューテンプレートという体裁で書きます。
(表示させたいビューテンプレート)
(中略) <tbody> <?php foreach ($my_photos as $photo): ?> <tr> <td><?= h($photo->id) ?></td> <td><?php echo $this->Html->image($photo->name,['width'=>'200','height'=>'200']) ?></td> // 肝心なところ </tr> <?php endforeach; ?> </tbody> (中略)
「肝心なところ」と書いた部分で表示させています。
$this->Html->image();はHTMLヘルパーという書き方で、これで安全に画像を表示させています。
echoを忘れないようにしてください。
これで表示されると思います。
終わりに
以上となります。
長くなりましたが、いかがだったでしょうか。
抜け漏れがあったらコメント欄で教えていただけると幸いです。
実際には保存失敗時に画像を消す処理やエラーメッセージを出す処理なども加えていきました。
ただそれを書くと複雑になるので、今回は省きました。
今後、別の記事でそれらは説明していきます。
ありがとうございました。