• Время чтения ~8 мин
  • 30.06.2022

Загрузка файлов в веб-приложениях — это то, с чем я всегда боролся.

Они просты, когда вы просто помещаете <input type="file" тег внутрь <form> и отправляете форму старомодным способом.

Однако все становится сложнее в интерфейсном интерфейсе, богатом JavaScript, с отправкой форм AJAX.

Прямая проволока ТОЛЬКО что добавлена поддержка загрузки файлов из коробки. Я думаю, что это изменит правила игры, и не могу дождаться, чтобы рассказать вам все об этом!

Давайте рассмотрим две наиболее распространенные стратегии загрузки файлов (и их недостатки), а затем мы увидим, как Livewire является лучшим из обоих миров.

Способ отправки

формы First up, традиционная отправка формы. Laravel делает обработку этих видов загрузок невероятно простой. Давайте рассмотрим пример кода:

Опыт разработчика:
1. Добавьте входные данные файла в форму, которая отправляет на сервер
2. Проверка и загрузка файла с контроллера на сервере

<form action="/profile" method="POST" type="multipart">
  <input type="text" name="email">
 
    <input type="file" name="photo">
 
    <button>Save</button>
</form>
public function store()
{
    request()->validate([
    'username' => 'required|email',
    'photo' => 'image|max:2000',
  ]);
 
  $filename = request('photo')->store('photos');
 
  Profile::create([
    'username' => request('username'),
    'photo' => $filename,
  ]);
}

Этот подход прост и понятен, но имеет несколько недостатков:

  • Файлы должны быть отправлены вместе с формой при отправке (что делает процесс отправки формы медленным)
  • Файлы не могут быть проверены из PHP до тех пор, пока форма не будет отправлена
  • Показать пользователю предварительный просмотр файла, который они выбрали, может быть выполнен только из сложного JavaScript на интерфейсе
  • Все файлы должны проходить через ваш сервер Laravel перед сохранением в другом месте (что может быть зависанием для использования S3 с бессерверными средами, такими как Vapor)
  • Поскольку файл не загружается в отдельную выделенную конечную точку, библиотеки загрузки файлов, такие как Filepond, невозможно использовать.

Способ AJAXy

Рассмотрим современный интерфейс на основе JS. Больше нет традиционных форм. Все обрабатывается с помощью AJAX-запросов к серверу с использованием чего-то вроде axios или fetch().

К сожалению, загрузка файла через AJAX не так проста, как вы надеялись.

Кроме того, большинство разработчиков предпочитают использовать технику, называемую «неопубликованной загрузкой», для загрузки файла ДО того, как форма будет отправлена по причинам, о которых я упоминал ранее.

Опыт разработчика:

  • Создайте форму с входным файлом
  • Прослушайте новый файл, выбранный в элементе ввода с помощью JavaScript
  • Загрузите файл немедленно в выделенную конечную точку, сохранив его временно и вернув имя
  • файла Когда форма отправлена, отправьте временное имя файла с формой
  • Обработка отправки формы путем загрузки временного файла на основе имени файла, Хранение и удаление временного файла
  • Поскольку некоторые файлы загружены, но не отправлены на хранение, выполните запланированную команду для очистки неохраняемых временных файлов через день или около того

Вот компонент Vue, который я собрал на слух (нет гарантий, что он действительно работает), чтобы продемонстрировать этот метод:

<template>
    <form @submit.prevent="save">
      <input v-model="email" type="text" name="email">
    <input @change="updatePhoto" type="file" name="photo">
    <button>Save</button>
  </form>
</template>
 
<script>
    export default {
    data() {
      return {
        email: '',
        photo: '',
        file: null,
      }
    },
 
    methods: {
      updatePhoto(event) {
        let formData = new FormData();
 
        formData.append('file', event.target.files[0]);
 
        axios.post('/file-upload', formData)
            .then(response => {
            this.photo = response.data.filePath
          })
      },
 
      save() {
        axios.post('/profile', {
          email: this.email,
          photo: this.photo,
        }).then(response => {
          ...
        })
      },
    }
  }
</script>

Теперь давайте посмотрим на серверный код, необходимый для того, чтобы это работало:

public function handleUpload()
{
  request()->validate(['file' => 'file|max:10000']);
 
  return [
    'file' => request('file')->storeAs('/tmp'),
  ];
}
 
