Lucas Tonolli

Desenvolvedor Full Stack

Especializado em Laravel e WordPress. Construindo produtos web com foco em performance, automação e usabilidade.

Voltar para o blog
  • Laravel
  • Livewire
  • PHP
  • Projeto

Coisas que aprendi construindo um sistema de aprovação de criativos com Laravel e Livewire 4

Anotações informações e técnicas sobre singe file components, Actions, Observers, policies para layout e outras decisões que curti tomar nesse projeto.

Recentemente terminei um projeto pessoal que achei bem divertido de desenvolver: um sistema de aprovação de criativos para agências.

A ideia é simples: a agência envia um link para aprovação e o cliente consegue revisar o criativo sem precisar fazer login.

A ideia surgiu depois que vi uma publicação em uma plataforma de freelancers. Não era um projeto de cliente; achei o problema interessante e resolvi implementá-lo por conta própria para estudar algumas tecnologias e padrões que ainda não tinha explorado tanto.

Veja o projeto

Stack

  • Laravel 13
  • Livewire 4
  • Tallstack 3
  • Pest 4

Livewire v4 com single componentes

Isso foi a primeira coisa que me chamou atenção. Nas vezes em que tinha usado para um projeto todo componente gerava uma classe e uma view. No início tive um pouco de estranheza, mas acabei me acostumando e no fim achei uma abordagem bem interessante.

Exemplo

<?php

use Livewire\Component;

new class extends Component
{
    public function render()
    {
        return $this->view()
            ->layout('layouts.app')
            ->title('Criar cliente');
    }
};
?>

<div class="app-page">
    ...
    <livewire:clients.form />
</div>

Para componentes pequenos foi uma praticidade não ficar navegando entre os arquivos.

Porém em uma parte específica, julguei ser interessante fazer a separação em um Form Object para remover linhas de validação, mensagens e o processo de salvar, deixando o componente bem mais limpo.

Separação create/edit e reaproveitamento de classe Form

Sempre gostei da ideia de ter um único save() para criar e atualizar registros.

Depois de usar a separação entre create e edit nesse projeto, mudei um pouco de opinião.

Os componentes ficaram muito mais simples de entender e praticamente eliminaram as condicionais que existiam apenas para diferenciar criação e edição.

Exemplo

class ClientForm extends Form
{
    public ?Client $client = null;
    public ?string $name;
    public ?string $cnpj;
    public string|UploadedFile|null $logo = null;

    public function setClient(Client $client): void
    {
        $this->client = $client;
        $this->name = $client->name;
        $this->cnpj = $client->cnpj;
        $this->logo = $client->logo;
    }

    public function save(): void
    {
        $this->isEditing() ? $this->update() : $this->store();
    }

    public function isEditing(): bool
    {
        return isset($this->client) && $this->client->exists;
    }
}

O componente <livewire:clients.form /> é o mesmo nas duas telas a diferença é que o edit passa a propriedade :client="$client"

Actions 💓

Desde que descobri esse padrão eu acho ele incrível. Porém como tudo em programação tem prós e contras, o ideal é sempre avaliar se faz sentido ou não utilizar

Nem tudo no projeto tinha a necessidade de ter uma action, então acabei adotando uma regra simples:

"Precisa fazer algo além de salvar o Model?" ↓ Então merece uma Action.

Olha isso...

(new CreateClientAction)->handle($this->name, $this->cnpj, $this->logo);

Componente genérico para ações de tabela

Quase todas as tabelas tinham exatamente as mesmas ações: editar e excluir.

Em vez de repetir HTML, Tailwind e lógica de confirmação em todas elas, criei um componente reutilizável

Basicamente o componente precisa do model, uma rota de edição (opcional), ter as permissões (Policy) para as ações e se necessário consegue adicionar ações customizadas

<livewire:table.actions :model="$row" edit-route="clients.edit" wire:key="actions-{{ $row->id }}">
	  /*slot para ações customizadas*/
    <x-button.circle icon="power" wire:click="confirmToggleStatus({{ $row->id }})" />
</livewire:table.actions>

Atributo #[On]

Eu já sabia da existência mas usar ele em um componente genérico para o componente pai saber que o componente precisa fazer algo foi muito legal, além de deixar uma experiência muito fluida (eu achei pelo menos).

// table.actions dispara depois de excluir
$this->dispatch('item-deleted');
// na tela de editar cliente, escuta e recalcula
#[Computed]
#[On('item-deleted')]
public function rows()
{
    return $this->client->contacts()->paginate($this->perPage);
}

Observer

Foi uma daquelas funcionalidades do Laravel que eu conhecia há bastante tempo, mas nunca tinha encontrado um caso onde realmente fizesse sentido utilizar.

Nesse projeto ele acabou encaixando muito bem.

Policies

Outra coisa que gostei bastante foi reforçar o uso de Policies.

Além de centralizar as regras de autorização, elas acabaram funcionando como uma segunda camada de segurança.

Mesmo quando uma ação fica escondida na interface, a Policy continua garantindo que ela não possa ser executada caso alguém tente acessar diretamente.

Além disso, o próprio Blade fica muito mais limpo utilizando @can ou ->can() nas rotas.

Conclusão

No fim, foi um projeto que me fez sair bastante da zona de conforto.

Mais do que aprender recursos novos do Laravel, gostei de finalmente encontrar situações em que padrões como Actions, Form Objects, Policies e Observers realmente fizeram sentido.

É aquele tipo de projeto que faz a gente mudar algumas opiniões e perceber que certos recursos do framework só "clicam" quando aparecem em um problema real.