3

Exportando dados em planilhas no Laravel e Livewire sem sujeira e sem raizes brancas #DIOProGrátis

#Laravel #PHP
Salustiano Muniz
Salustiano Muniz

Introdução


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


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


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();
    }

}

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;
}

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
{

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  
) {  
}

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'
];

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'
	]
];

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  
	);  
}

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
{

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,  
  ];  
}

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,  
    ];  
}

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
{

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');  
}

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  
{

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');
    }
}

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');  
}

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
	);  
}

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>

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 alguns deles não usam 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.

1
16

Comentários (2)

0
carlos oliveira

carlos oliveira

31/10/2021 11:45

obrigado, estava precisando disso!

1
Adauri Campos

Adauri Campos

29/09/2021 16:58

Parabéns ótimo conteúdo!!!

Programador PHP desde 2010, roquista, pai de 2 e vez em quando faço uns jogos com godot e love2d

Brasil