public function handleFormSubmit()
{
  request()->validate([
    'email' => 'required|email',
    'photo' => 'required|string',
  ]);
 
  $tmpFile = Storage::get('/tmp/'.request('photo'));
  Storage::put('/avatars/'.request('photo'), $tmpFile);
  Storage::delete('/tmp/'.request('photo'));
 
  Profile::create([
    'username' => request('username'),
    'photo' => request('photo'),
  ]);
}

Как я уже упоминал, эта стратегия называется «неопубликованная загрузка». Он обеспечивает всю мощность и гибкость, которые вы можете пожелать, но за счет некоторых экстремальных количеств дополнительной сложности. Часто для чего-то такого простого, как позволить пользователю загрузить аватар.

Обратите внимание, что этот код станет НАМНОГО сложнее, когда мы начнем добавлять такие вещи, как проверка, загрузка спиннеров и т. Д.

Мы можем сделать лучше, чем это. Гораздо лучше.

Поскольку Livewire

основан на JavaScript, мы не можем просто использовать традиционные формы.

Вместо этого Livewire использует «неопубликованную загрузку», но скрывает всю сложность для вас (с нулевой конфигурацией), предоставляя вам опыт традиционной отправки форм, но с несколькими плохими обновлениями.

Вот самый простой пример:

Опыт разработчика

  • Добавление входных данных файла в форму (и применениеwire:model="...")
  • Обработка отправки формы с помощью Livewire и проверка и хранение файла, как обычно в контроллере
class Profile extends Component
{
  use WithFileUploads;
 
  public $email;
  public $photo;
 
  public function save()
  {
    $this->validate([
      'email' => 'required|email',
      'photo' => 'image|max:2000',
    ]);
 
    $filename = $this->photo->store('photos');
 
    Profile::create([
      'username' => $this->username,
      'photo' => $filename,
    ]);
  }
 
  public function render()
  {
    return view('livewire.profile');
  }
}
<form wire:submit.prevent="save">
  <input wire:model="email" type="text" name="email">
 
    <input wire:model="photo" type="file" name="photo">
 
    <button>Save</button>
</form>

Довольно просто, а?

Помните, что под капотом Livewire фактически «загружается» файл во временную директорию.

Преимущество здесь, однако, заключается в том, что вам не нужно выполнять НИ ОДНОЙ работы.

ВСЕ заботится о Вас.

Позвольте мне просмотреть все плохие вещи, которые Livewire позволяет вам делать с вашими файлами-загрузками.

Обработка нескольких загрузок

Обработка нескольких загрузок в Livewire - это сложно.

Вот он:

<input wire:model=“photos” type=“file” multiple>

Livewire обнаружит атрибут «множественный» и обработает все за вас.

На стороне сервера свойство $this->photos будет представлять собой МАССИВ загруженных файлов.

Вы можете проверить и сохранить их, как и любой другой массив:

...
public $email;
public $photos;
 
public function save()
{
  $this->validate([
    'email' => 'required|email',
    'photos.*' => 'image|max:2000',
  ]);
 
  $filenames = collect($this->photos)->map->store('photos');
 
    Profile::create([
      'username' => $this->username,
      'photos' => $filenames->implode(','),
    ]);
}
...

Отображение индикаторов загрузки

Хотите показать пользователю индикатор загрузки во время загрузки файла?

Опять же, обработайте это так, как вы обычно делаете в Livewire с wire:loadingпомощью :

<input wire:model=“photo” type=“file”>
 
<div wire:loading wire:target="photo">Uploading...</div>

«Загрузка...» теперь сообщение будет отображаться в течение всего срока загрузки.

Отображение индикаторов

прогресса Простого индикатора загрузки недостаточно?

Livewire отправляет несколько полезных событий JavaScript, которые могут быть легко подключены чем-то вроде AlpineJS.

Вот простой индикатор загрузки, написанный с помощью Alpine внутри компонента Livewire:

<div
    x-data="{ progress: 0, uploading: false }"
    @livewire-upload-start="uploading = true"
  @livewire-upload-finish="uploading = false"
  @livewire-upload-error="uploading = false"
  @livewire-upload-progress="progress = $event.detail.progress"
>
  <input wire:model=“photo” type=“file”>
 
  <progress max="100" x-bind:value="progress" x-show="uploading"></progress>
</div>

Проверка

файлов в реальном времени Вы можете проверить файл, как только он выбран, так же, как вы проверяете ЛЮБОЕ значение при его обновлении в Livewire.

