---
title: Guia – Formulário com e-mail, banco e listagem pública
---

# Formulário com e-mail + banco + listagem pública

Indicado para:

- inscrições públicas em eventos
- cadastros simples de interesse público
- formulários institucionais com listagem pública (por exemplo, manifestações, apoios, participações)

Regras importantes:

- usar Blade + Bootstrap (sem CSS/JS inline)
- usar `{{ __('texto') }}` em TODO texto visível
- separar bem camadas (Migration, Model, Request, Service, Controller, Views)
- pensar em **segurança**: dados públicos, sanitização, paginação

Nos exemplos, vamos usar o caso genérico **“Manifesto Público”**.

---

## 1. Definir a migration (campos públicos)

Crie a migration:

```bash
php artisan make:migration create_manifestos_publicos_table
```

Edite em `database/migrations/...create_manifestos_publicos_table.php`:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('manifestos_publicos', function (Blueprint $table) {
            $table->id();
            $table->string('nome_publico');       // Nome que aparecerá na listagem
            $table->string('email');              // Não precisa aparecer na listagem
            $table->string('cidade')->nullable(); // Pode aparecer na listagem
            $table->text('mensagem');             // Conteúdo público
            $table->boolean('aprovado')->default(false); // controle interno
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('manifestos_publicos');
    }
};
```

Boas práticas:

- **não** salvar dados sensíveis desnecessários
- se o dado **não** precisa aparecer, não exiba na listagem pública

---

## 2. Criar o Model

`app/Models/ManifestoPublico.php`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ManifestoPublico extends Model
{
    protected $table = 'manifestos_publicos';

    protected $fillable = [
        'nome_publico',
        'email',
        'cidade',
        'mensagem',
        'aprovado',
    ];
}
```

---

## 3. Criar o Request de validação (e sanitização básica)

`app/Http/Requests/Paginas/Manifestos/ManifestoPublicoRequest.php`:

```php
<?php

namespace App\Http\Requests\Paginas\Manifestos;

use Illuminate\Foundation\Http\FormRequest;

class ManifestoPublicoRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'nome_publico' => ['required', 'string', 'max:255'],
            'email'        => ['required', 'email', 'max:255'],
            'cidade'       => ['nullable', 'string', 'max:255'],
            'mensagem'     => ['required', 'string', 'max:2000'],
        ];
    }

    public function attributes(): array
    {
        return [
            'nome_publico' => __('Nome a ser exibido'),
            'email'        => __('E-mail'),
            'cidade'       => __('Cidade'),
            'mensagem'     => __('Mensagem'),
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            // Remove espaços extras
            'nome_publico' => trim((string) $this->input('nome_publico')),
            'cidade'       => trim((string) $this->input('cidade')),
        ]);
    }
}
```

---

## 4. Criar o Service (persistência + e-mail + segurança)

`app/Services/ManifestosPublicosService.php`:

```php
<?php

namespace App\Services;

use App\Mail\ManifestoPublicoRecebidoMail;
use App\Models\ManifestoPublico;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

class ManifestosPublicosService
{
    public function criarManifesto(array $dados): ManifestoPublico
    {
        return DB::transaction(function () use ($dados) {
            $manifesto = ManifestoPublico::create([
                'nome_publico' => $dados['nome_publico'],
                'email'        => $dados['email'],
                'cidade'       => $dados['cidade'] ?? null,
                'mensagem'     => $dados['mensagem'],
                'aprovado'     => false, // padrão: precisa de aprovação interna
            ]);

            Mail::to(config('mail.formularios.manifesto_publico_destino'))
                ->send(new ManifestoPublicoRecebidoMail($manifesto));

            return $manifesto;
        });
    }
}
```

---

## 5. Criar o Mailable

`app/Mail/ManifestoPublicoRecebidoMail.php`:

```php
<?php

namespace App\Mail;

use App\Models\ManifestoPublico;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ManifestoPublicoRecebidoMail extends Mailable
{
    use Queueable, SerializesModels;

    public ManifestoPublico $manifesto;

    public function __construct(ManifestoPublico $manifesto)
    {
        $this->manifesto = $manifesto;
    }

    public function build(): self
    {
        return $this
            ->subject(__('Novo manifesto público recebido'))
            ->view('emails.formularios.manifesto-publico');
    }
}
```

