Importar archivos en formato Excel con Laravel Excel 3.x

A partir de la versión 3.1 de Laravel Excel ya podemos cargar datos desde fuentes externas como un archivo Excel o un CSV en un proyecto de Laravel usando las nuevas opciones que trae desde su actualización a la versión 3. En este artículo veremos todo lo disponible a partir de esta versión para importar archivos excel.

¿Qué trae de nuevo?

El paquete maatwebsite/excel fue reescrito para brindar una sintaxis más expresiva, sea más fácil de usar y flexible para extenderlo.

Importante saber que al ser reescrito ya no es compatible la versión 2.1 porque:

  • Esta nueva versión se basa en PhpSpreadsheet en vez de PHPExcel.
  • Requiere PHP 7.0 o superior y por tanto, requiere Laravel 5.5 o superior.

Al escribirse desde cero ya los métodos disponibles en la versión 2.1 descrita en el post Importar datos desde Excel o CSV a Laravel no son compatibles, es decir que el método Excel::load() fue eliminado y reemplezado por Excel::import($ImportClass) y además ya no se utilizarán ExcelFile injections. En este tutorial veremos con detalles de qué manera se trabajará de ahora en adelante.

Adicionalmente, esta nueva versión usa interfaces para configurar cómo se hará la importación de los datos, lo cual hace que la adaptación del paquete resulte en una forma muy elegante.

Instalación

Para empezar a trabajar con el paquete debemos ejecutar el siguiente comando en la terminal (no olvides que debe ser un proyecto de Laravel 5.5 en adelante):

composer require maatwebsite/excel

El Service Provider es registrado automáticamente pues el paquete tiene configurado el auto-descubrimiento.

Para publicar el archivo de configuración ejecuta:

php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"

Esto creará el archivo excel.php en el directorio config.

Proyecto de ejemplo

Para probar el paquete supongamos que tenemos una aplicación donde se carga información de vehículos. Para ello creamos un modelo con su respectiva migración y controlador ejecutando:

php artisan make:model -mc Vehicle

En el método up de la migración creada agregamos:

Schema::create('vehicles', function (Blueprint $table) {
    $table->increments('id');
    $table->string('registration_number');
    $table->string('brand');
    $table->string('model');
    $table->string('type');
    $table->string('fuel_type');
    $table->integer('doors');
    $table->integer('year');
    $table->boolean('is_active');
    $table->timestamps();
});

Luego de esto ejecutamos:

php artisan migrate

No olvides crear una base de datos y configurar las credenciales en el archivo .env en tu proyecto antes de ejecutar este comando.

Seguidamente vamos a editar nuestro modelo app\Vehicle.php para permitir que los datos puedan ser almacenados de forma masiva, solo basta agregar la propiedad guarded, de esta forma:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Vehicle extends Model
{
    protected $guarded = [];
}

Un ejemplo del tipo de archivo que cargará nuestra aplicación lo puedes descargar acá: vehicles

Al abrirlo verás algo como esto:

sample file xlsx

Uso de Laravel Excel

Laravel Excel puede obtener los datos de un archivo para convertir cada fila en un modelo de Eloquent o generar una colección de filas del archivo. Escoger una u otra opción dependerá de la propia información que contiene el archivo y lo que quieres hacer con ella.

Lo primero que debemos hacer para importar la información de los vehículos de nuestro proyecto de ejemplo es crear una clase Import para dicho modelo, en la cual agregaremos la configuración para obtener los datos del archivo en Excel de la siguiente manera:

Si queremos crear un registro del modelo Vehicle por cada fila del archivo ejecutamos el comando make:import pasándole la opción --model con el nombre del modelo:

php artisan make:import VehiclesImport --model=Vehicle

Este comando creará la clase VehiclesImport dentro del directorio app/Imports

<?php

namespace App\Imports;

use App\Vehicle;
use Maatwebsite\Excel\Concerns\ToModel;

class VehiclesImport implements ToModel
{
    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        return new Vehicle([
            //
        ]);
    }
}

Si por el contrario, queremos que al importar se genere una colección de filas, ejecutamos el comando sin ninguna opción, solo el nombre de la clase Import:

php artisan make:import VehiclesCollectionImport

Creará la siguiente clase:

<?php

namespace App\Imports;

use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;

class VehiclesCollectionImport implements ToCollection
{
    /**
    * @param Collection $collection
    */
    public function collection(Collection $collection)
    {
        //
    }
}

La diferencia entre una clase y la otra radica en la interfaz que implementa. Pues las clases Import implementarán las interfaces según la información que queremos importar.

