Implementando consulta em PDF com Laravel
Recentemente eu recebi a tarefa de implementar uma funcionalidade que permitisse a geração de um PDF a partir de um arquivo de consulta. A ideia era que o usuário pudesse fazer a consulta em mais de 8000 arquivos que pertenciam a um jornal com mais de 100 anos.
Esses arquivos já foram digitalizados e estavam disponíveis no servidor, mas o cliente precisava de uma forma de fazer a consulta de forma mais rápida e eficiente.
Como a aplicação já disponibilizava de um painel de gerenciamento em laravel, com todos esses PDFs já previamente cadastrados. A solução mais viável foi cadastrar as páginas do PDF no banco de dados, assim poderia ser feito a consulta de forma mais rápida com uma simples consulta SQL.
Para isso, utilizei a biblioteca PdfParser para fazer a leitura dos arquivos PDF e salvar as páginas no banco de dados.
Primeiramente eu fiz duas migrations, uma para criar a tabela de PDFs e outra para criar a tabela de páginas.
php artisan make:migration create_pdfs_table
php artisan make:migration create_pages_table
Então, no arquivo de migration de PDFs, eu adicionei os campos necessários para o cadastro do PDF.
Schema::create('pdfs', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('path');
$table->int('number_of_pages');
$table->timestamps();
});
Schema::create('pages', function (Blueprint $table) {
$table->id();
$table->foreignId('pdf_id')->constrained();
$table->integer('number');
$table->text('content');
$table->timestamps();
});
Então, no model de PDF, eu adicionei o relacionamento com as páginas.
class Pdf extends Model
{
protected $guarded = [];
public function pages()
{
return $this->hasMany(Page::class);
}
}
class Page extends Model
{
protected $guarded = [];
public function pdf()
{
return $this->belongsTo(Pdf::class);
}
}
Para instalar a biblioteca, basta executar o comando abaixo:
composer require smalot/pdfparser
Aqui é um simples formulário para enviar o PDF para o servidor.
<form
action="{{ route('newspapers.store') }}"
method="post"
enctype="multipart/form-data"
>
@csrf
<input type="file" name="pdf" />
<button type="submit">Salvar</button>
</form>
Então, no controller, vou adicionar a função para salvar o pdf. Depois estarei pegando o conteúdo do PDF e a quantidade de páginas, essa quantidade vou salvar no campo number_of_pages. Após isso vou fazer um foreach nas páginas do PDF e salvar no banco de dados.
public function store()
{
$pdfPath = request()->file("pdf")->store("pdf", "public");
$parser = new \Smalot\PdfParser\Parser();
$pdf = $parser->parseContent(file_get_contents($pdfPath));
$numberOfPages = count($pdf->getPages());
$pdfCreated = Pdf::create([
'path' => $pdfPath,
'number_of_pages' => $numberOfPages,
]);
foreach ($pdf->getPages() as $number => $value) {
Page::updateOrCreate(
[
"newspaper_id" => $pdfCreated->id,
"page_number" => $number + 1,
],
[
"newspaper_id" => $pdfCreated->id,
"page_number" => $number + 1,
"content" => iconv(
"ISO-8859-1",
"UTF-8",
utf8_decode($value->getText())
),
]
);
}
}
Note que estou usando a função icon
para converter o texto para UTF-8, isso é necessário pois os PDFs estavam em ISO-8859-1. Verifique qual a codificação do seu PDF e faça a conversão necessária.
E também estou usando o utf8_decode
para converter os caracteres especiais.
Essa parte dos caracteres especiais é bastante complicada, é bom dar uma atenção a isso.
Agora, para fazer a consulta, basta fazer uma simples consulta SQL.
public function search()
{
$pdf = Pdf::whereHas('pages', function ($q) {
$q->where('content', 'like', '%' . request('search') . '%');
})->get();
return response()->json($pages);
}
Essa é uma implementação simples, na aplicação real eu não coloquei a leitura das páginas junto com o upload do arquivo, eu fiz uma fila para processar os PDFs, pois a leitura de um PDF pode estourar o limite de memória facilmente.
Espero que esse artigo tenha te ajudado! Se tiver alguma dúvida pode falar comigo nas minhas redes sociais.
Até a próxima!