Multile upload ảnh sử dụng DropzoneJS và Intervention Image trong laravel 5.2

Chúng ta bắt đầu nhé.

Mình sẽ sử dụng thư viện Intervention Image. Nếu bạn làm việc với ảnh thì đây là một thư viện rất hữu ích cho bạn.

"require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.2.*",
        "intervention/image": "^2.3"
},

Tiếp theo mình sẽ tạo một bảng images.

    public function up()
    {
        Schema::create('images', function (Blueprint $table) {
            $table->increments('id');
             $table->string('original_name');
            $table->string('filename');
            $table->string('slug_icon');
            $table->string('slug_original');
            $table->timestamps();
        });
    }

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

Model Image tương ứng

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
    public static $rules = [
        'file' => 'required|mimes:png,gif,jpeg,jpg,bmp'
    ];

    public static $messages = [
        'file.mimes' => 'Uploaded file is not in image format',
        'file.required' => 'Image is required'
    ];
}
Routes.php
Route::get('/','ImageController@getUpload');
Route::post('upload', 'ImageController@postUpload');
Route::delete('upload-delete','ImageController@deleteUpload');

Trong ImageController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Helpers\ImageRepository;

class ImageController extends Controller
{
    protected $image;

    public function __construct(ImageRepository $imageRepository)
    {
        $this->image = $imageRepository;
    }

    public function getUpload()
    {
        return view('upload');
    }

    public function postUpload(Request $request)
    {
        $photo = $request->all();
        $response = $this->image->upload($photo);
        return $response;

    }

    public function deleteUpload(Request $request)
    {

        $filename = $request->input('id');
        
        if(!$filename)
        {
            return 0;
        }

        $response = $this->image->delete( $filename );

        return $response;
    }
}

Thêm thư viện bên ngoài.

<?php

namespace App\Helpers;

use App\Image;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Intervention\Image\ImageManager;

class ImageRepository
{
     public function upload ($form_data)
    {
        $validator = Validator::make($form_data, Image::$rules, Image::$messages);
        if ($validator->fails()) {

            return response()->json([
                'error' => true,
                'message' => $validator->messages()->first(),
                'code' => 400
            ], 400);
        }


        $path = 'uploads/images/' . date('Y') ."/" . date('m') . "/";

        if (!file_exists($path) && !is_dir($path)) {
            File::makeDirectory($path, $mode = 0777, true, true);
        }
        

        $photo = $form_data['file'];

        $originalName = $photo->getClientOriginalName();
        $originalNameWithoutExt = substr($originalName, 0, strlen($originalName) - 4);
        $filename = $this->sanitize($originalNameWithoutExt);
        $allowed_filename = $this->createUniqueFilename( $filename, $path );

        $filenameExt = $allowed_filename .".jpg";

        $uploadSuccess1 = $this->original( $photo, $filenameExt, $path );

        $uploadSuccess2 = $this->icon( $photo, $filenameExt, $path );

        if( !$uploadSuccess1 || !$uploadSuccess2 ) {

            return response()->json([
                'error' => true,
                'message' => 'Server error while uploading',
                'code' => 500
            ], 500);

        }
        
        $sessionImage = new Image;
        $sessionImage->original_name = $originalName;
        $sessionImage->filename      = $allowed_filename;
        $sessionImage->slug_icon = $path . Config::get('images.icon_size') .'-'. $filenameExt;
        $sessionImage->slug_original = $path . Config::get('images.full_size') .'-'. $filenameExt;

        $sessionImage->save();

        return response()->json([
            'error' => false,
            'code'  => 200
        ], 200);

    }

    public function createUniqueFilename( $filename , $path )
    {
        $full_image_path =  $path . Config::get('images.full_size') . '-' . $filename . '.jpg';

        if ( File::exists( $full_image_path ) )
        {
            // Generate token for image
            $imageToken = substr(sha1(mt_rand()), 0, 6);
            return $filename . '-' . $imageToken;
        }
        return $filename;
    }

    /**
     * Optimize Original Image
     */
    public function original( $photo, $filename, $path )
    {
        $manager = new ImageManager();
        $image = $manager->make( $photo )->encode('jpg')->save( $path . Config::get('images.full_size'). '-' . $filename );
        return $image;
    }

    /**
     * Create Icon From Original
     */
    public function icon( $photo, $filename, $path )
    {
        $manager = new ImageManager();
        $image = $manager->make( $photo )->encode('jpg')->resize(200, null, function($constraint){$constraint->aspectRatio();})->save( $path . Config::get('images.icon_size') . '-'  . $filename );
        return $image;
    }

    /**
     * Delete Image From Session folder, based on original filename
     */
    public function delete( $originalFilename)
    {
        $sessionImage = Image::where('original_name', 'like', $originalFilename)->first();
        if(empty($sessionImage))
        {
            return response()->json([
                'error' => true,
                'code'  => 400
            ], 400);

        }

        $full_path1 = $sessionImage->slug_icon;
        $full_path2 = $sessionImage->slug_original;

        if ( File::exists( $full_path1 ) )
        {
            File::delete( $full_path1 );
        }

        if ( File::exists( $full_path2 ) )
        {
            File::delete( $full_path2 );
        }

        if( !empty($sessionImage))
        {
            $sessionImage->delete();
        }

        return response()->json([
            'error' => false,
            'code'  => 200
        ], 200);
    }

    function sanitize($string, $force_lowercase = true, $anal = false)
    {
        $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
            "}", "\\", "|", ";", ":", "\"", "'", "&#8216;", "&#8217;", "&#8220;", "&#8221;", "&#8211;", "&#8212;",
            "—", "–", ",", "<", ".", ">", "/", "?");
        $clean = trim(str_replace($strip, "", strip_tags($string)));
        $clean = preg_replace('/\s+/', "-", $clean);
        $clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;

