
Laravel + Vue: загрузка файлов
23.01.2019 22:23 | Laravel, Vue
Кратко рассмотрим как загружать файлы в Laravel и Vuejs.
Создаём новый инстанс Laravel:
laravel new larexp
Переходим в директорию проекта и сразу устанавливаем зависимости:
cd larexp && npm install
Vue
Поскольку это всего лишь рецепт, не будем особо заморачиваться и просто перепишем пример компонента. Зайдём в resources/js/components
и переименуем ExampleComponent.vue
в AttachmentForm.vue
. Не забудем внести изменения в регистрацию компонента в файле resources/js/app.js
:
Vue.component('attachment-form', require('./components/AttachmentForm.vue').default);
Открываем файл компонента и для начала пропишем javascript. В данных у нас будет (отображаемое) название и непосредственно сам файл (attachment). Также понадобятся методы onAttachmentChange()
, которые при изменении файла будет присваивать его свойству attachment
, и метод submit()
для отправки данных на сервер с помощью axios
. Поскольку мы передаём файл, будем использовать объект FormData
, который на выходе использует такой же формат, как и при отправке обычной формы с параметром encoding
, значение которого установлено в multipart/form-data
. Помимо этого, обязательно надо и в опциях передать данное значение. Опять-таки для упрощения, после получения ответа просто выведем в консоль сообщение или отловим ошибку, если таковая будет. Код метода submit()
:
submit () {
const config = { 'content-type': 'multipart/form-data' }
const formData = new FormData()
formData.append('name', this.name)
formData.append('attachment', this.attachment)
axios.post('/', formData, config)
.then(response => console.log(response.data.message))
.catch(error => console.log(error))
}
Быстренько набросаем форму с помощью классов Bootstrap и в итоге файл компонента будет выглядеть так:
<template>
<form @submit.prevent="submit">
<div class="form-group">
<input type="text" class="form-control" placeholder="Name" v-model="name">
</div>
<div class="form-group">
<div class="custom-file">
<input type="file"
class="custom-file-input"
id="customFile"
@change="onAttachmentChange"
>
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</template>
<script>
export default {
data () {
return {
name: null,
attachment: null
}
},
methods: {
submit () {
const config = { 'content-type': 'multipart/form-data' }
const formData = new FormData()
formData.append('name', this.name)
formData.append('attachment', this.attachment)
axios.post('/', formData, config)
.then(response => console.log(response.data.message))
.catch(error => console.log(error))
},
onAttachmentChange (e) {
this.attachment = e.target.files[0]
}
}
}
</script>
Не ради авторизации, а для получения готового шаблона Blade выполним команду:
php artisan make:auth
Открываем resources/views/home.blade.php
и встраиваем компонент:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">File Uploads</div>
<div class="card-body">
<attachment-form></attachment-form>
</div>
</div>
</div>
</div>
</div>
@endsection
Laravel
Создаём модель и миграцию для файлов:
php artisan make:model -m Attachment
Добавляем нужные поля:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAttachmentsTable extends Migration
{
public function up()
{
Schema::create('attachments', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('path')->unique();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('attachments');
}
}
и запускаем:
php artisan migrate
Хотя это и простой пример, подумаем о проекте чуть побольше и о том, что возможно придётся загружать аватар в профиль пользователя, изображения и/или вложения постов или товаров и т.д. Другими словами, вместо того, чтобы прописать метод загрузки прямо в модели, создадим трейт, что позволит горизонтально расширять любые классы, где необходима загрузка каких-либо файлов. Заодно и предусмотрим ситуацию с использованием разных хранилищ и каталогов в этих хранилищах. Файл трейта будет располагаться в app/Traits/Eloquent
(каталоги создаём ручками) и будет называться Uploadable.php
. Вот его код:
<?php
namespace App\Traits\Eloquent;
use Illuminate\Support\Facades\Storage;
trait Uploadable
{
public function upload($file, $storage = 'public', $folder = 'uploads')
{
$filename = uniqid() . '_' . str_replace(' ', '_', $file->getClientOriginalName());
$path = Storage::disk($storage)->putFileAs($folder, $file, $filename);
if (Storage::disk($storage)->exists($path)) {
return $path;
}
return null;
}
}
Можно было и не создавать своё имя файла, а положиться на Laravel, но давайте таки добавим к уникальному идентификатору оригинальное название файла, попутно заменив пробелы на нижние подчёркивания.
Включаем трейт в модель Attachment:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Traits\Eloquent\Uploadable;
class Attachment extends Model
{
use Uploadable;
protected $fillable = ['name', 'path'];
}
Также создадим класс StoreAttachmentRequest
, в который вынесем валидацию (расширения указаны первые пришедшие в голову + максмальный размер файла 1Mb):
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreAttachmentRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|max:255',
'attachment' => 'required|max:1024|mimes:pdf,png,jpeg,gif,txt'
];
}
}
Используем HomeController
, в котором пропишем метод store()
для сохранения файлов. После сохранения отдадим сообщение о том, что файл успешно загружен (то самое сообщение, вывод которого мы ранее прописывали во Vue):
<?php
namespace App\Http\Controllers;
use App\Attachment;
use App\Http\Requests\StoreAttachmentRequest;
class HomeController extends Controller
{
public function index()
{
return view('home');
}
public function store(StoreAttachmentRequest $request)
{
$attachment = new Attachment;
$attachment->name = $request->name;
$attachment->path = $attachment->upload($request->attachment);
$attachment->save();
return response()->json([
'message' => 'Attachment has been successfully uploaded.',
]);
}
}
Последний штрих - роуты. Открываем routes/web.php
и используем то, что есть, добавив маршрут для сохранения:
Route::get('/', 'HomeController@index')->name('home');
Route::post('/', 'HomeController@store');
Готово, можно проверять. Успехов!