Por un lado el método model de la interfaz ToModel recibirá un array con los datos de una fila del archivo y nosotros agregaremos al método cada atributo del modelo asociándolo a una columna del archivo y el paquete se encargará de crear el registro. Por otro lado, el método collection de la interfazToCollection recibirá una colección de filas del archivo y por tanto podremos manipularla usando los métodos para Colecciones de Laravel que nos permitan procesar la información del archivo.

Para importar un archivo puedes hacerlo de varias maneras: usando inyección de dependencias, con el Facade, por medio de la interfaz Importer o convirtiendo a nuestra clase en Importable. Usemos el controlador creado al principio del tutorial para mostrar cómo usar cada uno:

Importar usando inyección de dependencias

<?php

namespace App\Http\Controllers;

use Maatwebsite\Excel\Excel;
use App\Imports\VehiclesImport;

class VehicleController extends Controller
{
    private $excel;

    public function __construct(Excel $excel)
    {
        $this->excel = $excel;
    }
    
    public function import()
    {
        return $this->excel->import(new VehiclesImport, 'vehicles.xlsx');
    }
}

Importar con el Facade Excel

use App\Imports\VehiclesImport;
use Maatwebsite\Excel\Facades\Excel;

class VehicleController extends Controller
{    
    public function import()
    {
        return Excel::import(new VehiclesImport, 'vehicles.xlsx');
    }
}

Importar con la interfaz Importer

use App\Imports\VehiclesImport;
use Maatwebsite\Excel\Importer;

class VehicleController extends Controller
{
    private $importer;

    public function __construct(Importer $importer)
    {
        $this->importer = $importer;
    }
    
    public function import()
    {
        return $this->importer->import(new VehiclesImport, 'vehicles.xlsx');
    }
}

Convirtiendo la clase en Importable

Laravel Excel nos provee de un trait llamado Maatwebsite\Excel\Concerns\Importable el cual agregamos a la clase Import creada en app/Imports por ejemplo:

<?php

namespace App\Imports;

use App\Vehicle;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\ToModel;

class VehiclesImport implements ToModel
{
    use Importable;
    ...

De esta forma el controlador queda así:

use App\Imports\VehiclesImport;

class VehicleController extends Controller
{    
    public function import()
    {
        return (new VehiclesImport)->import('vehicles.xlsx');
    }
}

La elección de una u otra forma de importar depende de las convenciones del proyecto o gusto del programador.

Método import

Este método es el encargado de realizar la importación y acepta tres parámetros:

  • El primero parámetro es el archivo a importar, el cual puede ser un string con la ubicación del archivo o una instancia de Symfony\Component\HttpFoundation\File\UploadedFile,
  • En caso de que el primer parámetro sea un string, el segundo parámetro es el string que indica el disco (disk) donde se encontrará el archivo y por defecto usa el establecido como default en config/filesystems.php En caso de ser una instancia de UploadedFileel segundo parámetro debe ser null.
  • El tercero parámetro es el formato de importación, que por defecto toma la extensión del archivo que queremos importar, pero si lo quieres definir explicítamente debes pasar una de las opciones aceptadas.

Trabajando con el archivo

Para probar nuestro controlador tomemos el archivo vehicles.xlsx y lo ubicamos en el directorio storage/app, el directorio por defecto de config/filesystems.php de un proyecto de Laravel.

El paquete buscará el archivo en el directorio por defecto de tu proyecto de Laravel.

Alternativamente, puedes trabajar con un archivo que se carga desde un formulario de esta manera:

(new VehiclesImport)->import(request()->file('your_file'));

Ruta

Creamos una ruta para el método import del controlador en el archivo routes/web.php

Route::get('/import', 'VehicleController@import');

Controlador

Preparamos el controlador para cargar el archivo. En mi caso convertiré la clase en Importable, como expliqué más arriba, quedando el método de esta manera:

public function import()
{
    (new VehiclesImport)->import('vehicles.xlsx');
    
    return redirect('/')->with('success', 'File imported successfully!');
}

y la clase VehiclesImport para crear un registro de Vehicle así:

<?php

namespace App\Imports;

use App\Vehicle;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\Importable;

class VehiclesImport implements ToModel
{
    use Importable;

    public function model(array $row)
    {
    	return new Vehicle([
            'registration_number' => $row[0],
            'brand' => $row[1],
            'model' => $row[2],
            'type' => $row[3],
            'fuel_type' => $row[4],
            'year' => $row[5],
            'doors' => $row[6],
            'is_active' => $row[7],
    	]);
    }
}

Finalmente, podemos probar llamando nuestra ruta y posteriormente verificando en la base de datos que cada una de las filas han sido cargadas como registros.

Fila de encabezado

Sin embargo, al revisar la base de datos como el primer vehículo se ha registrado el encabezado del archivo. Para resolverlo, debemos implementar la interfaz Maatwebsite\Excel\Concerns\WithHeadingRow y así podemos usar como referencia el nombre del encabezado en vez de un valor numérico:

<?php

namespace App\Imports;

use App\Vehicle;
use Maatwebsite\Excel\Concerns\{Importable, ToModel, WithHeadingRow};

class VehiclesImport implements ToModel, WithHeadingRow
{
    use Importable;

