Neste artigo, vou mostrar como você pode consumir API e utilizá-la para autopreenchimento de campos no Filament, tornando assim seu formulário mais automatizado e dinâmico de forma simples e fácil. Vamos aprender, como exemplo, a autopreencher campos de endereço a partir do CEP digitado. Partiu?
Tópicos
Introdução
E ai pessoal, este artigo não tem o intuito de ser clean code, mas sim de mostrar no geral como o autopreenchimento é feito para que a didática fique melhor, porém você pode organizar seu código da maneira que desejar. Neste exemplo, vou estar fazendo uso da API do ViaCep e vamos utilizá-la para autopreenchimento de campos de endereço, mas isso não se limita só a ela, sinta-se livre para usar a API que preferir. Vamos começar!
Passo a passo
Neste exemplo, estamos utilizando as seguintes versões de nossas ferramentas:
- Laravel v11.x
- PHP v8.2
- FilamentPHP v3.2
❗ Importante: O uso de outras versões pode mudar a forma como o procedimento abaixo é feito.
Passo 1: Criando projeto Laravel e adicionando o FilamentPHP
Para criar nosso projeto Laravel, podemos utilizar o seguinte comando:
composer create-project laravel/laravel example-app
Onde example-app
é o nome do nosso projeto. Feito isso, dentro da pasta do projeto vamos usar os seguintes comandos, sendo o primeiro para adicionar o FilamentPHP e o outro para instalar os painéis do Filament:
composer require filament/filament:"^3.2" -W php artisan filament:install --panels
Passo 2: Criando nossos models, migrations e relacionamentos
Agora vamos rodar o seguinte comando para criar o nosso Model, onde Address
é o nome do nosso model e a flag -m
fará com que a Migration seja criada junto com o Model:
php artisan make:model Address -m
Dentro da nossa Migration vamos adicionar os campos que vamos utilizar na nossa tabela. Levando em conta que vamos utilizar a API do ViaCep, os campos deverão ficar mais ou menos dessa forma:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('addresses', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('postal_code')->nullable(); $table->string('address')->nullable(); $table->string('number')->nullable(); $table->string('complement')->nullable(); $table->string('neighborhood')->nullable(); $table->string('city')->nullable(); $table->string('uf')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('addresses'); } };
Adicionado os campos na Migration, podemos rodar um php artisan migrate
para que nossas tabelas sejam criadas no banco de dados.
E também, para criar um usuário para acessar nosso painel, usamos php artisan make:filament-user
, digite um nome, um email e uma senha e use-os para fazer seu login no painel.
Agora vamos trabalhar nossos Models. Primeiro, precisamos adicionar os campos da nossa tabela na variável $fillable
, como mostrado abaixo:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Address extends Model { use HasFactory; protected $fillable = [ 'user_id', 'postal_code', 'address', 'number', 'complement', 'neighborhood', 'city', 'uf', ]; }
Após isso, ainda dentro do nosso Model precisamos criar nossos relacionamentos. Considerando que um usuário terá somente um endereço e que um endereço está relacionado somente a um usuário, podemos adicionar uma relação do tipo hasOne:
Address.php
public function users() { return $this->hasOne(User::class); }
User.php
public function addresses() { return $this->hasOne(Address::class); }
Passo 3: Criando nossa resource
Vamos agora criar nossa resource de User, para isso utilizamos o seguinte comando:
php artisan make:filament-resource User --view --generate // onde --view gera uma página de visualização e // -- generate gerará nosso formulário e tabela automaticamente
Com isso, será criada sua resource em àpp/Filament/Resources
já com seus campos e sua tabela gerada. Você perceberá também que nossos campos de endereço não estão nessa Resource, pois ele faz parte de outro Model e sendo assim "como vamos adicionar os campos de endereço na resource de User, se eles pertencem a outro Model?" Vamos lá, lembram da nossa relação? Então, vamos fazer o seguinte:
Podemos chamar nossa relação para dentro de um Fieldset através da flag ->relationship()
como mostrado no exemplo de layout abaixo:
<?php namespace App\Filament\Resources; use App\Filament\Resources\UserResource\Pages; use App\Models\User; use Exception; use Filament\Forms; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Fieldset; use Filament\Forms\Form; use Filament\Forms\Set; use Filament\Notifications\Notification; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; use Illuminate\Support\Facades\Http; class UserResource extends Resource { protected static ?string $model = User::class; protected static ?string $navigationIcon = 'heroicon-o-users'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\TextInput::make('email') ->email() ->required() ->maxLength(255), Forms\Components\DateTimePicker::make('email_verified_at'), Forms\Components\TextInput::make('password') ->password() ->required() ->maxLength(255), Fieldset::make('address')->label('Address') ->relationship('addresses') // chamando nosso relacionamento ->schema([ Forms\Components\TextInput::make('postal_code') ->maxLength(255), Forms\Components\TextInput::make('address') ->maxLength(255), Forms\Components\TextInput::make('number') ->maxLength(255), Forms\Components\TextInput::make('complement') ->maxLength(255), Forms\Components\TextInput::make('neighborhood') ->maxLength(255), Forms\Components\TextInput::make('city') ->maxLength(255), Forms\Components\TextInput::make('uf') ->maxLength(255), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name') ->searchable(), Tables\Columns\TextColumn::make('email') ->searchable(), Tables\Columns\TextColumn::make('email_verified_at') ->dateTime() ->sortable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ // ]) ->actions([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } // restante o código da resource... }
O Fieldset não é o único componente de layout que aceita relações, em Managing relationships você pode ver os campos/componentes e os tipos de relações que são compatíveis com eles.
Você pode organizar esses campos e a sua tabela da maneira como preferir, dentro da documentação na aba de Layout (Layout de Formulário, Layout de Tabela), você encontra componentes e exemplos que você pode usar para organizar seus dados.
Passo 4: Adicionando funcionalidade de autopreenchimento com API
Enfim, vamos adicionar nossa funcionalidade, para isso vamos utilizar a flag ->suffixAction
no nosso campo de CEP (postal_code
), ela adicionará um botão no final do nosso campo que ao clicar disparará a ação que definirmos dentro dela:
Forms\Components\TextInput::make('postal_code')->mask('99999-999') ->suffixAction( Action::make('search') ->icon('heroicon-o-magnifying-glass') ->action() ) ->maxLength(255),
- Você pode escolher outro ícone pra sua Action no site Heroicons, lá você encontra todos os ícones suportados pelo Filament
- E também usamos o método
->mask()
para fazer a máscara do nosso CEP, a máscara já respeitará o uso de somente números no campo.
Ela ficará visualmente da seguinte forma:
Agora, dentro da nossa Action, dentro da flag ->action()
vamos criar nossa função. Então, como parâmetros da nossa função vamos usar uma variável Set $set
(para setar valores nos outros campos) e uma variável $state
(usada no Filament para pegar o valor atual do campo).
Assim, a primeira coisa que vamos fazer é verificar se o valor atual do nosso campo está em branco e se estiver retornar uma mensagem para o usuário. No exemplo abaixo, estamos retornando uma notificação usando o Notification do Filament:
Forms\Components\TextInput::make('postal_code')->mask('99999-999') ->suffixAction( Action::make('search') ->icon('heroicon-o-magnifying-glass') ->action(function(Set $set, $state){ if (blank($state)) { Notification::make() ->title('Postal Code is required') ->danger()->send(); return; } }) ) ->maxLength(255),
Agora, dentro de um bloco try catch
, podemos fazer nossa requisição. Dentro do try
vamos adicionar uma variável $cepData
que vai receber os dados que vierem da nossa requisição e dentro da URL da nossa requisição adicionamos nossa variável $state
no endpoint para fazer a busca na API a partir do valor digitado no campo.
E dentro do catch
fazemos nossa notificação ao usuário caso nossa requisição retorne algum erro.
Forms\Components\TextInput::make('postal_code')->mask('99999-999') ->suffixAction( Action::make('search') ->icon('heroicon-o-magnifying-glass') ->action(function(Set $set, $state){ if (blank($state)) { Notification::make() ->title('Postal Code is required') ->danger()->send(); return; } try { $cepData = Http::get("https://viacep.com.br/ws/{$state}/json") ->throw()->json(); } catch (Exception $e) { Notification::make() ->title('Postal Code not found') ->danger()->send(); } }) ) ->maxLength(255),
Mas temos um detalhe, na API do ViaCep, ao enviarmos um CEP e ele não for encontrado ela irá retornar um array com uma chave de erro e não uma Exception (mostrado na imagem abaixo) e então não cairá no nosso bloco catch
e o usuário não será notificado.
Para resolver isso, temos que forçar uma Exception para que nosso bloco catch "agarre" este erro e a notificação ao usuário seja enviada, fazemos isso da seguinte forma:
Forms\Components\TextInput::make('postal_code')->mask('99999-999') ->suffixAction( Action::make('search') ->icon('heroicon-o-magnifying-glass') ->action(function(Set $set, $state){ if (blank($state)) { Notification::make() ->title('Postal Code is required') ->danger()->send(); return; } try { $cepData = Http::get("https://viacep.com.br/ws/{$state}/json") ->throw()->json(); // forçando a Exception if (in_array('erro', $cepData)) { throw new Exception('Postal Code not found'); } } catch (Exception $e) { Notification::make() ->title('Postal Code not found') ->danger()->send(); } }) ) ->maxLength(255),
Agora, considerando que nossa variável $cepData
, recebeu nossos dados da requisição, utilizamos a nossa variável $set
para setar os valores que vieram da nossa requisição nos outros campos ou null
caso não venha nada:
Forms\Components\TextInput::make('postal_code')->mask('99999-999') ->suffixAction( Action::make('search') ->icon('heroicon-o-magnifying-glass') ->action(function(Set $set, $state){ if (blank($state)) { Notification::make() ->title('Postal Code is required') ->danger()->send(); return; } try { $cepData = Http::get("https://viacep.com.br/ws/{$state}/json") ->throw()->json(); // forçando a Exception if (in_array('erro', $cepData)) { throw new Exception('Postal Code not found'); } } catch (Exception $e) { Notification::make() ->title('Postal Code not found') ->danger()->send(); } $set('address', $cepData['logradouro'] ?? null); $set('neighborhood', $cepData['bairro'] ?? null); $set('city', $cepData['localidade'] ?? null); $set('uf', $cepData['uf'] ?? null); }) ) ->maxLength(255),
Pronto! Agora nosso autopreenchimento já está funcionando. Abaixo mostro uma demonstração da nossa funcionalidade em ação.
Conclusão
Agora que você já aprendeu como fazer o autopreenchimento, pode usar isso nos seus projetos e tornar seus formulários menos cansativos de preencher e mais dinâmicos. Esta funcionalidade é até mesmo uma forma de facilitar a vida do usuário e reduzir erros de escrita na hora de preencher o formulário.
Abaixo vou estar disponibilizando o link do repositório do nosso exemplo, sinta-se livre para brincar como quiser. Espero que você tenha gostado e qualquer dúvida estou a disposição ^^
Até a próxima !!
Referências
- Laravel Relationships
- Managing Relationships - FIlament
- Filament Form Layout
- Repositório Filament ViaCep
- API ViaCep
- Heroicons
Obrigada @clintonrocha98 mais uma vez pelos feedbacks! 💜
Top comments (4)
Conteúdo sensacional, a galera que faz freelancer deve amar seu conteúdo!!
Muito obrigada :3
Muito massa! O Filament tá me deixando cada vez mais curiosa pra testá-lo. Obrigada pelo conteúdo incrível
Obrigada viu, seus artigos também são muito bons, estou esperando o próximo já hein u.u