        return ($force_lowercase) ?
            (function_exists('mb_strtolower')) ?
                mb_strtolower($clean, 'UTF-8') :
                strtolower($clean) :
            $clean;
    }
}

Để có thể sử dụng được thư viện bên ngoài mình cần cấu hình một chút. Tạo một Providers là HelpersServiceProvider.php trong App/Providers và trong method register mình sẽ require tất cả các file trong thư mục Helpers.

    public function register()
    {
        foreach (glob(app_path().'/Helpers/*.php') as $filename){
            require_once($filename);
        }
    }

và trong config/app.php thêm dòng này vào providers

App\Providers\HelpersServiceProvider::class,

Tiếp theo sẽ đến phần view

trong resources/views/layouts/index.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload Image</title>
    <!-- Latest compiled and minified CSS & JS -->
    <link rel="stylesheet" href="{{url('css/bootstrap.min.css')}}" >
	
	@yield('header')
	
</head>
<body>
    <nav class="navbar navbar-default" role="navigation">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Giáp Hiệp</a>
            </div>
    
            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse navbar-ex1-collapse">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="#">Home</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>

       
            </div><!-- /.navbar-collapse -->
        </div>
    </nav>

    <div class="container-fluid">
        @yield('content')
    </div>
    <script src="//code.jquery.com/jquery.js"></script>
    <script src="{{url('js/bootstrap.min.js')}}"></script>
    @yield('footer')
</body>
</html>

 

và trong resources/views/upload.blade.php

@extends('layouts.index')

@section('header')
<link rel="stylesheet" href="{{url('css/dropzone.css')}}">
<meta name="_token" content="{{csrf_token()}}">
@endsection

@section('content')
<div class="row">
	<div class="col-xs-12">
	  <div class="box box-primary">
	    <div class="box-header">
	        <h3 class="box-title">Images <span id="photoCounter"></span></h3>
	    </div><!-- /.box-header -->
	    <div class="box-body">
	        <form action="{{url('upload')}}" class="dropzone" id="real-dropzone" enctype="multipart/form-data">
	            {!! csrf_field() !!}
	                <div class="dz-message">
	                </div>
	                <div class="fallback">
	                    <input name="file" type="file" multiple />
	                </div>
	                <div class="dropzone-previews" id="dropzonePreview"></div>

	                <h4 style="text-align: center;">Drop files here or click upload </h4>
	                </form>

	                <div class="jumbotron how-to-create">
	                <ul>
	                    <li>Images are uploaded as soon as you drop them</li>
	                    <li>Maximum allowed size of image is 3MB</li>
	                </ul>
	            </div>

	                 <!-- Dropzone Preview Template -->
	                <div id="preview-template" style="display: none;">

	                    <div class="dz-preview dz-file-preview">
	                        <div class="dz-image"><img data-dz-thumbnail=""></div>

	                        <div class="dz-details">
	                            <div class="dz-size"><span data-dz-size=""></span></div>
	                            <div class="dz-filename"><span data-dz-name=""></span></div>
	                        </div>
	                        <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress=""></span></div>
	                        <div class="dz-error-message"><span data-dz-errormessage=""></span></div>

	                        <div class="dz-success-mark">
	                            <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
	                                <!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
	                                <title>Check</title>
	                                <desc>Created with Sketch.</desc>
	                                <defs></defs>
	                                <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
	                                    <path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
	                                </g>
	                            </svg>
	                        </div>

	                        <div class="dz-error-mark">
	                            <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
	                                <!-- Generator: Sketch 3.2.1 (9971) - http://www.bohemiancoding.com/sketch -->
	                                <title>error</title>
	                                <desc>Created with Sketch.</desc>
	                                <defs></defs>
	                                <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
	                                    <g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">
	                                        <path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>
	                                    </g>
	                                </g>
	                            </svg>
	                        </div>

	                    </div>
	                </div>
	                <!-- End Dropzone Preview Template -->

	    </div><!-- /.box-body -->
	    </div><!-- /.box -->
	</div>
</div>
@endsection

@section('footer')
<script src="{{url('js/dropzone.js')}}"></script>
<script src="{{url('js/dropzone-config.js')}}"></script>
@endsection

Cấu hình Dropzone.

var URL_PATH = $('#url_path').val();
var photo_counter = 0;
Dropzone.options.realDropzone = {

    uploadMultiple: false,
    parallelUploads: 100,
    maxFilesize: 3,
    previewsContainer: '#dropzonePreview',
    previewTemplate: document.querySelector('#preview-template').innerHTML,
    addRemoveLinks: true,
    dictRemoveFile: 'Remove',
    dictFileTooBig: 'Image is bigger than 3MB',

    // The setting up of the dropzone
    init:function() {

        this.on("removedfile", function(file) {
            console.log(file);

            $.ajaxSetup({
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
                }
            });
            $.ajax({
                type: 'DELETE',
                url: 'upload-delete',
                data: {id: file.name},
                dataType: 'html',
                success: function(data){
                    var rep = JSON.parse(data);
                    if(rep.code == 200)
                    {
                        photo_counter--;
                        $("#photoCounter").text( "(" + photo_counter + ")");
                    }

                }
            });

        } );
    },
    error: function(file, response) {
        if($.type(response) === "string")
            var message = response; //dropzone sends it's own error messages in string
        else
            var message = response.message;
        file.previewElement.classList.add("dz-error");
        _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]");
        _results = [];
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            node = _ref[_i];
            _results.push(node.textContent = message);
        }
        return _results;
    },
    success: function(file,done) {
        photo_counter++;
        $("#photoCounter").text( "(" + photo_counter + ")");
    }
}




Code:

https://github.com/Badman37/image-upload