DEV Community

Cover image for Autopreenchimento de campos no FilamentPHP usando API
Adryanne Kelly
Adryanne Kelly

Posted on

Autopreenchimento de campos no FilamentPHP usando API

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
    }
};
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

User.php

public function addresses()
{
    return $this->hasOne(Address::class);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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...

}
Enter fullscreen mode Exit fullscreen mode

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),
Enter fullscreen mode Exit fullscreen mode
  • 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:

suffixAction visualmente

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),
Enter fullscreen mode Exit fullscreen mode

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),
Enter fullscreen mode Exit fullscreen mode

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.

Array com chave de erro

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),
Enter fullscreen mode Exit fullscreen mode

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),
Enter fullscreen mode Exit fullscreen mode

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

 

Obrigada @clintonrocha98 mais uma vez pelos feedbacks! 💜

Top comments (4)

Collapse
 
clintonrocha98 profile image
Clinton Rocha

Conteúdo sensacional, a galera que faz freelancer deve amar seu conteúdo!!

Collapse
 
adryannekelly profile image
Adryanne Kelly

Muito obrigada :3

Collapse
 
fernandafmsf profile image
Fernanda Fernandes

Muito massa! O Filament tá me deixando cada vez mais curiosa pra testá-lo. Obrigada pelo conteúdo incrível

Collapse
 
adryannekelly profile image
Adryanne Kelly

Obrigada viu, seus artigos também são muito bons, estou esperando o próximo já hein u.u