Exportar archivos en formato Excel con Laravel Excel 3.x

Exportar datos desde Laravel a hojas de cálculos para Excel es muy sencillo gracias a un paquete llamado Laravel Excel el cual nos proporciona una gran cantidad de opciones. En este tutorial vamos a conocer algunas de las opciones que nos ofrece mediante el desarrollo de un ejemplo.

Preparación del proyecto de ejemplo

Para comenzar vamos a crear un nuevo proyecto ejecutando el siguiente comando de Composer en nuestra terminal:

composer create-project laravel/laravel laravel-excel

Estaremos trabajando con la versión 5.8 de Laravel, para seguir este artículo necesitaremos una versión mayor a la 5.5.

En nuestro ejemplo utilizaremos un tabla products la cual será representada por un modelo llamado Product. Para crear este modelo, su migración y el model factory asociado vamos a ejecutar el siguiente comando:

php artisan make:model Product -mf

Una vez creado, vamos a proceder a colocar lo siguiente en la migración de los productos:

<?php

Schema::create('products', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('description');
    $table->string('serial');
    $table->integer('stock');
    $table->timestamps();
});

Ahora, añadiremos un arreglo vacío en la propiedad fillable del modelo Product para que nos permita asignar valores masivamente.

protected $fillable = [];

Para probar nuestra aplicación necesitaremos algunos productos de prueba, anteriormente hemos creado un Model Factory llamado ProductFactory cuando creamos el modelo y pasamos la opción o flag f , vamos a proceder con la edición del archivo database/factories/ProductFactory.php:

<?php

use Faker\Generator as Faker;
 
$factory->define(App\Product::class, function (Faker $faker) {
    return [
        'name' => $faker->title,
        'description' => $faker->paragraph(1),
        'serial' => str_random(8),
        'stock' => $faker->numberBetween(0, 200)
    ];
});

Para utilizar este Model Factory vamos a dirigirnos al archivo database/seeds/DatabaseSeeder.php y colocamos lo siguiente:

<?php

use App\Product;

public function run()
{
    factory(Product::class, 50)->create();
}

Para terminar, vamos a ejecutar las migraciones y también los seeders, una vez ejecutado el comando tendremos 50 productos para hacer pruebas.

Antes de continuar con el comando debes establecer una conexión con alguna base de datos colocando las credenciales necesarias en el archivo .env ubicado en la raíz de la aplicación.

php artisan migrate --seed

Instalación del paquete

Para instalar el paquete le decimos a Composer que lo requerimos en nuestro proyecto con el siguiente comando:

composer require maatwebsite/excel

Estaremos trabajando con la versión 3.1 del paquete la cual requiere una versión mayor a la 5.5 de Laravel.

En caso de modificar la configuración que viene por defecto en el paquete, ejecutamos el comando:

php artisan vendor:publish

Al ejecutarlo seleccionamos la opción donde se indique el Service Provider del paquete, una vez hecho tendremos un nuevo archivo de configuración config/excel.php donde podemos cambiar algunos valores.

Uso básico del paquete orientado a exportación

Las exportaciones de datos son representadas en clases, estas clases son guardadas en app/Exports, el paquete sigue este concepto para ordenar un poco más el código y hacerlo reutilizable.

Para crear un clase de exportación ejecutamos el siguiente comando:

php artisan make:export ProductsExport --model=Product

Al pasar la opción --model estamos indicando cuál es el modelo relacionado a esta clase, la opción antes mencionada no es requerida. Una vez ejecutado el comando tendremos la clase ProductsExport ubicada en app/Exports/ProductsExport.php con este contenido:

<?php

namespace App\Exports;

use App\Product;
use Maatwebsite\Excel\Concerns\FromCollection;

class ProductsExport implements FromCollection
{
    /**
    * @return \Illuminate\Support\Collection
    */
    public function collection()
    {
        return Product::all();
    }
}

El método collection será el encargado de retornar todos los datos que serán exportados de esta clase a los documentos de Excel.

Para hacer este ejemplo más sencillo vamos a dirigirnos a nuestro archivo de rutas routes/web.php y agregamos lo siguiente:

<?php

use App\Exports\ProductsExport;
use Maatwebsite\Excel\Facades\Excel;
 
Route::get('/excel', function () {
    return Excel::download(new ProductsExport, 'products.xlsx');
});

Si levantamos el servidor con el comando php artisan serve y nos dirigimos a la dirección http://localhost:8000/excel se descargará un archivo llamado products.xlsx. Al abrirlo con Excel veremos algo como esto:

Ejemplo Excel

En caso querer generar otro tipo de formato para el documento solo debemos cambiar la extensión de esta forma:

Route::get('/excel', function () {
    return Excel::download(new ProductsExport, 'products.csv');
});

Los formatos disponibles que tenemos con este paquetes son los siguientes:

  • XLSX
  • CSV
  • TSV
  • ODS
  • XLS
  • HTML
  • MPDF
  • DOMPDF
  • TCPDF

En caso de querer ser un poco más explícito podemos usar otra opción la cual requiere que apliquemos un trait sobre nuestra clase ProductsExport de esta forma:

<?php

namespace App\Exports;

use App\Product;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromCollection;

class ProductsExport implements FromCollection
{
    use Exportable;
	
    /**
    * @return \Illuminate\Support\Collection
    */
    public function collection()
    {
        return Product::all();
    }
}

El trait Exportable nos ofrece métodos como download el cual nos dejará ejecutar acciones como ésta:

<?php

use App\Exports\ProductsExport;
use Maatwebsite\Excel\Facades\Excel;

