CakePHP3でファイルのアップロード処理を自作・解説付き・その1
2017/11/26
CakePHP3でファイルのアップロード処理を作る
CakePHPでは、下記の GitHubに公開されているファイルアップロードプラグインがよく利用されているそうです。
https://github.com/josegonzalez/cakephp-upload/blob/master/README.md#requirements
ですが、このプラグインでは、アップロードしたファイルのサムネイルを作成するのですが、その際に GDライブラリか、Imagickライブラリを使います。
そのため、GD、Imagickのいずれかが利用でいないとこのプラグインを利用できないのですが、レンタルサーバの場合は、ライブラリを自由にインストールできない場合もあろうかと考え、まずは、アップロード処理を自作してみることにしました。
画像アップロードを行う画面の前提
一般的な CMSを想定し、ニュース登録の画面を作成します。
その中に、画像の登録を行う項目があることを想定しています。
テーブルは下記を想定し、必要なプログラムは、bake allで生成しているものとして話を始めます。
1 2 3 4 5 6 7 8 9 |
CREATE TABLE `news` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `title_date` date NOT NULL, `title` varchar(255) NOT NULL, `body` text, `file_name` text, `created` datetime NOT NULL, `modified` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; |
テンプレートファイルの変更
ファイルのアップロードをするには、まず、formタグを修正する必要があります。
CakePHP3の Formヘルパーの使い方は「CakePHP3のForm Helperの使い方のまとめ」で解説をしていますので、詳しくはそちらを参照してください。
add.ctp、edit.ctpを変更
アップロードする画像を入力する画面となるテンプレートの変更を行います。
対象ファイルは、以下の 2ファイルです。
/src/template/news/add.ctp
/src/template/news/edit.ctp
修正箇所は以下の通りです。
Formタグでファイルのアップロードを扱えるようにします。
1 2 3 4 5 |
<?= $this->Form->create($news) ?> ↓修正 <?= $this->Form->create($news, ['enctype' => 'multipart/form-data']); ?> // または <?= $this->Form->create($news, ['type' => 'file']); ?> |
inputタグのタイプを fileに変更します。
1 2 3 4 5 |
<?= $this->Form->input('file_name'); ?> ↓修正 <?= $this->Form->input('file_name',["type"=>"file"]); ?> // または <?= $this->Form->file('file_name'); ?> |
アップロードされる画像の確認
入力フォームを修正しただけで、画像をアップロードすることはできます。
実際にアップロードしたファイルを、コントローラー側では下記の様に記述することで受け取っていることを確認することができます。
/src/controller/NewsController.php
1 2 3 4 |
if ($this->request->is(['patch', 'post', 'put'])) { // 下記のログ出力処理を追記 $this->log($this->request->data['file_name'],LOG_DEBUG); |
これにより、ログファイルには下記の情報が出力されます。
ログファイルへの出力する方法の解説は「CakePHP3ログファイルへの出力・$this->log()、独自ログへの出力方法の解説」を参照してください。
1 2 3 4 5 6 7 |
Array ( [tmp_name] => C:\xampp\tmp\php37DC.tmp [error] => 0 [name] => sample.gif [type] => image/gif [size] => 665 ) |
入力フォームに追加したファイルの入力項目「file_name」から送信された値が取得できていることが確認できます。
ただ、この状態では、送信された画像ファイルは、「tmp_name」の値にあるように「C:\xampp\tmp\php37DC.tmp」という一時ファイルとして生成されますが、その後の処理でそのファイルも削除されてしまいます。
そのため、この一時ファイルを希望する場所に移すための処理を記述します。
加えて、ファイルがアップロードされていない場合や、ファイルサイズが大きい場合、想定しているファイル種類でない場合などのチェックも行います。
コントローラーの変更
ファイルアップロードの処理を作成するにあたって、必要になる処理をコントローラーに追記します。
対象となるコントローラーファイルは、下記になります。
/src/controller/NewsController.php
必要なクラスをロードする処理
ファイルアップロードの処理に必要となるクラスをロードする処理、下記の内容を追記します。
1 2 3 |
use Cake\Filesystem\Folder; use Cake\Filesystem\File; use RuntimeException; |
Folder、Fileクラスに関しては下記が参考になります。
https://book.cakephp.org/3.0/ja/core-libraries/file-folder.html
「RuntimeException」クラスに関しては下記が参考になります。
http://waterada.ldblog.jp/archives/20000008.html
ファイルアップロードの処理の追記
下記の 4~12行目を追加します。
「add()」「edit()」アクションに追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
: if ($this->request->is(['patch', 'post', 'put'])) { $news = $this->News->patchEntity($news, $this->request->data); // 下記の処理を追記します。 $dir = realpath(WWW_ROOT . "/upload_img"); $limitFileSize = 1024 * 1024; try { $news['file'] = $this->file_upload($this->request->data['file_name'], $dir, $limitFileSize); } catch (RuntimeException $e){ $this->Flash->error(__('ファイルのアップロードができませんでした.')); $this->Flash->error(__($e->getMessage())); } if ($this->News->save($news)) { : : |
5行目で、アップロードするファイルを保存するフォルダのパスを指定します。
6行目で、アップロードするファイルの容量の最大値を指定します。
そして、8行目で自作のファイルアップロード関数「file_upload()」に、ファイルの入力項目の値「$this->request->data['file_name']
」と、5行目、6行目で指定した値を引数として渡して、関数を実行します。
(今回は、同一コントローラー内の関数としてアップロード処理を作っていますが、アップロード処理はコンポーネントとして作成する方がいいでしょうね。)
5行目では、アップロードするファイルを保存するパスを指定しています。
このパスの指定では、「WWW_ROOT」という定数を使って指定していますが、各種フォルダの位置を示す定数に関しては「CakePHP3でDocumentRootやtmp、webroot、logsなどのフォルダへのパスの定数」にまとめましたので参考にしてみてください。
ドキュメントルートのパスや、「tmp」フォルダへのパスなどが規定されています。
ファイルアップロードの処理の関数本体
実際にファイルアップロードを行う本体の関数処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
public function file_upload ($file = null,$dir = null, $limitFileSize = 1024 * 1024){ try { // ファイルを保存するフォルダ $dirの値のチェック if ($dir){ if(!file_exists($dir)){ throw new RuntimeException('指定のディレクトリがありません。'); } } else { throw new RuntimeException('ディレクトリの指定がありません。'); } // 未定義、複数ファイル、破損攻撃のいずれかの場合は無効処理 if (!isset($file['error']) || is_array($file['error'])){ throw new RuntimeException('Invalid parameters.'); } // エラーのチェック switch ($file['error']) { case 0: break; case UPLOAD_ERR_OK: break; case UPLOAD_ERR_NO_FILE: throw new RuntimeException('No file sent.'); case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: throw new RuntimeException('Exceeded filesize limit.'); default: throw new RuntimeException('Unknown errors.'); } // ファイル情報取得 $fileInfo = new File($file["tmp_name"]); // ファイルサイズのチェック if ($fileInfo->size() > $limitFileSize) { throw new RuntimeException('Exceeded filesize limit.'); } // ファイルタイプのチェックし、拡張子を取得 if (false === $ext = array_search($fileInfo->mime(), ['jpg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif',], true)){ throw new RuntimeException('Invalid file format.'); } // ファイル名の生成 // $uploadFile = $file["name"] . "." . $ext; $uploadFile = sha1_file($file["tmp_name"]) . "." . $ext; // ファイルの移動 if (!@move_uploaded_file($file["tmp_name"], $dir . "/" . $uploadFile)){ throw new RuntimeException('Failed to move uploaded file.'); } // 処理を抜けたら正常終了 // echo 'File is uploaded successfully.'; } catch (RuntimeException $e) { throw $e; } return $uploadFile; } |
各処理について解説します。
4~10行目。
ファイルを保存するフォルダとして指定する「$dir」の値が正しいかどうかをチェックしています。
「$dir」が空でないことと、「file_exists()」関数を使ってのフォルダの存在をチェックしています。
13~15行目。
アップロードするファイル項目が未定義であったり、複数のファイルが送信されてきたり、破損攻撃をするなど、不正な処理を受け付けた場合に無効にするための処理が記述されています。
18~30行目。
アップロードされたファイルのチェックを行います。
ファイルがない場合や、送信できるファイルの容量を超えている場合などのエラーチェックを行います。
33行目。
「tmp」フォルダにアップロードされたファイルの情報をチェックするため、file()オブジェクトを呼び出します。
36~38行目。
33行目の File()オブジェクトからファイルのサイズを取得し、引数で受け取ったサイズより小さいことをチェックします。
「$file[‘size’]」でもファイルサイズは取得できますが、File()オブジェクトの情報の方を信用していますので、そちらで処理をしています。
41~47行目。
File()オブジェクトから MIMEタイプを取得し、想定しているファイルタイプの中に存在するか否かをチェックするとともに、拡張子を取得します。
50行目、51行目
保存するファイル名を生成しています。
51行目は「$file[“tmp_name”]」を基にして、sha1_file()関数でファイル名を生成しています。
50行目は「$file[“name”]」としていまして、入力があったファイル名をそのまま使用する場合を想定しています。
ここは、どちらかを指定することを想定していますが、50行目の元のファイル名を使用する場合は、下記の不具合につながる可能性についての理解が必要です。
ファイル名が重複する可能性
全角文字(日本語)が文字化けする可能性
54~56行目
ファイル移動の処理本体です。
関数の引数として受け取ったフォルダ「$dir」に、「move_uploaded_file()」関数を使ってファイルを移動させることで、所定の場所にファイルが保存されたことになります。
ここまでいろいろな処理をしていますが、51行目までの処理は全てが入力チェックですので、入力チェックがなくていいならば、54行目のみでも OKです。
(入力チェックをしないのは現実的ではありませんが。)
59行目。
コメントアウトにしていますが、ここまで来たら正常に処理が終了したことになります。
50行目、51行目で生成するファイル名を戻して処理終了です。
このファイルのアップロード処理に関しては、下記の処理を参考にしています。
https://secure.php.net/features.file-upload
「The following code cannot cause any errors absolutely.(次のコードはエラーを絶対に引き起こしません。)」と書いてありましたので、それを利用しました。
ただ、「move_uploaded_file()」関数は、正常にファイル移動ができない場合は、「false」を返すとともに、Warningを出力するため、CakePHP3ではそこで止まってしまいます。
そのため「@move_uploaded_file()」とすることで Warningを出さないように対処しています。
CakePHP3でファイルアップロードのまとめ
CakePHP3でファイルをアップロードするための処理を自作してみました。
ですが、この処理ではレコードを新規登録する際にファイルをアップロードする時には問題なく実行できるものの、レコードを更新する場合の画像の取り扱いや、ファイルだけを削除したい場合などの対処が十分ではないため実運用に使うにはもう少し処理を追加する必要があります。
それについては、下記に改めて記事を書きましたので、そちらを参照してください。
CakePHP3で画像・ファイルのアップロード処理を自作・解説付き・その2
また、ファイルを保存する場所を指定するパス、および、画像を表示する際のドキュメントルートからのパスを取得する処理については、下記に記事を書いていますのであわせて参考にしてください。
CakePHP3でDocumentRootやtmp、webroot、logsなどのフォルダへのパスの定数
CakePHP3でDocumentRootやwebroot、imgフォルダのURLやドメイン、パスを取得
CakePHP3の関連記事
CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールするCakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説
CakePHP3でPHP Simple HTML DOM Parserを使ってスクレイピングする方法
CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も
CakePHP3の1対多での連携を中間テーブルを使った多対多の連携に変更するときの手順
CakePHP3でデフォルトのソート条件を設定してユーザの選択肢たソート条件を有効にする方法
CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
CakePHP3でパンくずの指定は HTMLヘルパーを使って指定する方法を解説
CakePHP3にOGPをfetch、asignを利用してテンプレートごとに指定する方法を解説
その他の「CakePHP3」に関する記事一覧
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
CakePHP3で環境変数を設定して本番環境と開発環境を分けて処理をする場合
CakePHP3で開発環境と本番環境とで違う設定ファイルを読み込ませて環境ごとに定数を切り替える方法を解説。Apacheのhttpd.confに環境変数を設定し、それを読み込み判別する。
-
CakePHPで同一テーブル内の値を比較する条件でレコードを取得する方法
CakePHPの同一テーブルにある項目の値を比較し条件に合致するレコードを取得する方法を解説。[”項目名”=>”値”]ではなく[”項目名 = 項目名”]と書くところがポイント。
-
CakePHP3のインストールでURL rewriting……のエラーが!その解決方法解説
CakePHP3をインストールしたら URL rewriting is not properly configured.のエラーが。原因は.htaccessが有効になっていないこと。httpd.confに設定を追加すればOK。
-
CakePHP3、CakePHP4、CakePHP5のバージョンを指定してインストールする詳細な手順を解説
CakePHP3のバージョンを指定してインストールする方法を詳細解説。CakePHP3のインストールはComposerを使うため設定もほぼ自動で完了。データベースの接続情報を記載すればアプリ開発のベースが整う。
-
CakePHP3のアソシエーション機能を使い関連レコードをまとめて削除
CakePHP3でレコードを削除する際に関連するレコードをまとめて削除する機能の解説。フレームワークのメリットを存分に発揮し、コマンドを1行追加するだけで実装可能。
-
CakePHP3のアソシエーションでJOINのタイプのLEFT、INNERを切り替えながら使う方法
CakePHP3でテーブルのアソシエーションしたデータの取得をコントローラー側でINNERかLEFTを指定する方法を解説。TableファイルにINNERで指定していてもController側で変更ができる。
-
CakePHP4の数値項目は「like %10%」の部分一致検索(find select)はできない
CakePHP4でテーブルの数値項目に対してlike句を使用した部分一致検索を実行するとエラーが発生する。クリエービルダーの不具合だと思われ対処方法が分からない。
-
CakePHP4の定数定義ファイルを環境変数によって本番と開発を振り分ける方法解説
CakePHP4で開発環境と本番環境とで違う設定ファイルを読み込ませて環境ごとに定数を切り替える方法を解説。Apacheのhttpd.confに環境変数を設定しそれを読み込み判別する。
-
CakePHP4の規約外のカラムをキーにアソシエーション(テーブル連結)する方法
CakePHPで規定外のカラム名のキーを指定してアソシエーション(テーブル連結)をする方法を解説。アソシエーション名によってはミスが発生しやすい点もあるので注意も必要。
-
CakePHP3で /Layout/defult.ctpにある titleタグ、h1タグを編集する方法
CakePHP3でtitleタグ、h1タグのテキストをデフォルトから変更する方法を解説。テンプレートファイルに「$this->assign()」でテキストを指定して「/Layout/defult.ctp」で受け取る。