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のデバッグキット(DebugKit)を強制的に有効、無効に変更する方法
CakePHP3に付属しているデバッグのためのツール、デバッグキットを強制的に有効化、無効化する方法を解説。初期設定では開発環境としてありそうなドメインの場合のみ有効になるように設定されている。
-
CakePHP 2.x系 Viewでのコメントの記述方法など
CakePHPにおいて View、ctpファイルでコメントを記述する方法を解説。
-
CakePHP3のCakeDC/Usersでログインユーザの所有レコードのみ更新、削除する権限管理の設定方法
CakePHP3のユーザ管理、ログイン認証プラグイン「CakeDC/Users」の権限管理を行う方法やアクセスできるコントローラー、アクションを設定、所有権を持つレコードのみ更新できる設定方法を解説。
-
CakePHP3でPHPExcelを使ってエクセルファイルを生成、出力する方法
CakePHP3でPHPExcelを利用してエクセルを編集、出力するサンプルソース+解説。PHPExcelのインストール方法の解説からファイル保存とダウンロードの方法なども解説。
-
CakePHP4のCakeDC/Usersの Usersへの接続、バリデーションのカスタマイズ方法解説
CakeDC謹製Usersプラグインの紹介。CakePHP4で使う場合のUsersのカスタマイズとして入力項目のバリデーションの変更を、プラグインのファイルは触らずオーバーライドにより実装する方法を解説する。
-
CakePHP3でcomposerを利用してライブラリ・プラグインをインストールする方法
CakePHP3でcomposerを使ってパッケージ(ライブラリ、プラグイン)をインストールする方法の解説。PHPを使うのはCakePHP3が初めてというような方への使い方から解説。
-
CakePHP4のcake cache clear_allでPermission deniedはパーミッションの変更が必要
CakePHP4でキャッシュをクリアするコマンドcake cache clear_allでPermission deniedのエラーが出るのはcakeにパーミッションが足りないとき。その対処方法を解説。
-
CakePHP3のCakeDC/Usersのログイン後のリダイレクトとユーザ権限管理の設定解説
CakeDC謹製Usersプラグインの紹介。ログイン認証後にリダイレクトする先の設定方法についての解説と実運用するために必要なコツを解説。便利な仕組みも仕様の理解があって初めてうまく使いこなせる。
-
CakePHP3で /Layout/defult.ctpにある titleタグ、h1タグを編集する方法
CakePHP3でtitleタグ、h1タグのテキストをデフォルトから変更する方法を解説。テンプレートファイルに「$this->assign()」でテキストを指定して「/Layout/defult.ctp」で受け取る。
-
CakePHP 2.3 デバッグキット(DebugKit)超初心者向けフォロー講座
CakePHP初心者に向けてデバッグキット(DebugKit)のインストール方法、はまりポイントを解説。