Saltearse al contenido

Subida de archivos

Livewire ofrece un potente soporte para subir archivos dentro de sus componentes.

Primero, añade el rasgo WithFileUploads a tu componente. Una vez que este rasgo se ha añadido a su componente, puede utilizar wire:model en las entradas de archivos como si fueran cualquier otro tipo de entrada y Livewire se encargará del resto.

Aquí hay un ejemplo de un componente simple que maneja la carga de una foto:

<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
class UploadPhoto extends Component
{
use WithFileUploads;
#[Validate('image|max:1024')] // 1MB Max
public $photo;
public function save()
{
$this->photo->store(path: 'photos');
}
}
<form wire:submit="save">
<input type="file" wire:model="photo">
@error('photo') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Guardar Foto</button>
</form>

Desde la perspectiva del desarrollador, la gestión de las entradas de archivos no difiere de la gestión de cualquier otro tipo de entrada: Añade wire:model a la etiqueta <input> y todo lo demás estará hecho.

Sin embargo, hay más cosas que suceden bajo el capó para que la carga de archivos funcione en Livewire. He aquí un vistazo a lo que sucede cuando un usuario selecciona un archivo para subir:

  • 1º Cuando se selecciona un nuevo archivo, el JavaScript de Livewire realiza una petición inicial al componente en el servidor para obtener una URL de subida temporal “firmada”.
  • 2º Una vez recibida la URL, JavaScript realiza la “carga” real a la URL firmada, almacenando la carga en un directorio temporal designado por Livewire y devolviendo el ID hash único del nuevo archivo temporal.
  • 3º Una vez cargado el archivo y generado el hash ID único, el JavaScript de Livewire realiza una última petición al componente en el servidor, indicándole que “establezca” la propiedad pública deseada en el nuevo archivo temporal.
  • 4º Ahora, la propiedad pública (en este caso, $photo) se establece en el archivo temporal cargado y está lista para ser almacenada o validada en cualquier momento.

Almacenamiento de archivos subidos

El ejemplo anterior muestra el escenario de almacenamiento más básico: mover el archivo subido temporalmente al directorio “photos” del disco del sistema de archivos predeterminado de la aplicación.

Sin embargo, es posible que desee personalizar el nombre del archivo almacenado o incluso especificar un “disco” de almacenamiento específico en el que guardar el archivo (como S3).

Livewire hace honor a las mismas APIs que Laravel utiliza para almacenar los archivos subidos, así que no dudes en consultar la documentación de carga de archivos de Laravel. Sin embargo, a continuación se presentan algunos escenarios comunes de almacenamiento y ejemplos:

public function save()
{
// Almacena el archivo en el directorio "photos" del disco del sistema de archivos por defecto
$this->photo->store(path: 'photos');
// Almacena el archivo en el directorio "photos" en un disco "s3" configurado
$this->photo->store(path: 'photos', options: 's3');
// Almacena el archivo en el directorio "photos" con el nombre de archivo "avatar. png"
$this->photo->storeAs(path: 'photos', name: 'avatar');
// Almacenar el archivo en el directorio "photos" en un disco "s3" configurado con el nombre de archivo "avatar. png"
$this->photo->storeAs(ruta: 'fotos', nombre: 'avatar', opciones: 's3');
// Almacenar el archivo en el directorio "fotos", con visibilidad "pública" en un disco "s3" configurado
$this->photo->storePublicly(ruta: 'fotos', opciones: 's3');
// Almacenar el archivo en el directorio "fotos", con el nombre "avatar. png", con visibilidad "public" en un disco "s3" configurado
$this->photo->storePubliclyAs(path: 'photos', name: 'avatar', options: 's3');
}

Manejo de múltiples archivos

Livewire maneja automáticamente la carga de múltiples archivos detectando el atributo multiple en la etiqueta <input>.

Por ejemplo, a continuación se muestra un componente con una propiedad array llamada $photos. Al añadir multiple a la entrada de archivos del formulario, Livewire añadirá automáticamente nuevos archivos a este array:

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
class UploadPhotos extends Component
{
use WithFileUploads;
#[Validate(['photos.*' => 'image|max:1024'])]
public $photos = [];
public function save()
{
foreach ($this->photos as $photo) {
$photo->store(path: 'photos');
}
}
}
<form wire:submit="save">
<input type="file" wire:model="photos" multiple>
@error('photos.*') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Guardar Foto</button>
</form>

Validación de archivos

Como hemos comentado, validar la subida de archivos con Livewire es lo mismo que manejar la subida de archivos desde un controlador estándar de Laravel.

Para más información sobre la validación de archivos, consulte la documentación de validación de archivos de Laravel.

URLs de previsualización temporales

