12/08/2018, 17:06

Ví dụ về upload nhiều files trong Laravel 5.5

Upload file có lẽ là phần cơ bản của bất kì dự án nào nhưng với những newbie thì có thể gặp vài vấn đề lúc mới tiếp xúc, ví dụ như thực hiện upload nhiều files cùng lúc có validation sẽ không biết làm như nào. Trong bài viết này tôi sẽ tập trung vào một phần nhỏ đó trong nhiều thứ có thể làm khi ...

Upload file có lẽ là phần cơ bản của bất kì dự án nào nhưng với những newbie thì có thể gặp vài vấn đề lúc mới tiếp xúc, ví dụ như thực hiện upload nhiều files cùng lúc có validation sẽ không biết làm như nào. Trong bài viết này tôi sẽ tập trung vào một phần nhỏ đó trong nhiều thứ có thể làm khi upload nhiều file lên. Tôi sử dụng thư mục storage của Laravel và có DB khi upload files lên kết hợp dùng Bootstrap để code được dễ dàng hơn.

Trước hết tôi sẽ đi tạo ra model và bảng nơi tôi sẽ lưu files. Trong thư mục root của ứng dụng tôi chạy lệnh sau :

php artisan make:model Product -m
php artisan make:model ProductDetails -m

Khi migration và model đã được tạo thành công thì đến file app/Product.php và thêm code protected $fillable = ['name']; vào :

<?php

namespace App;

use IlluminateDatabaseEloquentModel;

class Product extends Model
{
    protected $fillable = ['name'];
}

Sau đó đến thư mục database/migration và mở file migration bạn sẽ nhìn thấy cấu trúc mặc định có trong đó gồm id và timestamps, hãy thêm $table->string('name'); vào như dưới :

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

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

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

Còn ProductDetails sẽ thêm code như sau :

<?php
namespace App;
use IlluminateDatabaseEloquentModel;
 
class ProductDetails extends Model
{
    protected $fillable = ['product_id', 'filename'];

    public function product()
    {
        return $this->belongsTo('AppProduct');
    }
}
?>

Trong đoạn code trên tôi sử dụng belongsTo bởi vì ProductDetails là thuộc về bảng Product và product_id là khoá ngoại, cái này là quan hệ nghịch đảo (inverse relation) trong Laravel.

Bạn hãy mở file tương ứng của productdetails trong thư mục database/migration, tương tự như product bạn sẽ thấy có id và timestamps, hãy thêm vào đoạn code dưới :

            $table->integer('product_id')->unsigned();
            $table->foreign('product_id')->references('id')->on('products');
            $table->string('filename');

thành nội dung đầy đủ như sau :

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateProductDetailsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('product_details', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('product_id')->unsigned();
            $table->foreign('product_id')->references('id')->on('products');
            $table->string('filename');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('product_details');
    }
} 
?>

Tiếp theo, trong app/Providers/AppServiceProvider.php hãy thiết lập mặc định độ dài của chuỗi là 200 :

<?php

namespace AppProviders;

use IlluminateSupportServiceProvider;
use IlluminateSupportFacadesSchema;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
	public function boot()
	{
		Schema::defaultStringLength(200);
	}
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
?>

Trong ứng dụng Laravel thiết lập DB được handle bởi hai files env và config/database.php, tôi đã tạo DB với tên là multiupload và thiết lập kết nối tới nó.

CREATE SCHEMA `multiupload` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ;
<?php
// config/database.php
    'default' => env('DB_CONNECTION', 'mysql'),
    // giản lược
        'mysql' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'multiupload'),
            'username' => env('DB_USERNAME', 'root'),
            'password' => env('DB_PASSWORD', 'xxx'),
            'unix_socket' => env('DB_SOCKET', '),
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix' => ',
            'strict' => true,
            'engine' => null,
        ],
        // giản lược
?>
// .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=multiupload
DB_USERNAME=root
DB_PASSWORD=xxx

Hãy chạy lệnh sau để tạo bảng trong DB đó :

php artisan migrate

Bạn kiểm tra DB thì sẽ thấy có 5 bảng được tạo thành công như hình

Thiết lập Route

Route thiết lập URL của ứng dụng và các phương thức của cho URL đó. Routes được lưu ở trong routes/web.php và chứa code sau :

Route::get('/multiuploads', 'UploadController@uploadForm');
Route::post('/multiuploads', 'UploadController@uploadSubmit');

Tạo Controller

Chỉ cần chạy lệnh sau bạn sẽ tạo ra được Controller :

php artisan make:controller UploadController

Kế đến mở app/Http/Controller/UploadController.php rồi thêm vào code như sau:

<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;

class UploadController extends Controller
{ 
    public function uploadForm()
    {
        return view('upload_form');
    }
 
    public function uploadSubmit(Request $request)
    {
        // coding ….
    }
}
?>

