画像のファイルのアップロードの基本を学んだところで、少し実践的なことを考えてみましょう。

例えば、ECサイトなら、販売する商品画像を管理画面でアップロードします。しかし、重複となるかもしれないので、アップロードした画像のファイル名で保存することはできません。

そこで、DBが自動発行する商品のIDあるいは商品番号を利用してファイル名を変えてサーバーに保存します。

例えば、Nikeの靴.jpgというファイル名のファイルをアップするなら、その商品のIDが234なら、234.jpgとしてサーバーに保存します。

もう1つ考慮必要なことは、アップする画像はいつも、JPEGの形式とは限らないことです。GIFかもしれませんし、PNGのフォーマットかもしれません。もちろん、手元でJPEGに変換してからアップもできますが、サーバーで違う画像フォーマットに対応できるならそれに越したことありません。

これらのフォーマットの違いの情報を対応するには、アップしたMIMEの情報あるいはファイルの拡張子をDBに保存する必要あります。

まず、DBの設計から始めましょう。ここで必要なのは以下の2つのテーブル、

+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| product_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255)     | NO   |     | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
| updated_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
+------------------+------------------+------+-----+---------+----------------+
| Field            | Type             | Null | Key | Default | Extra          |
+------------------+------------------+------+-----+---------+----------------+
| product_image_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| product_id       | int(11)          | NO   | MUL | NULL    |                |
| mime             | varchar(255)     | NO   |     | NULL    |                |
| created_at       | timestamp        | YES  |     | NULL    |                |
| updated_at       | timestamp        | YES  |     | NULL    |                |
+------------------+------------------+------+-----+---------+----------------+

一応、productproduct_imageのテーブルの関係は、1対多の関係となります。つまり、商品1に対して複数の画像を持つことが可能。

migrationを作成して、以下のように作成してテーブルを作成します。

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
		Schema::create('product', function (Blueprint $table) {
			$table->increments('product_id');
			$table->string('name');
			$table->timestamps();
		});
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('product');
    }
}

class CreateProductImageTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
		Schema::create('product_image', function (Blueprint $table) {
			$table->increments('product_image_id');
			$table->integer('product_id');
			$table->string('mime');
			$table->timestamps();
			$table->index('product_id');
		});
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('product_image');
    }
}

次は管理画面でのルートの作成。

...
Route::group(['prefix' => 'admin', 'middleware' => 'web'], function () {
    ...
    Route::get('product/{product}/image', 'Admin\ProductController@getImage');
    Route::post('product/{product}/image', 'Admin\ProductController@postImage');
    Route::resource('product', 'Admin\ProductController');
    ...
});
...

CRUD(Create, Read, Update, Delete)のオペレーションは、Route::resource('product', ..に任せて、画像に関しては、product/{product}/imageのURIを使用して、それらのメソッドはすべてProductControllerに収めます。今回は、前回と違って管理画面での作業のことに注意してください。

それらの定義は、

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Product;
use App\ProductImage;

class ProductController extends Controller
{
	public function getImage(Product $product)
	{
		return view('admin/product_image', compact('product'));
	}

	public function postImage(Request $request, Product $product)
	{
		$file = $request->file('file');

		$image = new ProductImage;

		$image->product_id = $product->product_id;
		$image->mime = $file->getClientMimeType();

		$image->save();  // product_imageにレコードを作成

		$image->storeImage($file); // アップロードしたファイルを移動
	}

}

となり前回とほぼ同じようなコードです。しかし、前回と違って、product_imageのテーブルに、アップロードしたファイルのMIME情報を入れてレコードを作成しています。


namespace App;

use Illuminate\Database\Eloquent\Model;

class ProductImage extends Model
{
    protected $table = 'product_image';
    protected $primaryKey = 'product_image_id';
    public $incrementing = true;
    public $timestamps = true;

    protected $fillable = [
        'product_id', 'mime'
    ];

    public function storeImage($file)
    {
		$file->move(public_path('images/product'), $this->filename());
    }

    public function filename()
    {
    	$ext = 'jpg';

    	switch($this->mime)
    	{
    		case 'image/jpeg':
    		case 'image/jpg':
    			$ext = "jpg";
    			break;

  			case 'image/png':
  				$ext = "png";
  				break;

  			case 'image/gif':
  				$ext = "gif";
  				break;
    	}

    	return sprintf("%d.%s", $this->product_image_id, $ext);
    }
}

filenameでは、レコードで自動発行されたproduct_image_idとMIMEをもとにした拡張子を合わせて移動先のファイル名を作成します。

コントローラにより使用されるテンプレートも少し違います。

@extends('user.layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">File Upload</div>
                <div class="panel-body">
                    <div class="form-group{{ $errors->has('file') ? ' has-error' : '' }}">
                        <div>
                            <form
                                method="POST"
                                action="/demo/public/admin/product/{{ $product->product_id }}/image"
                                class="dropzone"
                                id="imageUpload"
                                enctype="multipart/form-data">
                                {{ csrf_field() }}
                            </form>
	                    </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

@section('script')

Dropzone.options.imageUpload = {
    dictDefaultMessage: 'アップロードするファイルをここへドロップしてください',
    maxFiles: 10,
    acceptedFiles: '.jpg,.jpeg,.gif,.png',
    maxFilesize: 5, // 5 MB
}
@endsection

ファイルのアップロードの結果は以下のようにレコードの生成となります。

+------------------+------------+------------+---------------------+---------------------+
| product_image_id | product_id | mime       | created_at          | updated_at          |
+------------------+------------+------------+---------------------+---------------------+
|                1 |          1 | image/jpeg | 2016-05-15 19:46:24 | 2016-05-15 19:46:24 |
|                2 |          1 | image/gif  | 2016-05-15 19:46:24 | 2016-05-15 19:46:24 |
|                3 |          1 | image/png  | 2016-05-15 19:46:24 | 2016-05-15 19:46:24 |
+------------------+------------+------------+---------------------+---------------------+

By khino