View de e-mail (`resources/views/emails/formularios/manifesto-publico.blade.php`):

```blade
<p><strong>{{ __('Nome público') }}:</strong> {{ $manifesto->nome_publico }}</p>
<p><strong>{{ __('E-mail') }}:</strong> {{ $manifesto->email }}</p>
<p><strong>{{ __('Cidade') }}:</strong> {{ $manifesto->cidade }}</p>

<p><strong>{{ __('Mensagem') }}:</strong></p>
<p>{{ $manifesto->mensagem }}</p>
```

---

## 6. Criar o Controller público

`app/Http/Controllers/Paginas/ManifestosPublicosController.php`:

```php
<?php

namespace App\Http\Controllers\Paginas;

use App\Http\Controllers\Controller;
use App\Http\Requests\Paginas\Manifestos\ManifestoPublicoRequest;
use App\Models\ManifestoPublico;
use App\Services\ManifestosPublicosService;

class ManifestosPublicosController extends Controller
{
    public function __construct(private ManifestosPublicosService $service)
    {
    }

    public function create()
    {
        return view('paginas.manifestos-publicos.create');
    }

    public function store(ManifestoPublicoRequest $request)
    {
        $this->service->criarManifesto($request->validated());

        return redirect()
            ->route('paginas.manifestos-publicos.create')
            ->with('success', __('Manifesto enviado com sucesso e aguardando aprovação.'));
    }

    public function publicIndex()
    {
        $manifestos = ManifestoPublico::query()
            ->where('aprovado', true)
            ->latest('created_at')
            ->paginate(20);

        return view('paginas.manifestos-publicos.index', compact('manifestos'));
    }
}
```

---

## 7. Definir as rotas públicas

Em `routes/web.php`:

```php
use App\Http\Controllers\Paginas\ManifestosPublicosController;

Route::get('/manifestos/enviar', [ManifestosPublicosController::class, 'create'])
    ->name('paginas.manifestos-publicos.create');

Route::post('/manifestos/enviar', [ManifestosPublicosController::class, 'store'])
    ->name('paginas.manifestos-publicos.store');

Route::get('/manifestos', [ManifestosPublicosController::class, 'publicIndex'])
    ->name('paginas.manifestos-publicos.index');
```

Cuidados:

- não exponha IDs sensíveis ou dados desnecessários via URL
- nesse caso, a listagem é geral (`/manifestos`), sem parâmetros sensíveis

---

## 8. Criar a view do formulário público

`resources/views/paginas/manifestos-publicos/create.blade.php`:

```blade
@extends('layouts.app')

@section('content')
    <div class="container">
        <h1>{{ __('Enviar manifesto público') }}</h1>

        @if(session('success'))
            <div class="alert alert-success" role="alert">
                {{ session('success') }}
            </div>
        @endif

        <form action="{{ route('paginas.manifestos-publicos.store') }}" method="post">
            @csrf

            <div class="mb-3">
                <label for="nome_publico" class="form-label">{{ __('Nome a ser exibido') }}</label>
                <input
                    type="text"
                    id="nome_publico"
                    name="nome_publico"
                    class="form-control @error('nome_publico') is-invalid @enderror"
                    value="{{ old('nome_publico') }}"
                    required
                >
                @error('nome_publico')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="email" class="form-label">{{ __('E-mail (não será exibido)') }}</label>
                <input
                    type="email"
                    id="email"
                    name="email"
                    class="form-control @error('email') is-invalid @enderror"
                    value="{{ old('email') }}"
                    required
                >
                @error('email')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="cidade" class="form-label">{{ __('Cidade') }}</label>
                <input
                    type="text"
                    id="cidade"
                    name="cidade"
                    class="form-control @error('cidade') is-invalid @enderror"
                    value="{{ old('cidade') }}"
                >
                @error('cidade')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <div class="mb-3">
                <label for="mensagem" class="form-label">{{ __('Mensagem') }}</label>
                <textarea
                    id="mensagem"
                    name="mensagem"
                    rows="5"
                    class="form-control @error('mensagem') is-invalid @enderror"
                    required
                >{{ old('mensagem') }}</textarea>
                @error('mensagem')
                    <div class="invalid-feedback">{{ $message }}</div>
                @enderror
            </div>

            <button type="submit" class="btn btn-primary">
                {{ __('Enviar manifesto') }}
            </button>
        </form>
    </div>
@endsection
```