Route::get('/excel', function () {
    return (new ProductsExport)->download('products.tsv', \Maatwebsite\Excel\Excel::TSV);
});

Ademas, no solo tenemos la posibilidad de descargar el archivo sino que también podemos guardarlo en alguno de nuestros discos con el método store como se muestra a continuación:

return (new ProductsExport)->store('products.xlsx', 's3');

Si aún no conoces como Laravel maneja los archivos con sus discos, te invito a revisar el articulo de Cómo subir archivos con Laravel 5.

Testing con el paquete

Como seguramente eres un integrante de la comunidad y sigues todos los cursos que tenemos en Styde sabes que las pruebas son muy importantes en el desarrollo. Con este paquete tenemos la posibilidad de trabajar con exportaciones en las pruebas gracias a los métodos que nos proporciona el facade Excel. A continuación tenemos una pequeña prueba:

/** @test */
function user_can_download_invoices_export() 
{
    Excel::fake();

    $this->actingAs(factory(User::class)->create())
         ->get('/products/download/xlsx');

    Excel::assertDownloaded('filename.xlsx', function(ProductsExport $export) {
        // Se confirma que se ha descargado la exportación correcta.
        return $export->collection()->contains('#2019-01');
    });
}

Si deseas conocer un poco más puedes visitar la sección sobre testing de la documentación oficial.

Enviar datos hacia una vista

Podemos también exportar datos a partir de una vista tan solo enviando los datos de nuestra clase hacia una vista implementando la interfaz llamada FromView y desde un método view. Un ejemplo sencillo puede ser:

namespace App\Exports;

use App\Product;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;

class ProductsExport implements FromView
{
    public function view(): View
    {
        return view('exports.products', [
            'products' => Product::all()
        ]);
    }
}

Por supuesto en nuestra vista trabajamos con la colección $products de forma muy sencilla:

<table>
    <thead>
    <tr>
        <th>Name</th>
        <th>Serial</th>
        <th>Stock</th>
    </tr>
    </thead>
    <tbody>
    @foreach($products as $product)
        <tr>
            <td>{{ $product->name }}</td>
            <td>{{ $product->serial }}</td>
            <td>{{ $product->stock }}</td>
        </tr>
    @endforeach
    </tbody>
</table>

El resultado final es simplemente una vista HTML con los datos de nuestra colección retornada del método view.

Múltiples Hojas

Para permitir que la exportación tenga varias hojas, se debe utilizar la interface WithMultipleSheets. El método sheet espera que se devuelva una matriz de objetos para la exportación de la hoja. Por ejemplo:

<?php

namespace App\Exports;

use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;

class ProductsExport implements WithMultipleSheets
{
    use Exportable;

    protected $year;
    
    public function __construct(int $year)
    {
        $this->year = $year;
    }

    /**
     * @return array
     */
    public function sheets(): array
    {
        $sheets = [];

        for ($month = 1; $month <= 12; $month++) {
            $sheets[] = new ProductsPerMonthSheet($this->year, $month);
        }

        return $sheets;
    }
}

La clase ProductsPerMonthSheet implementará las interfaces FromQuery y WithTitle.

<?php

namespace App\Exports;

use App\Product;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithTitle;

class ProductsPerMonthSheet implements FromQuery, WithTitle
{
    private $month;
    private $year;

    public function __construct(int $year, int $month)
    {
        $this->month = $month;
        $this->year  = $year;
    }

    /**
     * @return Builder
     */
    public function query()
    {
        return Product
            ::query()
            ->whereYear('created_at', $this->year)
            ->whereMonth('created_at', $this->month);
    }

    /**
     * @return string
     */
    public function title(): string
    {
        return 'Month ' . $this->month;
    }
}

Con este ejemplo ahora se descargará un xlsx de todas los productos registrados del año en curso, con 12 hojas de trabajo que representan cada mes del año.

public function download() 
{
    return (new ProductsExport(2018))->download('products.xlsx');
}

Una recomendación

Como recomendación personal no soy partidario que la clase ProductsExport se encargue de obtener los datos que serán exportados, porque quizás nuestra aplicación necesite filtrar los productos por su categoría y exportarlos, y en ese caso, tendríamos que hacer una clase de exportación por cada categoría.

Para evitar esto lo recomendable es pasarle a la clase la colección la cual va a exportar. Comenzaremos modificando la clase ProductsExport la cual nos quedaría de esta forma:

<?php

namespace App\Exports;

use App\Product;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromCollection;

class ProductsExport implements FromCollection
{
    use Exportable;

    protected $products;

    public function __construct($products = null)
    {
        $this->products = $products;
    }

    /**
    * @return \Illuminate\Support\Collection
    */
    public function collection()
    {
        return $this->products ?: Product::all();
    }
}

Ahora nuestra clase es flexible, podemos pasar la colección a exportar y en caso de no pasar nada se exportarán todos los productos. Un ejemplo sencillo del uso de nuestra clase con esta modificación es el siguiente:

// Solo queremos exportar los productos que tienen un stock superior a 10.

use App\Product;

$products = Product::where('stock', '>', 10)->get();

return (new ProductsExport($products))->download('products.tsv', \Maatwebsite\Excel\Excel::TSV);

Esto es todo, en caso de que quieras estudiar más posibilidades que ofrece este paquete puedes visitar su página oficial. Si tienes una duda referente a lo que hemos visto en este tutorial puedes dejarla en los comentarios así como tus sugerencias para nuevos artículos. No olvides compartir este material en tus redes sociales y con tus compañeros.

Material Relacionado

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.