Después de que un usuario elija un archivo, normalmente debería mostrarle una previsualización de ese archivo antes de que envíe el formulario y almacene el archivo.

Livewire hace esto trivial usando el método ->temporaryUrl() en los archivos subidos.

Veamos un ejemplo de carga de archivos con previsualización de imágenes:

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
class UploadPhoto extends Component
{
use WithFileUploads;
#[Validate('image|max:1024')]
public $photo;
// ...
}
<form wire:submit="save">
@if ($photo)
<img src="{{ $photo->temporaryUrl() }}">
@endif
<input type="file" wire:model="photo">
@error('photo') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Guardar Foto</button>
</form>

Como se ha comentado anteriormente, Livewire almacena los archivos temporales en un directorio no público; por lo tanto, normalmente no hay una forma sencilla de exponer una URL temporal y pública a sus usuarios para la previsualización de imágenes.

Sin embargo, Livewire resuelve este problema proporcionando una URL temporal firmada que simula ser la imagen subida para que su página pueda mostrar una vista previa de la imagen a sus usuarios.

Esta URL está protegida contra la visualización de archivos en directorios superiores al directorio temporal. Y, como está firmada, los usuarios no pueden abusar de esta URL para previsualizar otros archivos de su sistema.

Testing de carga de archivos

Puede utilizar los helpers de testing de carga de archivos existentes en Laravel para probar la carga de archivos.

A continuación se muestra un ejemplo completo de prueba del component UploadPhoto con Livewire:

<?php
namespace Tests\Feature\Livewire;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use App\Livewire\UploadPhoto;
use Livewire\Livewire;
use Tests\TestCase;
class UploadPhotoTest extends TestCase
{
/** @test */
public function can_upload_photo()
{
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.png');
Livewire::test(UploadPhoto::class)
->set('photo', $file)
->call('upload', 'uploaded-avatar.png');
Storage::disk('avatars')->assertExists('uploaded-avatar.png');
}
}

A continuación se muestra un ejemplo del componente UploadPhoto necesario para superar la prueba anterior:

use Livewire\Component;
use Livewire\WithFileUploads;
class UploadPhoto extends Component
{
use WithFileUploads;
public $photo;
public function upload($name)
{
$this->photo->storeAs('/', $name, disk: 'avatars');
}
// ...
}

Para más información sobre pruebas de carga de archivos, consulte la documentación de pruebas de carga de archivos de Laravel.

Subida directa a Amazon S3

Como se ha comentado anteriormente, Livewire almacena todas las subidas de archivos en un directorio temporal hasta que el desarrollador almacena el archivo de forma permanente.

Por defecto, Livewire utiliza la configuración de disco del sistema de archivos por defecto (normalmente local) y almacena los archivos dentro de un directorio livewire-tmp/.

En consecuencia, las subidas de archivos siempre utilizan su servidor de aplicaciones, incluso si elige almacenar los archivos subidos en un bucket S3 más tarde.

Si desea omitir su servidor de aplicaciones y en su lugar almacenar las subidas temporales de Livewire en un cubo de S3, puede configurar ese comportamiento en el archivo de configuración config/livewire.php de su aplicación. En primer lugar, establezca livewire.temporary_file_upload.disk como s3 (u otro disco personalizado que utilice el controlador s3):

return [
// ...
'temporary_file_upload' => [
'disk' => 's3',
// ...
],
];

Ahora, cuando un usuario sube un archivo, el archivo nunca será almacenado en tu servidor. En su lugar, se subirá directamente a su cubo de S3 dentro del subdirectorio livewire-tmp/.

Configuración de la limpieza automática de archivos

El directorio de carga temporal de Livewire se llenará de archivos rápidamente; por lo tanto, es esencial configurar S3 para limpiar los archivos de más de 24 horas.

Para configurar este comportamiento, ejecuta el siguiente comando Artisan desde el entorno que está utilizando un bucket S3 para la carga de archivos:

php artisan livewire:configure-s3-upload-cleanup

Ahora, cualquier archivo temporal de más de 24 horas será limpiado por S3 automáticamente.

Indicadores de carga

Aunque wire:model para la carga de archivos funciona de forma diferente a otros tipos de entrada de wire:model, la interfaz para mostrar indicadores de carga sigue siendo la misma.

Puedes mostrar un indicador de carga limitado a la carga de archivos de la siguiente manera:

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

Ahora, mientras se carga el archivo, se mostrará el mensaje “Cargando…” y se ocultará cuando finalice la carga.

Para obtener más información sobre los estados de carga, consulte nuestra completa documentación sobre los estados de carga.

Indicadores de progreso

Cada operación de carga de archivos Livewire envía eventos JavaScript en el elemento <input> correspondiente, permitiendo que JavaScript personalizado intercepte los eventos:

EventoDescripción
livewire-upload-startDisparado cuando se inicia la carga
livewire-upload-finishDisparado si la carga ha finalizado con éxito
livewire-upload-cancelDisparado si la carga se ha cancelado prematuramente
livewire-upload-errorDisparado si la carga falla
livewire-upload-progressEvento que contiene el porcentaje de progreso de la carga a medida que ésta avanza.

A continuación se muestra un ejemplo de envolver una carga de archivos Livewire en un componente Alpine para mostrar una barra de progreso de carga:

<form wire:submit="save">
<div
x-data="{ uploading: false, progress: 0 }"
x-on:livewire-upload-start="uploading = true"
x-on:livewire-upload-finish="uploading = false"
x-on:livewire-upload-cancel="uploading = false"
x-on:livewire-upload-error="uploading = false"
x-on:livewire-upload-progress="progress = $event.detail.progress"
>
<!-- File Input -->
<input type="file" wire:model="photo">
<!-- Progress Bar -->
<div x-show="uploading">
<progress max="100" x-bind:value="progress"></progress>
</div>
</div>
<!-- ... -->
</form>

Cancelación de una subida

Si una subida está tardando mucho, un usuario puede querer cancelarla. Puede proporcionar esta funcionalidad con la función $cancelUpload() de Livewire en JavaScript.

A continuación se muestra un ejemplo de creación de un botón “Cancelar subida” en un componente Livewire utilizando wire:click para manejar el evento click:

<form wire:submit="save">
<!-- File Input -->
<input type="file" wire:model="photo">
<!-- Cancel upload button -->
<button type="button" wire:click="$cancelUpload('photo')">Cancelar Subida</button>
<!-- ... -->
</form>

Cuando se pulsa “Cancelar Subida”, se cancela la solicitud de carga de archivos y se borra la entrada de archivos. El usuario puede ahora intentar otra subida con un archivo diferente.

Alternativamente, puede llamar a cancelUpload(...) desde Alpine de esta forma:

<button type="button" x-on:click="$wire.cancelUpload('photo')">Cancelar Subida</button>

API de carga JavaScript

La integración con bibliotecas de carga de archivos de terceros a menudo requiere más control que un simple elemento <input type="file" wire:model="...">.

Para estos escenarios, Livewire expone funciones JavaScript dedicadas.

Estas funciones existen en un objeto componente JavaScript, al que se puede acceder usando el conveniente objeto $wire de Livewire desde dentro de la plantilla de su componente Livewire:

@script
<script>
let file = $wire.el.querySelector('input[type="file"]').files[0]
// Subir un archivo...
$wire.upload('photo', file, (uploadedFilename) => {
// Devolución de llamada de éxito...
}, () => {
// Devolución de llamada de error...
}, (event) => {
// Devolución de llamada de progreso...
// event.detail.progress contiene un número entre 1 y 100 a medida que avanza la carga
}, () => {
// Llamada cancelada...
})
// Subir varios archivos...
$wire.uploadMultiple('photos', [file], successCallback, errorCallback, progressCallback, cancelledCallback)
// Eliminar un único archivo de varios archivos subidos...
$wire.removeUpload('photos', uploadedFilename, successCallback)
// Cancelar una subida...
$wire.cancelUpload('photos')
</script>
@endscript

Configuración

Debido a que Livewire almacena todas las subidas de archivos temporalmente antes de que el desarrollador pueda validarlas o almacenarlas, asume algunos comportamientos de manejo por defecto para todas las subidas de archivos.

Validación global

Por defecto, Livewire validará todas las subidas temporales de archivos con las siguientes reglas: file|max:12288 (Debe ser un archivo de menos de 12MB).

Si desea personalizar estas reglas, puede hacerlo dentro del archivo config/livewire.php de su aplicación:

'temporary_file_upload' => [
// ...
'rules' => 'file|mimes:png,jpg,pdf|max:102400', // (100 MB como máximo, y sólo se aceptan PNG, JPEG y PDF)
],

Middleware global

El endpoint de carga temporal de archivos tiene asignado por defecto un middleware de estrangulamiento. Puede personalizar exactamente qué middleware utiliza este endpoint mediante la siguiente opción de configuración:

'temporary_file_upload' => [
// ...
'middleware' => 'throttle:5,1', // Sólo se permiten 5 cargas por usuario y minuto
],

Directorio de carga temporal

Los archivos temporales se cargan en el directorio livewire-tmp/ del disco especificado. Puede personalizar este directorio mediante la siguiente opción de configuración:

'temporary_file_upload' => [
// ...
'directory' => 'tmp',
],