    public function model(array $row)
    {
        return new Vehicle([
            'registration_number' => $row['registration_number'],
            'brand' => $row['brand'],
            'model' => $row['model'],
            'type' => $row['type'],
            'fuel_type' => $row['fuel_type'],
            'year' => (integer) $row['year'],
            'doors' => (integer) $row['doors'],
            'is_active' => ($row['active'] == 'YES') ? 1 : 0,
        ]);
    }
}

Toma en cuenta que el paquete formatea los encabezados con el helper de Laravel str_slug pero es flexible, pues te permite configurar que no use el formato predeterminado o le puedes sustituir por el formato que necesites. Además, puedes establecer una fila diferente de la 1 como la de encabezado. Mira la documentación oficial para mayor información.

Validando los datos de una fila

En esta nueva versión de Laravel Excel podrás validar los datos que vienen del archivo antes de ingresarlos a la base de datos, implementando la interfaz Maatwebsite\Excel\Concerns\WithValidation y agregando un nuevo método llamado rules que retornará las reglas de validación de Laravel para cada columna del archivo que desees validar:

<?php

namespace App\Imports;

use App\Vehicle;
use Maatwebsite\Excel\Concerns\{Importable, ToModel, WithHeadingRow, WithValidation};

class VehiclesImport implements ToModel, WithHeadingRow, WithValidation
{
    use Importable;

    public function model(array $row)
    {
        return new Vehicle([
            'registration_number' => $row['registration_number'],
            'brand' => $row['brand'],
            'model' => $row['model'],
            'type' => $row['type'],
            'fuel_type' => $row['fuel_type'],
            'year' => (integer) $row['year'],
            'doors' => (integer) $row['doors'],
            'is_active' => ($row['active'] == 'YES') ? 1 : 0,
        ]);
    }

    public function rules()
    {
        return [
            'registration_number' => 'regex:/[A-Z]{3}-[0-9]{3}/',
            'doors' => 'in:2,4,6',
            'years' => 'between:1998,2017'
        ];
    }
}

El funcionamiento interno del paquete hace que la importación se realice mediante transacciones de base de datos por tanto si ocurre un error de validación se realizará un rollback de la importación y en caso de trabajar con lotes, solo se hará rollback del lote en proceso.

Sin embargo, también podremos configurar para que salte las fallas de validación y los errores para que importe el archivo y al final poder capturar todas las ocurrencias y podamos controlar cómo mostrárselas a los usuarios. Adicionalmente, la validación es muy flexible pues nos permite personalizar tanto los mensajes como los nombres de los atributos.

Para mayor información puedes revisar en la documentación oficial: Row validation

Otras funcionalidades del paquete

Laravel Excel trae muchas más funcionalidades como:

  • Trabajar con múltiples hojas en un archivo permitiéndonos definir cómo procesar cada hoja separadamente, omitir alguna en específico e incluso seleccionar las hojas con condicionales. Ver Multiple Sheets
  • Trabajar con archivos grandes (muchas filas) usando Chunk reading para procesar el archivo dividiéndolo en partes más pequeñas para mejorar el impacto de uso de la memoria. Además, puedes usar Batch inserts para limitar la cantidad de consultas de inserción, es decir cuántos registros se pueden guardar en la base datos a la vez.
  • Puedes enviar a colas de trabajo el procesamiento del archivo usando Queued reading además de poder agregar trabajos adicionales que se ejecuten luego que la cola haya sido procesada correctamente.
  • En caso de tener un archivo excel muy personalizado y quieras solo acceder a unas celdas específicas, puedes trabajar con Mapped Cells.
  • Puedes personalizar la configuración del CSV a importar. Ver Custom CSV Settings
  • Puedes implementar una barra de progreso para cuando trabajes en una importación en la consola. Ver Progress Bar
  • Puedes usar eventos del paquete para agregar comportamientos adicionales durante el proceso de importación, así como usar la capacidad Macroable para agregar nuevos métodos. Ver Extending
  • Escribir pruebas automatizadas con los métodos proporcionados por el paquete. Ver Testing

Laravel Excel es una gran herramienta y con su actualización nos da muchas opciones que son de gran ayuda al momento de trabajar con la importación de archivos. ¿Qué te ha parecido? ¿Te interesa profundizar en el uso de esta herramienta? Si es así por favor déjanos un comentario. Además, si te ha gustado este artículo por favor no dejes de compartirlo en las redes sociales y únete a nuestro listado de correos para recibir noticias sobre futuros cursos.

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.