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を忘れないようにしてください。

これで表示されると思います。

終わりに

以上となります。

長くなりましたが、いかがだったでしょうか。

抜け漏れがあったらコメント欄で教えていただけると幸いです。

実際には保存失敗時に画像を消す処理やエラーメッセージを出す処理なども加えていきました。

ただそれを書くと複雑になるので、今回は省きました。

今後、別の記事でそれらは説明していきます。

ありがとうございました。