Tạo View (upload_form.blade.php)

Trong View file tôi sử dụng Bootstrap để stlyle cho code, dùng jQuery, JavaScript.

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="awidth=device-awidth, initial-scale=1">
 
	<title>Laravel Multi Uploading Example</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" 		integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
 
	 
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" 		integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
 
	 
	<link href="https://fonts.googleapis.com/css?family=Raleway:100,600" rel="stylesheet" type="text/css">

	 
	<style>
		.container {
			margin-top:2%;
		}
	</style>
</head>
<body>
	@if (count($errors) > 0)
	<div class="alert alert-danger">
		<ul>
			@foreach ($errors->all() as $error)
				<li>{{ $error }}</li>
			@endforeach
		</ul>
	</div>
	@endif
	<div class="container">
		<div class="row">
			<div class="col-md-2"> <img src="/upload_icon.png" awidth="80" /></div>
			<div class="col-md-8"><h2>Laravel 5.5 Multiple File Uploading</h2></div>
		</div>
		<br>
		<div class="row">
			<div class="col-md-3"></div>
			<div class="col-md-6">
				<form action="/multiuploads" method="post" enctype="multipart/form-data">
					{{ csrf_field() }}
					<div class="form-group">
						<label for="Product Name">Product Name</label>
						<input type="text" name="name" class="form-control"  placeholder="Product Name" >
					</div>
					<label for="Product Name">Product photos (can upload multi photos):</label>
					<br>
					<input type="file" class="form-control" name="photos[]" multiple />
					<br><br>
					<input type="submit" class="btn btn-primary" value="Upload" />
				</form>
			</div>
		</div>
	</div>
</body>
</html>

Validation

Tôi sẽ dùng Bootstrap để hiển thị cảnh báo cho validation và sử dụng các phương thức validate của Laravel để kiểm tra loại file được phép upload.

<?php
namespace AppHttpControllers;
use AppProduct;
use AppProductDetails;
use IlluminateHttpRequest;

class UploadController extends Controller
{
	public function uploadForm()
	{
		return view('upload_form');
	}
 
	public function uploadSubmit(Request $request)
	{
        // Thiết lập required cho cả 2 mục input
		$this->validate($request, [
			'name' => 'required',
			'photos'=>'required',]
		);
        // kiểm tra có files sẽ xử lý
		if($request->hasFile('photos')) {
			$allowedfileExtension=['jpg','png'];
			$files = $request->file('photos');
            // flag xem có thực hiện lưu DB không. Mặc định là có
			$exe_flg = true;
			// kiểm tra tất cả các files xem có đuôi mở rộng đúng không
			foreach($files as $file) {
				$extension = $file->getClientOriginalExtension();
				$check=in_array($extension,$allowedfileExtension);
				
				if(!$check) {
                    // nếu có file nào không đúng đuôi mở rộng thì đổi flag thành false
					$exe_flg = false;
					break;
				}
			} 
			// nếu không có file nào vi phạm validate thì tiến hành lưu DB
			if($exe_flg) {
                // lưu product
				$products= Product::create($request->all());
                // duyệt từng ảnh và thực hiện lưu
				foreach ($request->photos as $photo) {
					$filename = $photo->store('photos');
					ProductDetails::create([
						'product_id' => $products->id,
						'filename' => $filename
					]);
				}
				echo "Upload successfully";
			} else {
				echo "Falied to upload. Only accept jpg, png photos.";
			}
		}
	}
}
?>

Laravel cung cấp chức storage để lưu tất cả loại dữ liệu bao gồm cả ảnh và các loại files khác. Với config/filesystems.php bạn có thể chỉ định rõ nơi mà nội dung của bạn được lưu trữ.

return [
    'default' => 'local',
    'disks' => [
        'local' => ['driver' => 'local',
        'root' => storage_path('app')], 
// ...

Để cho demo thì tôi đã tạo một thư mục photos trong storage/app/. Thực hiện chạy php artisan serve

Giờ hãy truy cập vào http://localhost:8000/multiuploads bạn sẽ thấy như hình dưới.

Hãy thử thực hiện upload khi chưa chỉ định cả 2 mục không có gì bạn sẽ gặp lỗi required.

Tương tự bạn hãy thử với file có đuôi không đúng định dạng sẽ bị báo Falied to upload. Only accept jpg, png photos.. Còn khi thực hiện đúng file sẽ được upload lên folder storage/app/photos/ và dữ liệu được lưu vào 2 bảng products và product_details.

Khi upload lên storage/app/photos/ tên file qua store() sẽ được mã hoá nên nếu bạn muốn giữ nguyên tên file gốc thì có thể dùng storeAs() kết hợp dùng getClientOriginalName() để lấy ra tên gốc của file như sau :

$filename = $photo->storeAs('photos', $photo->getClientOriginalName());
0