システム構築の案件で、ユーザーが入力するフォームを作成することがあります。
ファイルアップロードの処理は少し特殊なので、流れをまとめてみました。
今回はWordPressでのSmart Custom Fieldsを利用することを想定しています。
WordPressではなく素のPHPの場合、Smart Custom Fieldsに保存するのをDBのカラムに保存するように書き換えれば、大まかな流れは同じだと思います。
目次
概要
まずやりたいことを整理します。
・ファイルを選択されると画像が表示される
・ファイルをサーバー上の特定のフォルダに保存する
・画像のパスをカスタムフィールドとして保存する
ざっとこんな感じでしょうか。
Smart Custom Fieldsのタイプは画像ではなく、テキストです。
処理が重たくなるので、DBに画像そのものを保存することは基本ないです。
それでは、それぞれの処理を追っていきたいと思います。
ちなみに今回のファイル構成は以下のようになります。
ファイルのアップロード-フロント側
まずファイルを選択するためのフォームを用意します。
フォームを作成
1 2 3 4 5 6 7 8 9 10 11 | <form action="form.php" method="POST" enctype="multipart/form-data"> <div class="js_file_upload_unit"> <div class="thumbnail"></div> <div class="file-group"> <input type="file" name="file" class="file"> </div> </div> <button type="submit">送信する</button> </form> |
ファイルをアップロードする際は、form要素の「enctype=”multipart/form-data”」と指定します。
CSSは適当に当ててください。私はこんな感じになりました。
サムネイル表示する枠に高さと幅を持たせておきましょう。
今回は下記のように指定しました。
1 2 3 4 5 6 7 | .thumbnail{ display: inline-block; width: 300px; height: 300px; } |
画像の表示処理
選択された画像の表示は、jQueryで行います。
1 2 3 4 5 6 7 8 9 10 11 | $('.file').on('change',function(e) { var reader = new FileReader(); reader.onload = function (e) { $('.thumbnail').attr('style','background-image: url(' + e.target.result + ');'); }; reader.readAsDataURL(e.target.files[0]); }); |
すると上の動画みたいにファイルアップロードした画像が、表示されます。
ファイルの画像を表示する際にFileReaderオブジェクトを利用します。
readAsDataURLメソッドでファイルの中身を非同期的に読み込みます。
読み込みが完了すると、FileReaderのresultプロパティにdata: URLの文字列が格納されます。
アップロードされたファイルの情報がURLの形で取得できるのですね。
そして読み込みが完了したら、onloadイベントが走ってアップした画像が背景画像で表示されます。
画像の表示処理-改善
先ほどのコードで画像の表示はされます。
しかし、同じページ内で複数のアップロード機能があることを考慮されていません。
また実際に私が書いたコードは、要素の指定にprev()、next()、parent()を多用していて大変読みづらいコードでした。
エンジニアの先輩によって書き直されたコードが以下になります。
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 | var FileUploadUnitObj = (function() { var Obj = function($target) { this.$thumbnail = $target.find('.thumbnail'); this.$file_input = $target.find('input[type=file]'); this.init(); }; Obj.prototype = { //初回設定 init: function() { var _this = this; //イベント登録 this.event(); }, event: function() { var _this = this; //ファイルアップロード this.$file_input.on('change', function (e) { //アップロードされたらサムネイルを表示 _this.changeThumbnail(e); }); }, //サムネイル表示 changeThumbnail: function (e) { var _this = this; var reader = new FileReader(); reader.onload = function (e) { _this.$thumbnail.attr('style', 'background-image: url(' + e.target.result + ');'); }; reader.readAsDataURL(e.target.files[0]); } }; return Obj; })(); var $file_upload_area = $('.js_file_upload_unit'); if ($file_upload_area[0]) { var $file_upload_unit = []; $file_upload_area.each(function(index,elm) { $file_upload_unit.push(new FileUploadUnitObj($(elm))); }); } |
今回は処理が短いので、恩恵が分かりにくいかなと思います。
実際は画像の削除処理やDBに画像が保存されている場合は表示するなど色々処理があって、ごちゃごちゃしていたのが分割されて大変見やすくなっていました。
何回も出てくる処理などはオブジェクト指向で書いたほうがいいそうです。
プラグインの紹介
サムネイル表示するプラグインもあるみたいです。
ただ画像を表示するだけなら、こちらを使ってもいいかもしれません。
ファイルのアップロード-サーバー側
流れを説明すると
1. 新規のカスタム投稿を作成
2. ファイルをサーバー上の特定のフォルダに移動
3. ファイルのパスを値として持つカスタムフィールドを作成
のような感じです。
素のPHPの場合は
1を省略して、3の代わりにDBにファイルのパスを保存する流れでしょうか。
では、実際のコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $insert_post = array('post_title' => '画像アップロード'); $insert_post['post_type'] = 'form'; $insert_post['post_status'] = 'publish'; $new_post_id = wp_insert_post($insert_post); $base_file_name = date('YmdHis_'); if (is_uploaded_file($_FILES["file"]["tmp_name"])) { if (move_uploaded_file($_FILES["file"]["tmp_name"], dirname(__FILE__) . "/../file/" . $base_file_name . $_FILES["file"]["name"])) { $file_name = $_FILES["file"]["name"]; $upload_message = $file_name . "をアップロードしました。"; add_post_meta( $new_post_id, 'file_path', $template_url . '/file/' . $base_file_name . $file_name); } else { $upload_message = "ファイルをアップロードできません。"; } } |
新規のカスタム投稿を作成
カスタム投稿を作成するのは以下のコードですね。
1 2 3 4 5 6 7 | $insert_post = array('post_title' => '画像アップロード'); $insert_post['post_type'] = 'form'; $insert_post['post_status'] = 'publish'; $new_post_id = wp_insert_post($insert_post); |
wp_insert_postメソッドでカスタム投稿を追加しています。カスタム投稿の情報を配列$insert_postに詰め込んでいますね。
利用する際、2行目の
1 2 3 | $insert_post['post_type'] = 'form'; |
の値は追加したいカスタム投稿のものに変更してください。
投稿を作成できたら、wp_insert_postメソッドは戻り値として投稿IDを返します。
ファイルをサーバー上の特定のフォルダに移動
フォームからファイルがアップロードされた際、ファイルはサーバーの一時保存ディレクトリに保存されます。
しかしPHPの処理が終われば削除されてしまいます。
なので、一時保存ディレクトリに保存されている画像を格納したい場所に移動する必要があります。
その処理が以下のコードになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $base_file_name = date('YmdHis_'); if (is_uploaded_file($_FILES["file"]["tmp_name"])) { if (move_uploaded_file($_FILES["file"]["tmp_name"], dirname(__FILE__) . "../file/" . $base_file_name . $_FILES["file"]["name"])) { $file_name = $_FILES["file"]["name"]; $upload_message = $file_name . "をアップロードしました。"; } else { $upload_message = "ファイルをアップロードできません。"; } } |
大まかな流れとしては
・is_uploaded_fileメソッドでPOSTでアップロードされたファイルか判定する(セキュリティ対策)
・move_uploaded_fileメソッドでアップロードされたファイルを新しい場所に移動する
です。
アップロードされたファイルの情報はグローバル変数$_FILESに格納されています。
1 2 3 4 5 6 | // 元のファイル名 $_FILES['file']['name']; // 一時ファイルのファイル名 $_FILES['file']['tmp_name']; |
このように情報を取得できます。
最初のキーはファイルのinputタグのname属性の値を指定します。
ファイルを保存するときは名前をそのまま保存するのではなく、ユニークな値で保存するようにしましょう。
そのまま保存すると、もし同じファイル名のものがアップロードされたらファイルが上書きされる可能性があるのですね。
今回のファイル名は[タイムスタンプ]_元ファイル名としました。
同時に複数人が使用することが想定される場合、ランダムな文字列を挟むなどもっと複雑な名前にしたほうがいいです。
ファイルのパスを値として持つカスタムフィールドを作成
カスタムフィールドを作成するのは以下のコードです。1行で作成できるのですね。
1 2 3 | add_post_meta( $new_post_id, 'file_path', $template_url . '/file/' . $base_file_name . $file_name); |
add_post_meta( $post_id, $meta_key, $meta_value, $unique )
引数
・$post_id:カスタムフィールドを追加する投稿のID
・$meta_key:カスタムフィールドのキー
・$meta_value:カスタムフィールドの値
・$unique:キーをユニークにするかどうか。trueのときは指定したIDに$meta_keyがないときのみ追加
参考記事
ファイルのアップロードはこちらの記事も参考になりますので、読んでみてください。
大容量ファイルアップロードに関係するPHP・Apacheの設定
まとめ
やりたいことが分かっていれば、結構簡単な処理ではないかと思います。
このやりたいことを言葉にして、それを実行する手段を知っているかどうかが重要ですね。
今回は最低限の処理のみ説明させていただきました。
案件で実装する際には、
・画像がDBに保存されている場合は最初から画像を表示、されていない場合は非表示
・画像の更新がある
・フォームで画像をアップロードした後、削除ボタンで画像を非表示にする
・アップロードする画像のサイズを制限する
など追加の処理があります。
このサンプルをベースに考えていただければと思います。参考になれば幸いです。