CakePHP 4 でファイルアップロードを実装する方法

はじめに

CakePHP 4 にはファイルアップロード用の機能があります。

PHP のフレームワークなので、PHP 標準の関数 move_uploaded_file() でも実装できますが、せっかくなので CakePHP 4 の機能を使ってファイルをアップロードする方法をご紹介します。

CakePHP
4.0.3
目次
  1. シンプルに実装
  2. 配列で複数ファイルも可能
  3. エラーコードについて
  4. おわりに

1. シンプルに実装

まずはシンプルに実装してみます。フォームから送られてきたデータを任意のフォルダに保存するプログラムです。

下記の例では /webroot/upload ディレクトリに、アップロードしたファイルの名前で保存しています。

templates/Sample/index.php
<form method="post" enctype="multipart/form-data">
  <div>
    <input type="file" name="my_file">
    <input type="hidden" name="_csrfToken" autocomplete="off" value="<?= $this->request->getAttribute('csrfToken') ?>">
    <button type="submit">アップロード</button>
  </div>
</form>
src/Controller/SampleController.php
function index()
{
  if ($this->request->is('post')) {
    $myFile = $this->request->getData('my_file');
    $name = $myFile->getClientFilename();
    $path = WWW_ROOT . 'upload' . DS . $name;
    $myFile->moveTo($path);
  }
}

getClientFilename() でファイル名を取得しています。

WWW_ROOT は webroot へパスで、config/paths.php の中で定義されています。

そして moveTo() で アップロードしたファイルを $path に保存しています。

Cookbook には getClientFilename() と moveTo() 以外の関数についても説明があるので、是非ご一読ください。

2. 配列で複数ファイルも可能

下記のように配列として複数のファイルをアップロードすることもできます。

templates/Sample/index.php
<form method="post" enctype="multipart/form-data">
  <div>
    <input type="file" name="my_file[]"><br>
    <input type="file" name="my_file[]"><br>
    <input type="file" name="my_file[]"><br>
    <input type="hidden" name="_csrfToken" autocomplete="off" value="<?= $this->request->getAttribute('csrfToken') ?>">
    <button type="submit">アップロード</button>
  </div>
</form>
src/Controller/SampleController.php
function index()
{
  if ($this->request->is('post')) {
    $myFiles = $this->request->getData('my_file');
    foreach ($myFiles as $myFile) {
      $name = $myFile->getClientFilename();
      $path = WWW_ROOT . 'upload' . DS . $name;
      $myFile->moveTo($path);
    }
  }
}

3. エラーコードについて

getError() 関数を使うとエラーコードを取得することができます。
これは PHP のファイルアップロード時のエラーコードに対応します。

これを使ってエラー内容によって処理を分けることができます。例えば以下のような感じです。

src/Controller/SampleController.php
if ($this->request->is('post')) {
  $myFile = $this->request->getData('my_file');
  // エラー時の処理
  switch ($myFile->getError()) {
    case UPLOAD_ERR_INI_SIZE:
    case UPLOAD_ERR_FORM_SIZE:
      die('ファイルのサイズが大きすぎます');
    case UPLOAD_ERR_PARTIAL:
      die('一部のファイルがアップロードされませんでした');
    case UPLOAD_ERR_NO_FILE:
      die('ファイルが選択されていません');
    case UPLOAD_ERR_NO_TMP_DIR:
    case UPLOAD_ERR_CANT_WRITE:
    case UPLOAD_ERR_EXTENSION:
      die('アップロードができませんでした。システムが利用できなくなっているかもしれません');
  }
  $name = $myFile->getClientFilename();
  $path = WWW_ROOT . 'upload' . DS . $name;
  $myFile->moveTo($path);
}

上記はサンプルなので die() で済ませていますが、実際の開発ではエラー画面を表示したり、ログを出力するなど、もっと丁寧に作ります。

また <input name="my_file"> が存在しない場合、getData('my_file') は null を返すので、 上記 $myFile->getError() の前にそれを考慮したコードも必要になります。

4. おわりに

今回紹介したサンプルはシンプルなので、実際にはもっと丁寧に組む必要があると思います。上記で紹介したエラー時のフォローのほかにも、ファイル名が重複した場合の対応や、ファイルタイプに制限を設ける、などです。

今回はその辺の説明は割愛しますが、書籍やインターネットでそのあたりの情報は手に入ると思いますので探してみてください。