Boas práticas:

- usar `@csrf` sempre
- mostrar mensagens de erro com `@error`
- informar claramente o que será ou não exibido publicamente

---

## 9. Criar a Blade de listagem pública

`resources/views/paginas/manifestos-publicos/index.blade.php`:

```blade
@extends('layouts.app')

@section('content')
    <div class="container">
        <h1>{{ __('Manifestos públicos') }}</h1>

        <p class="mb-3">
            {{ __('Abaixo estão listados apenas manifestos aprovados pela equipe responsável.') }}
        </p>

        <div class="list-group">
            @forelse($manifestos as $manifesto)
                <div class="list-group-item">
                    <h2 class="h5 mb-1">
                        {{ $manifesto->nome_publico }}
                        @if($manifesto->cidade)
                            <small class="text-muted">- {{ $manifesto->cidade }}</small>
                        @endif
                    </h2>
                    <p class="mb-1">
                        {{ $manifesto->mensagem }}
                    </p>
                    <small class="text-muted">
                        {{ $manifesto->created_at->format('d/m/Y H:i') }}
                    </small>
                </div>
            @empty
                <p>{{ __('Nenhum manifesto aprovado até o momento.') }}</p>
            @endforelse
        </div>

        <div class="mt-3">
            {{ $manifestos->withQueryString()->links() }}
        </div>
    </div>
@endsection
```

Boas práticas de segurança:

- exibir apenas registros com `aprovado = true`
- não mostrar e-mail nem dados sensíveis
- não permitir HTML enviado pelo usuário (Blade já escapa por padrão com `{{ }}`)

---

## 10. Boas práticas de segurança para formulários públicos

1. **Validação forte**  
   Sempre use `FormRequest` com regras claras (tamanho máximo, tipos, formatos).

2. **Sanitização**  
   - remova espaços extras em campos de texto
   - se precisar aceitar HTML, use filtros específicos e avalie bem o risco

3. **Escapar saída**  
   - use `{{ $variavel }}` (escape padrão do Blade) para evitar XSS
   - evite usar `{!! !!}` com dados de usuários

4. **Dados públicos x internos**  
   - se um campo não precisa aparecer publicamente (como e-mail), **nunca** mostre na listagem
   - pense sempre: “isso pode causar algum problema se aparecer em público?”

5. **Paginação obrigatória**  
   - use `paginate()` em listagens públicas para não sobrecarregar o sistema

6. **Permissões internas para aprovação**  
   - deixe a aprovação (`aprovado = true`) restrita ao admin, com permissão de módulo
   - crie um fluxo simples: recebido → analisado → aprovado/rejeitado

---

## 11. Checklist rápido – fluxo completo

1. **Criar migration** com campos pensados para uso público (sem dados desnecessários).
2. **Criar Model** único do módulo (`ManifestoPublico`).
3. **Criar Request** com validação e pequena sanitização.
4. **Criar Service** que salva no banco em transação e envia e-mail.
5. **Criar Mailable** com view própria.
6. **Criar Controller público** com ações `create`, `store` e `publicIndex`.
7. **Definir rotas públicas** (`/manifestos/enviar` e `/manifestos`).
8. **Criar Blade do formulário** usando Bootstrap, i18n e tratamento de erros.
9. **Criar Blade de listagem pública** paginada, exibindo só dados permitidos.
10. **Configurar destinatário** no `config/mail.php` + `.env`.

Seguindo esses passos, você terá um formulário público que:

- **valida e sanitiza os dados**
- **salva no banco**
- **envia e-mail para o setor responsável**
- **exibe somente o que pode ser público**, de forma segura e padronizada com o restante do projeto.