Подключитесь к «обновленному» жизненному циклу вашего свойства файла, и все готово!

class Profile extends Component
{
  use WithFileUploads;
 
  public $email;
  public $photo;
 
  // Validate the photo as soon as it's set.
  public function updatedSave()
  {
    $this->validate([
      'photo' => 'image|max:2000',
    ]);
  }
 
  ...
}

Обратите внимание, что вы получаете опыт проверки в режиме реального времени на интерфейсе, но внутренний код - это тот же код обработки ошибок Laravel, к которому вы привыкли:

...
<input wire:model="photo" type="file" name="photo">
 
@error('photo') {{ $message }} @enderror
...

Direct Upload To S3

Livewire позволяет вашим пользователям легко загружать файлы, которые НИКОГДА не касаются вашего сервера.

Это ЧРЕЗВЫЧАЙНО полезно для больших загрузок или бессерверных сред, таких как Laravel Vapor.

Укажите в своем приложении диск хранения, использующий s3 драйвер хранилища Laravel, и все будет работать волшебным образом:

config/livewire.php

...
'temporary_file_upload' => [
  'disk' => 's3',
  ...
],
...

Livewire теперь загрузит временный файл непосредственно в S3, используя предварительно подписанный URL-адрес загрузки.

Файлы будут храниться в каталоге, вызываемом livewire-tmp/ по умолчанию.

Чтобы настроить эту папку для удаления файлов старше 24 часов, Livewire предоставляет удобную команду artisan:

php artisan livewire:configure-s3-upload-cleanup

Примечание: вы все равно можете обращаться со свойством $this->photo как с обычным, фактически не вызывая S3. Только при доступе к его содержимому ($this->photo->get();) или его размеру (для проверки: $this->photo->getSize()) будет вызываться S3.

Это позволяет быстро выполнять запросы Livewire для задач, не связанных с загрузкой.

Временный предварительный просмотр URL-адресов

Иногда вы можете показать пользователю предварительный просмотр файла, который он только что выбрал, прежде чем он нажмет «отправить».

Как правило, это сложно, потому что временные загружаемые файлы не являются общедоступными.

Livewire позволяет чрезвычайно легко генерировать временные, безопасные, общедоступные URL-адреса для использования в браузере.

Вот пример представления компонента, которое будет отображать предварительный просмотр изображения вновь загруженного файла для пользователя:

...
@if ($photo)
  <img src="{{ $photo->temporaryUrl() }}">
@endif
 
<input wire:model="photo" type="file" name="photo">
...

Теперь, как только пользователь выберет файл (и после того, как Livewire обработает новую загрузку внутренне), он увидит предварительный просмотр, прежде чем они решат отправить форму.

Еще больше S3 Awesomeness

Если вы настроили временный диск загрузки Livewire на «s3», то ->temporaryUrl() сгенерируете предварительно подписанный URL-адрес временного файла, который считывается непосредственно из S3.

Тестирование загрузки

файлов Тестирование загрузки файлов с помощью Livewire невероятно просто.

Вы можете использовать все функции, к которым вы привыкли, со стандартными контроллерами Laravel.

Вот пример теста, охватывающего загрузку файлов в компоненте:

/** @test **/
function can_upload_photo() {
  Storage::fake();
 
  $file = UploadedFile::fake()->image('avatar.png');
 
  Livewire::test(Profile::class)
    ->set('email', '[email protected]')
    ->set('photo', $file)
    ->call('save');
 
  Storage::assertExists('avatar.png');
}

Интеграция с Filepond

Filepond - это фантастическая библиотека JavaScript, которая делает перетаскивание и другие связанные с загрузкой причуды чрезвычайно простыми.

Функция загрузки файлов Livewire была создана для обслуживания интеграций, подобных этой.

Если вас интересует, как это выглядит, перейдите к Livewire's File Upload Screencasts для подробного учебника.

Подписание Whew

, какая особенность. Простой на первый взгляд, но гораздо более мощный, если у вас есть более глубокие потребности.

Моя цель с Livewire - сделать веб-разработку в Laravel максимально простой (не будучи «ручной»). Я надеюсь, что эта особенность перекликается с этой мантрой.

Если вы готовы погрузиться, перейдите к документации или посмотрите новую серию скринкастов по загрузке файлов, чтобы узнать больше!

Удачной загрузки!
–Калеб

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297