risetechapps / form-request-for-laravel
Package Form Request
Package info
github.com/risetechapps/form-request-for-laravel
pkg:composer/risetechapps/form-request-for-laravel
Requires
- php: ^8.3
- facade/ignition-contracts: ^1.0.2
- illuminate/support: ^12.0
- risetechapps/has-uuid-for-laravel: ^1.2
- risetechapps/monitoring-for-laravel: ^4.0.1
- tpetry/laravel-postgresql-enhanced: ^3.7.0
Requires (Dev)
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
📌 Sobre o Projeto
O Laravel Form Request é um package para Laravel que gerencia as regras de validação dos formulários de forma dinâmica, permitindo definir regras tanto via código quanto via banco de dados.
✨ Funcionalidades
- 📋 Forms dinâmicos - Regras de validação configuráveis em banco de dados
- 📁 Forms via código - Regras definidas em classes PHP
- 🔐 Validadores customizados - Documentos brasileiros, boletos, Pix, cartão e senha forte
- 🔗 Contexto de validação - Valores da rota disponíveis às regras, com a sintaxe nativa do Laravel
- 🏢 Escopos de presença - Condições extras nas regras
uniqueeexistssem alterar a regra - ⚡ Cache - Cache automático das regras para melhor performance
- 🔄 Export/Import - Migração de regras entre ambientes
- 📊 Estatísticas - Monitoramento e análise de uso
🚀 Instalação
1⃣ Requisitos
- PHP >= 8.3
- Laravel >= 12
- Composer instalado
2⃣ Instalação do Package
composer require risetechapps/form-request-for-laravel
3⃣ Publicar Configuração
php artisan vendor:publish --tag=config
4⃣ (Opcional) Publicar Traduções
php artisan vendor:publish --tag=lang
5⃣ Executar Migrations
php artisan form-request:migrate
6⃣ (Opcional) Popular regras padrão
php artisan form-request:seed
📋 Comandos Artisan
Gerenciamento de Regras
| Comando | Descrição |
|---|---|
php artisan form-request:list |
Lista todas as regras em formato de tabela |
php artisan form-request:list --database |
Lista apenas regras do banco |
php artisan form-request:list --config |
Lista apenas regras em código |
php artisan form-request:list --form=clients |
Filtra por formulário específico |
php artisan form-request:list --field=email |
Filtra por campo específico |
Exportar/Importar
# Exportar todas as regras php artisan form-request:export --file=regras.json # Exportar apenas um formulário php artisan form-request:export --file=clients.json --form=clients # Importar regras php artisan form-request:import --file=regras.json # Importar e sobrescrever existentes php artisan form-request:import --file=regras.json --force
Cache
# Pré-carregar cache de todos os formulários php artisan form-request:warm-cache # Pré-carregar cache de um formulário específico php artisan form-request:warm-cache --form=clients # Limpar cache de um formulário php artisan form-request:clear-cache clients # Limpar cache de todos os formulários php artisan form-request:clear-cache --all
Validação e Estatísticas
# Validar sintaxe das regras php artisan form-request:validate-rules # Estatísticas básicas php artisan form-request:stats # Estatísticas detalhadas php artisan form-request:stats --detailed
📝 Uso
Estendendo DynamicFormRequest
A forma recomendada. A classe declara apenas qual formulário resolver; o pacote cuida de buscar as regras, traduzir as mensagens e alimentar o validador:
use RiseTechApps\FormRequest\Http\Requests\DynamicFormRequest; class StoreClientRequest extends DynamicFormRequest { protected function formKey(): string { return 'clients'; } public function authorize(): bool { return auth()->check() && auth()->user()->hasPermission('clients.store'); } }
Para uma atualização, declare o contexto — os valores que as regras podem referenciar:
class UpdateClientRequest extends DynamicFormRequest { protected function formKey(): string { return 'clients'; } #[\Override] protected function validationContext(): array { return ['id' => $this->route('id')]; } public function authorize(): bool { return auth()->check() && auth()->user()->hasPermission('clients.update'); } }
As regras são resolvidas uma única vez por request e ficam em cache na instância.
Usando apenas a Trait HasFormValidation
Se a classe já estende outro FormRequest e não pode trocar de base, o trait
entrega o tratamento de erros e o mesmo mecanismo de contexto. Nesse caso a
resolução das regras fica por sua conta:
use Illuminate\Foundation\Http\FormRequest; use RiseTechApps\FormRequest\Traits\HasFormValidation\HasFormValidation; use RiseTechApps\FormRequest\ValidationRuleRepository; class StoreClientRequest extends FormRequest { use HasFormValidation; protected array $result = []; public function __construct( protected ValidationRuleRepository $repository, array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null ) { parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content); $this->result = $this->repository->getRules('clients', $this->validationContext()); } protected function validationContext(): array { return ['id' => $this->route('id')]; } public function rules(): array { return $this->result['rules']; } public function authorize(): bool { return auth()->check(); } }
O construtor precisa repassar os sete parâmetros do
Request. Omiti-los quebraRequest::create()eduplicate(), que constroem a classe posicionalmente.
Ao declarar
validationContext()numa classe que o recebe do trait, não use#[\Override]: não há método de pai correspondente e o PHP emite um erro fatal. Em subclasses deDynamicFormRequesto atributo é válido.
Registrando Regras via Código
Opção 1: Usando FormRequest::register()
No AppServiceProvider ou em um Service Provider:
use RiseTechApps\FormRequest\FormRequest; public function boot(): void { FormRequest::register('clients', [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:clients,email', 'cpf' => 'required|cpf', 'phone' => 'nullable|string', ], [ 'name.required' => 'O nome é obrigatório', 'email.unique' => 'Este email já está cadastrado', 'cpf.cpf' => 'CPF inválido', ], [ 'description' => 'Regras de validação para clientes', ]); }
Opção 2: Usando RulesContract (Recomendado para projetos grandes)
Crie uma classe de regras:
<?php namespace App\Rules; use RiseTechApps\FormRequest\Contracts\RulesContract; class ClientsRule implements RulesContract { public static function Rules(): array { return [ 'store_client' => [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:clients,email', 'cpf' => 'required|cpf', ], 'update_client' => [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:clients,email', ], ]; } public static function Messages(): array { return [ 'store_client' => [ 'name.required' => 'O nome do cliente é obrigatório', 'email.required' => 'O email é obrigatório', 'email.email' => 'Informe um email válido', 'email.unique' => 'Este email já está cadastrado', 'cpf.required' => 'O CPF é obrigatório', 'cpf.cpf' => 'CPF inválido', ], 'update_client' => [ 'name.required' => 'O nome do cliente é obrigatório', 'email.unique' => 'Este email já está em uso por outro cliente', ], ]; } public static function Validator(): array { return [ // Validadores customizados específicos deste módulo ]; } }
Registre no AppServiceProvider:
use RiseTechApps\FormRequest\RulesRegistry; use App\Rules\ClientsRule; public function boot(RulesRegistry $rulesRegistry): void { $rulesRegistry->register(ClientsRule::class); }
Vantagens desta abordagem:
- Separação de responsabilidades
- Facilidade de manutenção
- Suporte a múltiplos formulários em uma única classe
- Organização por módulo
Opção 3: Usando a facade
O alias FormRequest é registrado automaticamente pelo package discovery e expõe os
mesmos métodos:
use FormRequest; FormRequest::register('clients', ['name' => 'required|string|max:255']);
Atenção: o alias global tem o mesmo nome curto de
Illuminate\Foundation\Http\FormRequest. Em arquivos que estendem o FormRequest do Laravel, mantenha ouseexplícito.
API RESTful
O pacote expõe endpoints para gerenciar formulários via API:
// Em routes/api.php use RiseTechApps\FormRequest\FormRequest; FormRequest::routes([ 'middleware' => ['auth:sanctum'], 'prefix' => 'admin' ]);
Endpoints disponíveis:
GET /api/admin/forms- Listar formuláriosPOST /api/admin/forms- Criar formulárioGET /api/admin/forms/{id}- Ver formulárioPUT /api/admin/forms/{id}- Atualizar formulárioDELETE /api/admin/forms/{id}- Remover formulário
🔗 Contexto de validação
O contexto são valores que as regras podem referenciar — tipicamente o id do
registro em edição. Ele é declarado uma vez, em validationContext(), e o pacote
o usa em dois lugares:
- Ao resolver as regras — interpola placeholders na string e completa o
exceptde regrasunique. - Nos dados do validador — o contexto é mesclado em
validationData(), então as regras o enxergam em tempo de execução.
O contexto tem precedência sobre o corpo da requisição: ele vem da rota ou da
sessão autenticada, e o payload não deve poder sobrescrevê-lo. Uma chave sem regra
correspondente não aparece em validated() e portanto não é persistida.
Ignorar o próprio registro em unique
Três formas, da mais idiomática para a mais explícita:
// 1. Interpolação nativa do Laravel: lê o valor dos dados validados. 'email' => 'required|email|unique:clients,email,[id]', // 2. Sem informar o except: o pacote completa com o id do contexto. 'email' => 'required|email|unique:clients,email', // 3. Placeholder do pacote, para posições que o Laravel não interpola. 'email' => 'required|email|unique:clients,email,{id}',
Um except informado explicitamente é sempre preservado — o preenchimento
automático só ocorre quando a posição foi omitida.
Regras customizadas
Um parâmetro que nomeia um campo é idioma nativo do Laravel (same:password,
gt:idade). O pacote preserva a string, e quem resolve o valor é o validador:
'cpf' => 'required|cpf|meuValidador:id',
public static function validate($attribute, $value, $parameters, $validator): bool { $id = data_get($validator->getData(), $parameters[0] ?? 'id'); // ... }
Placeholders do pacote
Para posições que a sintaxe do Laravel não interpola, o contexto pode ser
injetado na string da regra com {chave} ou :chave:
'ref' => 'required|in:{tipo},outro', // in:cliente,outro 'ref' => 'required|in::tipo,outro', // idem
A substituição ocorre apenas na porção de parâmetros da regra, nunca no nome.
Um parâmetro que apenas se chama como a chave do contexto é preservado:
exists:clients,id mantém a coluna id.
Para condições dinâmicas em
uniqueeexists— tenant, soft delete — prefira os escopos de presença: eles se aplicam a todas as regras sem alterar nenhuma string.
🔐 Validadores Customizados
O pacote inclui validadores extras:
Documentos
| Regra | Descrição | Exemplo |
|---|---|---|
cpf |
Valida CPF brasileiro | 'cpf' => 'required|cpf' |
cnpj |
Valida CNPJ, numérico ou alfanumérico | 'cnpj' => 'required|cnpj' |
cnae |
Estrutura do código CNAE 2.x (7 dígitos) | 'cnae' => 'required|cnae' |
ncm |
Estrutura do código NCM/SH (8 dígitos) | 'ncm' => 'required|ncm' |
O
cnpjsegue a Nota Técnica COTEC nº 49/2024: as 12 primeiras posições aceitam letras e números, e os 2 dígitos verificadores usam o valor ASCII do caractere menos 48. CNPJs numéricos antigos continuam válidos sem alteração.
cnaeencmnão possuem dígito verificador. A validação é estrutural (tamanho, divisão/capítulo válidos) e não garante que o código exista nas tabelas oficiais.
Financeiro
| Regra | Descrição | Exemplo |
|---|---|---|
credit_card |
Número de cartão pelo algoritmo de Luhn | 'card' => 'required|credit_card' |
credit_card:bandeiras |
Restringe as bandeiras aceitas | 'card' => 'required|credit_card:visa,mastercard' |
pix_key |
Chave Pix em qualquer formato do Banco Central | 'key' => 'required|pix_key' |
pix_key:tipos |
Restringe os tipos aceitos | 'key' => 'required|pix_key:email,phone' |
bank_barcode |
Código de barras bancário de 44 posições | 'code' => 'required|bank_barcode' |
digitable_line |
Linha digitável de 47 ou 48 posições | 'line' => 'required|digitable_line' |
bank_slip |
Alias de digitable_line |
'boleto' => 'required|bank_slip' |
Bandeiras aceitas em credit_card: visa, mastercard, amex, elo, hipercard,
diners, discover, jcb.
Tipos aceitos em pix_key: cpf, cnpj, email, phone, random.
Chaves de CPF/CNPJ trafegam apenas com dígitos e telefone segue E.164 (+5511999998888),
como definido pela DICT.
bank_barcode e digitable_line cobrem tanto títulos bancários quanto contas de
arrecadação (iniciadas em 8). Na linha digitável, além do DV de cada campo, o código de
barras é remontado e revalidado.
Outros
| Regra | Descrição | Exemplo |
|---|---|---|
strong_password |
Força da senha, parametrizável | 'password' => 'required|strong_password' |
uniqueJson |
Valida unicidade em coluna JSON | 'email' => 'uniqueJson:users,preferences.email' |
existsJson |
Exige que o valor exista em coluna JSON | 'email' => 'existsJson:users,preferences.email' |
required_if_any |
Requerido se qualquer campo for preenchido | 'field' => 'required_if_any:field_a,field_b' |
O strong_password exige, por padrão, 8 caracteres com maiúscula, minúscula, número e
símbolo. Os parâmetros ajustam esse conjunto — um número define o comprimento mínimo e os
demais valores (upper, lower, number, symbol) substituem a lista de exigências:
'password' => 'required|strong_password', // padrão 'password' => 'required|strong_password:12', // apenas aumenta o mínimo 'password' => 'required|strong_password:10,upper,number',
Letras acentuadas contam como letra, e não como símbolo.
Mensagens de erro
Todas as regras acima têm mensagem padrão em inglês, registrada como fallback. Elas só aparecem quando a aplicação não define a sua própria — a tradução para outros idiomas fica a cargo de quem usa o package.
A ordem de precedência é a do próprio Laravel:
- Mensagem inline do FormRequest (
messages()ou o 3º argumento deValidator::make) validation.custom.{campo}.{regra}nos arquivos de tradução da aplicaçãovalidation.{regra}nos arquivos de tradução da aplicação- Mensagem padrão do package (inglês)
Para traduzir, basta declarar as chaves no lang da aplicação:
// lang/pt_BR/validation.php return [ 'cpf' => 'O campo :attribute deve conter um CPF válido.', 'strong_password' => 'A senha informada é muito fraca.', 'pix_key' => 'O campo :attribute deve conter uma chave Pix válida.', ];
A chave é o nome da regra em snake_case, que é como o Laravel a procura. Atenção nas regras em camelCase:
uniqueJsonviravalidation.unique_jsoneexistsJsonviravalidation.exists_json.
Se preferir partir das mensagens do package, publique-as e edite:
php artisan vendor:publish --tag=lang
Os arquivos vão para lang/vendor/form-request/. Esse caminho altera apenas o fallback;
para aplicações multi-idioma prefira declarar validation.{regra} no lang da aplicação,
que é resolvido a cada validação e respeita o locale do request.
Registrando validadores próprios
Em config/rules.php, no formato 'regra' => Classe::class. A classe precisa implementar
RiseTechApps\FormRequest\Contracts\ValidatorContract. Chaves declaradas aqui
sobrescrevem os validadores nativos:
'validators' => [ 'my_document' => \App\Validators\MyDocument::class, ],
🏢 Escopos de unique e exists
Regras como unique:authentications,email geram sempre a mesma consulta:
select count(*) as aggregate from "authentications" where "email" = ?
Em cenários multi-tenant é preciso restringir essa consulta ao tenant atual, o que
normalmente exigiria criar uma regra personalizada para cada tabela. Os escopos de
presença injetam condições extras em todas as regras unique e exists, sem
alterar nenhuma string de regra:
use RiseTechApps\FormRequest\FormRequest; // Em AppServiceProvider::boot() FormRequest::presenceScope('authentications', fn($query) => $query->where('tenant_id', tenant()->id)); // Aplicado a todas as tabelas FormRequest::presenceScopeAll(fn($query) => $query->whereNull('deleted_at'));
A regra continua unique:authentications,email, mas a consulta passa a ser:
select count(*) as aggregate from "authentications" where "tenant_id" = ? and "deleted_at" is null and "email" = ?
As closures são avaliadas no momento da consulta, então leem sempre o tenant resolvido naquele request ou job.
Escopos nomeados
Informar um nome permite substituir ou remover o escopo depois — útil quando o registro acontece em um middleware por request:
use RiseTechApps\FormRequest\Validation\PresenceScopeRegistry; FormRequest::presenceScope('authentications', $scope, 'tenant'); app(PresenceScopeRegistry::class)->forget('authentications', 'tenant');
Ignorando os escopos
FormRequest::withoutPresenceScopes(fn() => $validator->validate());
Via configuração
Cada entrada é uma classe invocável resolvida pelo container, que recebe o query builder
e o nome da tabela. Use '*' para alcançar todas as tabelas:
'presence_scopes' => [ '*' => [\App\Validation\TenantScope::class], 'authentications' => [\App\Validation\NotDeletedScope::class], ],
namespace App\Validation; use Illuminate\Database\Query\Builder; class TenantScope { public function __invoke(Builder $query, string $table): void { $query->where('tenant_id', tenant()->id); } }
Os escopos valem para
uniqueeexists. O ponto de extensão é oPresenceVerifierdo Laravel, que não informa qual das duas regras originou a consulta.
⚛️ Configuração
Arquivo config/rules.php:
return [ // Validadores próprios: 'regra' => Classe::class 'validators' => [ // 'my_document' => \App\Validators\MyDocument::class, ], // Condições extras aplicadas às regras unique e exists 'presence_scopes' => [ // '*' => [\App\Validation\TenantScope::class], // 'authentications' => [\App\Validation\NotDeletedScope::class], ], // Configuração de cache 'cache' => [ 'enabled' => true, 'ttl' => 300, // segundos 'store' => null, // null = cache padrão ], ];
🏛️ Arquitetura
Estrutura de Banco
Tabela form_requests:
id- UUIDform- Nome/chave do formulário (unique)rules- JSON com as regrasmessages- JSON com mensagens personalizadasdata- Metadados adicionaisdescription- Descriçãotimestamps- created_at, updated_at
Fluxo de Resolução
- Cache - Verifica se existe no cache. Cada formulário usa uma única chave,
form-request:{nome}, invalidada a cada escrita. - Banco de Dados - Busca regras persistidas, apenas pelo nome do formulário.
- Configuração - Se o banco não tiver o formulário, usa as regras em código.
- Mensagens - Gera mensagens padrão se o formulário não trouxer as suas.
- Contexto - Sobre o resultado cacheado, completa o
exceptdas regrasuniquee resolve os placeholders. Por acontecer depois do cache, o contexto não multiplica as entradas.
Validação
Os validadores customizados são registrados no boot via Validator::extend, a partir do
RulesRegistry — que reúne os validadores nativos, os das classes RulesContract e os
declarados em config('rules.validators').
Para as regras unique e exists, o package substitui o validation.presence do Laravel
por um ScopedPresenceVerifier, que aplica os escopos registrados. Um presence verifier
totalmente customizado da aplicação tem precedência e é preservado.
🧪 Testes
composer install
composer test
🤝 Contribuição
Sinta-se à vontade para contribuir! Basta seguir estes passos:
- Faça um fork do repositório
- Crie uma branch (
feature/nova-funcionalidade) - Faça um commit das suas alterações
- Envie um Pull Request
📜 Licença
Este projeto é distribuído sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.
💡 Desenvolvido por Rise Tech