DEV Community

Cover image for Exportando dados em planilhas no Laravel e Livewire sem sujeira e sem raizes brancas
Salustiano Muniz
Salustiano Muniz

Posted on • Edited on

3

Exportando dados em planilhas no Laravel e Livewire sem sujeira e sem raizes brancas

Esses dias eu tive que criar "só um botãozinho" que exportasse os dados filtrados pelo usuário na página e exportasse para uma planilha para ser aberta no Excel. E depois de uma boa pesquisa, encontrei o pacote Laravel Excel, que facilita muito o trabalho. Neste artigo quero compartilhar minha experiência na implementação, pois apesar de o pacote ter documentação com exemplos, tive um pouco de trabalho pra juntar tudo e fazer funcionar da forma que eu precisava.

Antes de começar

O projeto que estou trabalhando usa Livewire rodando em Laravel 8 e PHP 8.0 e por isso os exemplos de código contidos no artigo usarão estas tecnologias, mas o pacote não depende do Livewire, podendo ser usado com Laravel a partir da versão 5.8, e com versões do PHP a partir da versão 7.2.

Configuração

Instalando o pacote

O primeiro passo é instalar o pacote através do composer.

composer require maatwebsite/excel
Enter fullscreen mode Exit fullscreen mode

Criando a classe de exportação

Com o pacote instalado, o próximo passo é criar a classe exportadora, que vai cuidar de configurar o arquivo a ser exportado. O comando que cria a classe de exportação aceita o parâmetro --model, usado para informar o Model de onde sairão as informações.

php artisan make:export TransactionExport --model=AccountTransaction
Enter fullscreen mode Exit fullscreen mode

A classe gerada pelo comando do artisan terá a seguinte estrutura:

 <?php

namespace App\Exports;  

use App\Models\AccountTransaction;  
use Maatwebsite\Excel\Concerns\FromCollection;  

class TransactionExport implements FromCollection  
{  
    public function collection()
    {
        return AccountTransaction::all();
    }

}
Enter fullscreen mode Exit fullscreen mode

Utilização

Passando dados para a exportação

Por padrão, a classe de exportação seleciona todos os registros do Model para exportar, mas é possível passar outra fonte de dados, que pode ser uma Collection ou um array através do método mágico __constructor da classe. No exemplo abaixo, é feito com uma collection

public function __construct(
    public Collection $transactions
) {
}

public function collection(): Collection
{
    return $this->transactions;
}
Enter fullscreen mode Exit fullscreen mode

Adicionando o cabeçalho

Apesar de neste ponto já ser possível baixar o arquivo, ele ainda não vai ter cabeçalho, mas o pacote permite adicionar cabeçalho ao arquivo utilizando a interface do pacote Laravel Excel WithHeadings à chamada da classe:

class TransactionExport implements FromCollection, WithHeadings
{
Enter fullscreen mode Exit fullscreen mode

Depois disso, precisamos passar os dados do cabeçalho no __constructor através de um array

public function __construct(
    public Collection $transactions,
    public array $headings  
) {  
}
Enter fullscreen mode Exit fullscreen mode

Eu optei por usar os nomes das colunas como estão no banco de dados no cabeçalho e porque poderia reaproveitar esta informação para selecionar as colunas no método de exportação

$columns  =  [
    'description',
    'financial_category_id',
    'transaction_status_id',
    'accrual_month',
    'due_date',
    'value',
    'document_number',
    'notes',
    'financial_account_id',
    'supplier_id',
    'cost_center_id',
    'payment_date',
    'final_value',
    'payment_method_id'
];
Enter fullscreen mode Exit fullscreen mode

Mas como estes nomes de colunas não seriam bons, criei entradas respectivas no arquivo de tradução resources\lang\pt_BR\dashboard.php:

return [
    ...
    'account_transaction' => [  
        'description' => 'Descrição',  
        'financial_category_id' => 'Categoria Financeira',  
        'transaction_status_id' => 'Status da Transação',  
        'accrual_month' => 'Mês de Vigência',  
        'due_date' => 'Data de Vencimento',  
        'value' => 'Valor',  
        'document_number' => 'Número do Documento',  
        'notes' => 'Observações',  
        'financial_account_id' => 'Conta Financeira',  
        'supplier_id' => 'Fornecedor',  
        'cost_center_id' => 'Centro de Custo',  
        'payment_date' => 'Data do Pagamento',  
        'final_value' => 'Valor Recebido',  
        'payment_method_id' => 'Forma de Pagamento'
    ]
];
Enter fullscreen mode Exit fullscreen mode

E usando o método headings da interface do pacote Laravel Excel WithHeadings, usei a função array_map para fazer com que o array de nomes do cabeçalho fosse traduzido:

public function headings(): array  
{ 
    return array_map(  
        fn($columnName) => __('dashboard.account_transaction.'.$columnName),  
        $this->headings  
    );  
}
Enter fullscreen mode Exit fullscreen mode

Personalizando a planilha

Nosso trabalho está quase completo, mas tanto os dados quanto a aparência da planilha podem ser melhor exibidos, e é isso que faremos a seguir.

Formatando dados

Os vêm do jeito que estão no banco de dados, ou seja, com os IDs das propriedades que têm relacionamento e as datas "cruas", mas podemos formatar estes dados com as interfaces do pacote Laravel Excel ColumnFormatting e WithMapping

class TransactionExport implements
    FromCollection,  
    WithColumnFormatting,  
    WithMapping,  
    WithHeadings
{
Enter fullscreen mode Exit fullscreen mode

E implementamos seus métodos. Começando pelo map, para ordenar e substituir os IDs pelos respectivos nomes das entidades relacionadas com as colunas:

use PhpOffice\PhpSpreadsheet\Shared\Date;

...

public function map($row): array  
{  
  return [  
  $row->description,  
  $row->financialCategory->name,  
  $row->transactionStatus->name,  
  Date::dateTimeToExcel($row->accrual_month),  
  Date::dateTimeToExcel($row->due_date),  
  $row->value,  
  $row->document_number,  
  $row->notes,  
  $row->financialAccount->name,  
  $row->supplier->name,  
  $row->costCenter?->name,  
  $row->payment_date ? Date::dateTimeToExcel($row->payment_date) : null,  
  $row->final_value,  
  $row->paymentMethod?->name,  
  ];  
}
Enter fullscreen mode Exit fullscreen mode

A classe PhpOffice\PhpSpreadsheet\Shared\Date é um "bônus" que o pacote Laravel Excel nos dá para facilitar a manipulação de dados. Como o pacote tem dependência com o PhpSpreadsheet, podemos usar métodos deste pacote.

O segundo passo para ajustar as linhas da planilha é resolvida pelo método columnFormats, que ficará assim:

use PhpOffice\PhpSpreadsheet\Style\NumberFormat;

...

public function columnFormats(): array  
{
    return [  
        'D' => 'mm/yyyy',  
        'E' => NumberFormat::FORMAT_DATE_DDMMYYYY,  
        'F' => NumberFormat::FORMAT_CURRENCY_USD_SIMPLE,  
        'L' => NumberFormat::FORMAT_DATE_DDMMYYYY,  
        'M' => NumberFormat::FORMAT_CURRENCY_USD_SIMPLE,  
    ];  
}
Enter fullscreen mode Exit fullscreen mode

Como é visto no código de exemplo, o array deve ser composto pela letra que corresponde à coluna que precisamos formatar e uma máscara com a regra de formatação. Aqui mais uma vez usamos um método do pacote PhpSpreadsheet. Neste caso, o PhpOffice\PhpSpreadsheet\Style\NumberFormat, que tem inúmeras máscaras para formatação, mas você pode usar uma personalizada, assim como eu fiz na primeira entrada do array retornado pelo método.

Estilizando o cabeçalho

Agora só falta destacar o cabeçalho para que nossa planilha fique pronta para ser exportada e lida. E o Laravel Excel tem uma Interface para nos ajudar: WithStyles:

class TransactionExport implements  
    FromCollection,  
    WithColumnFormatting,  
    WithMapping,  
    WithHeadings,  
    WithStyles
{
Enter fullscreen mode Exit fullscreen mode

Com a interface adicionada, hora de implementar o método styles:

use PhpOffice\PhpSpreadsheet\Style\Fill;

...

public function styles(Worksheet $sheet)  
{  
  $sheet->getStyle('A1:N1')
        ->getFill()  
        ->setFillType(Fill::FILL_SOLID)  
        ->getStartColor()  
        ->setRGB('51d2b7');  

  $sheet->getStyle('A1:N1')  
        ->getFont()  
        ->setBold(true)  
        ->getColor()  
        ->setRGB('ffffff');  
}
Enter fullscreen mode Exit fullscreen mode

Aqui, a primeira declaração é responsável por estilizar a cor de fundo do cabeçalho, e a segunda é responsável por formatar a fonte, deixando o cabeçalho com o fundo verde e a fonte em negrito e branca. Além destes atributos, também é possível mudar outros como borda, alinhamento, etc. Os métodos e parâmetros obedecem os padrões da documentação de formatação do PhpSpreadsheet

Pra arrematar, indico usar a interface ShouldAutoSize, que ajusta o tamanho das colunas automagicamente.

class TransactionExport implements  
    FromCollection,  
    WithColumnFormatting,  
    WithMapping,  
    WithHeadings,  
    WithStyles,  
    ShouldAutoSize  
{
Enter fullscreen mode Exit fullscreen mode

Classe de exportação

No final, a classe de exportação ficará assim:

<?php

namespace App\Exports;

use App\Models\AccountTransaction;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class TransactionExport implements
    FromCollection,
    WithColumnFormatting,
    WithMapping,
    WithHeadings,
    WithStyles,
    ShouldAutoSize
{
    public function __construct(
        public Collection $transactions, public array $headings
    ) {
    }

    public function collection()
    {
        return $this->transactions;
    }

    public function columnFormats(): array
    {
        return [
            'D' => 'mm/yyyy',
            'E' => NumberFormat::FORMAT_DATE_DDMMYYYY,
            'F' => NumberFormat::FORMAT_CURRENCY_USD_SIMPLE,
            'L' => NumberFormat::FORMAT_DATE_DDMMYYYY,
            'M' => NumberFormat::FORMAT_CURRENCY_USD_SIMPLE,
        ];
    }

    public function map($row): array
    {
        return [
            $row->description,
            $row->financialCategory->name,
            $row->transactionStatus->name,
            Date::dateTimeToExcel($row->accrual_month),
            Date::dateTimeToExcel($row->due_date),
            $row->value,
            $row->document_number,
            $row->notes,
            $row->financialAccount->name,
            $row->supplier->name,
            $row->costCenter?->name,
            $row->payment_date ? Date::dateTimeToExcel($row->payment_date) : null,
            $row->final_value,
            $row->paymentMethod?->name,
        ];
    }

    public function headings() : array
    {
        return array_map(
            fn($columnName) => __('dashboard.account_transaction.' . $columnName),
            $this->headings
        );
    }

    public function styles(Worksheet $sheet)
    {
        $sheet->getStyle('A1:N1')
            ->getFill()
            ->setFillType(Fill::FILL_SOLID)
            ->getStartColor()
            ->setRGB('51d2b7');

        $sheet->getStyle('A1:N1')
            ->getFont()
            ->setBold(true)
            ->getColor()
            ->setRGB('ffffff');
    }
}
Enter fullscreen mode Exit fullscreen mode

Criando o método de exportação

A partir daqui, nossa classe está pronta para receber, formatar e exportar nossos dados em uma planilha, que faremos através do Livewire com o método runQueryBuilder, o mesmo que busca os dados para exibir na página, com a diferença de que na chamada da página, ela é terminada com o método paginate para enviar dados paginados.


public function runQueryBuilder()  
{
    return $this->client  
        ->accountTransactions()  
        ->when(
            $this->transactionType,  
            fn($q) => $q->where('transaction_type', $this->transactionType)  
        )
        ->when(  
            $this->financialAccount,  
            fn($q) => $q->where('financial_account_id', $this->financialAccount)  
        )
        ->when(  
            $this->timePeriod,  
            fn($q) => $q->whereDate('due_date', '>=', $this->initialDueDate)
                        ->whereDate('due_date', '<=', $this->finalDueDate);
        )
        ->orderBy('due_date');  
}
Enter fullscreen mode Exit fullscreen mode

E finalmente o método de exportar, que recebe os dados e chama o método estático download da facade Maatwebsite\Excel\Facades\Excel para criar e baixar a planilha.

public function export(): BinaryFileDownload
{
    $columns = [  
        'description',  
        'financial_category_id',  
        'transaction_status_id',  
        'accrual_month',
        'due_date',  
        'value',  
        'document_number',  
        'notes',  
        'financial_account_id',  
        'supplier_id',  
        'cost_center_id',  
        'payment_date',  
        'final_value',  
        'payment_method_id'  
    ];  

    $data = $this->runQueryBuilder()->get($columns);  
    $filename = 'transactions_'
        . strtolower(TransactionTypeEnum::search($this->transactionType))
        .'_'. date('Ymd_his') .'.xls';

    return Excel::download(
        new TransactionExport($data, $columns),
        $filename
    );  
}
Enter fullscreen mode Exit fullscreen mode

Criando o botão de download

Com todo o código do backend criado, agora resta criar o botão que disparará a ação:

<button
    wire:click="export"
    wire:loading.attr="disabled"
    class="btn btn-primary btn-block text-uppercase">  
    <b>{{__('dashboard.Export')}}</b>
</button>
Enter fullscreen mode Exit fullscreen mode

Resultado

E finalmente temos nosso "só um botãozinho". Ao clicar no botão de exportar, o download será iniciado em instantes e você terá um arquivo em formato XLS, que ao ser aberto em um programa de edição de planilhas - no meu caso, usei o Google Spreadsheets - será exibido assim:
Imagem da planilha concluída com estilos e formatações aplicadas

Você também tem a opção de exportar o arquivo em outros formatos, mas lembre-se que alguuns deles não usa estilização, portanto parte deste trabalho será desnecessário.

E atenção, se liga aí que é hora da revisão

Na tele-aula de hoje, vimos que o pacote Laravel Excel pode ser usado para facilitar o processo de exportação de dados em forma de planilhas. Vimos também algumas formas de manipular estes dados e estilizar a planilha para melhorar sua leitura e apresentação. Por fim, implementamos tudo em um componente Livewire que nos permitiu fazer uso dos métodos desenvolvidos com um clique, baixamos o arquivo e visualizamos este em um editor de planilhas.

Este artigo foi uma forma de compartilhar minha experiência com o Laravel Excel, que me ajudou a facilitar um processo que tenho certeza que não sou só eu que vejo como complexo. A documentação do pacote tem várias outras opções de configuração de exportação de arquivos que podem ser exploradas, mas me concentrei em demonstrar as que usei. Recomendo a quem se interessar que dê uma lida e teste os exemplos.

Por hoje é isso. Nos vemos na próxima. Até lá e bons estudos.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs