Aplicação web com Laravel

Rotas, controladores, modelos e vistas para recursos REST apoiados na base de dados.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Um GET /sessoes precisa de escolher uma operação, consultar sessões e produzir uma resposta. No Laravel, a rota encaminha o pedido, o controlador coordena a operação, o modelo representa dados e relações e a vista produz HTML. Middleware, policies e serviços participam quando a operação exige autenticação, autorização ou regras de negócio.

O recurso Sessões de ponta a ponta

No percurso de um pedido, distingue o software pelo papel:

PeçaPapelExemplos
NavegadorPede recursos e apresenta a páginaFirefox, Brave
Servidor HTTPRecebe pedidos e devolve recursos ou encaminha a aplicaçãoApache HTTP Server, Nginx
AplicaçãoExecuta regras e produz a resposta dinâmicaUma aplicação Laravel executada em PHP
Base de dadosGuarda e consulta dadosPostgreSQL

O servidor pode devolver um ficheiro estático ou obter uma resposta da aplicação. O navegador é o cliente deste percurso. O motor que interpreta e apresenta HTML no navegador não substitui o servidor HTTP.

Um recurso é algo identificável por um endereço, como uma sessão ou a lista de sessões. A aplicação define que operações permite sobre ele. As sessões da loja são um recurso. Estas rotas usam métodos HTTP com a semântica esperada; uma aplicação REST também tem outras condições, explicadas em HTTP:

Route::get('/sessoes', [SessaoController::class, 'index']);
Route::post('/sessoes', [SessaoController::class, 'store'])->middleware('auth');

O GET lista, o POST cria. O controlador recebe o pedido já encaminhado e devolve uma resposta, sem gerar HTML à mão:

// ler não muda nada: pode repetir-se e ficar na história do navegador
Route::get('/sessoes', [SessaoController::class, 'index']);

O index lê as sessões futuras ordenadas e entrega-as à vista. Nada é escrito, por isso recarregar a página é seguro.

// escrever muda o estado: exige validação e redirecionamento
Route::post('/sessoes', [SessaoController::class, 'store'])->middleware('auth');

O store verifica a permissão, valida os dados e só depois escreve. Em caso de erro volta ao formulário; em caso de sucesso redireciona, para recarregar não criar a sessão duas vezes.

// Excerto: Request, Gate e Sessao são importados no controlador.
class SessaoController extends Controller
{
    public function index()
    {
        $sessoes = Sessao::where('data_hora', '>', now())
            ->orderBy('data_hora')
            ->get();
        return view('sessoes.index', ['sessoes' => $sessoes]);
    }

    public function store(Request $request)
    {
        Gate::authorize('create', Sessao::class);
        $dados = $request->validate([
            'evento_id' => 'required|exists:eventos,id',
            'data_hora' => 'required|date|after:now',
            'preco' => 'required|numeric|min:0',
            'lotacao' => 'required|integer|min:1',
        ]);
        Sessao::create($dados);
        return redirect('/sessoes', 303);
    }
}

O método validate é o ponto a fixar: se os dados cumprirem as regras, devolve os campos validados. Não os sanitiza para todos os destinos. Se falharem num formulário web, interrompe e redireciona com os erros; um pedido que espera JSON recebe normalmente 422. A regra exists:eventos,id repete na aplicação a chave estrangeira da base de dados, para dar erro amigável antes do erro técnico. A regra after:now valida a criação de uma sessão futura. Sessões antigas continuam a existir no histórico.

Este exemplo pressupõe SessaoPolicy associada ao modelo: o seu método create recebe o utilizador autenticado e permite apenas os papéis que podem gerir sessões. Gate é a facade Illuminate\Support\Facades\Gate e recusa com 403 quando a policy não permite; o formulário usa @csrf e apresenta os erros com @error e os valores anteriores com old(...).

Migração e modelo

A tabela nasce de uma migração, código versionado que cria e altera o esquema. Este excerto cria as colunas e a FK da tabela do esquema relacional, com campos adicionais de criação e atualização:

Schema::create('sessoes', function (Blueprint $table) {
    $table->id();
    $table->foreignId('evento_id')->constrained('eventos');
    $table->dateTime('data_hora');
    $table->decimal('preco', 8, 2);
    $table->integer('lotacao');
    $table->timestamps();
});

O excerto de migração não declara os CHECK de preço e lotação. Mantém essas garantias no SQL de PostgreSQL, além da validação do formulário. Se usares o esquema sem created_at e updated_at, o modelo deve declarar public $timestamps = false.

O modelo liga a classe à tabela e declara o que se pode preencher em massa:

class Sessao extends Model
{
    protected $table = 'sessoes';
    protected $fillable = ['evento_id', 'data_hora', 'preco', 'lotacao'];

    public function evento()
    {
        return $this->belongsTo(Evento::class);
    }
}

O nome da tabela é explícito porque as convenções de pluralização não garantem os nomes portugueses do esquema. Os restantes modelos precisam do mesmo cuidado. O belongsTo espelha a associação do UML: cada sessão pertence a um evento. A partir daqui, $sessao->evento->titulo navega da sessão ao evento sem SQL escrito à mão.

Um POST que falha a validação

Com um utilizador autorizado a criar sessões, preenche o formulário de nova sessão com lotação 0 e guarda. O pedido POST /sessoes chega ao store, o validate rejeita lotacao por violar min:1, e o utilizador volta ao formulário com a mensagem de erro junto ao campo e os restantes valores preservados. Nenhuma linha tocou na base de dados, porque a validação correu antes de qualquer escrita.

Se a autorização e a validação passassem, o create inseria a sessão e o redirecionamento voltava à lista. Se dois compradores disputarem o último lugar, a validação anterior à compra pode estar desatualizada. O protocolo de concorrência decide qual compra pode confirmar.

Para isolar a condição «inteiro positivo», o bloco usa um inteiro PHP onde um formulário HTTP enviaria texto, que o validador Laravel também precisa de interpretar:

Validação isolada da lotação
<?php
function validar($dados) {
    $erros = [];
    if (!isset($dados['lotacao']) || !is_int($dados['lotacao']) || $dados['lotacao'] < 1) {
        $erros['lotacao'] = 'A lotação tem de ser um inteiro a partir de 1.';
    }
    return $erros;
}
$erros = validar(['evento_id' => 3, 'preco' => 10, 'lotacao' => 0]);
foreach ($erros as $campo => $mensagem) {
    echo "$campo: $mensagem
";
}
Resultado

Executa o código para ver a saída e os gráficos.

Num formulário Laravel, o erro é apresentado junto ao campo. O bloco não reproduz a conversão das strings HTTP nem todas as regras do validador.

Na sequência da página da lista, a vista gera HTML no servidor; o navegador recebe esse HTML e decide quando enviar outro pedido.

Entrar e proteger rotas

A loja tem registo e login nos requisitos, e as compras exigem saber quem compra. A autenticação no Laravel segue o mesmo molde do recurso: formulário, validação e tentativa de entrada.

public function entrar(Request $request)
{
    $dados = $request->validate([
        'email' => 'required|email',
        'palavra_passe' => 'required',
    ]);
    if (!Auth::attempt([
        'email' => $dados['email'],
        'password' => $dados['palavra_passe'],
    ])) {
        return back()->withErrors([
            'email' => 'Credenciais inválidas.',
        ]);
    }
    $request->session()->regenerate();
    return redirect('/sessoes');
}

Este fragmento pressupõe o modelo autenticável e a coluna password do starter Laravel. Para usar utilizadores.palavra_passe_hash do esquema anterior, configura o provider e o modelo para essa tabela e esse atributo de hash. A chave password das credenciais continua a receber a palavra-passe em claro para a API a verificar. Não passes o hash como credencial.

O Auth::attempt verifica a palavra-passe com hash e inicia a sessão. Se falhar, volta atrás com erro genérico de propósito: dizer “email certo, palavra-passe errada” ajuda quem ataca a adivinhar contas.

Com a entrada feita, protege as rotas que precisam de comprador autenticado com o middleware auth:

Route::middleware('auth')->group(function () {
    Route::post('/compras', [CompraController::class, 'store']);
});

Quem não entrou é redirecionado para o login em vez de chegar ao controlador. A autorização sobre a operação e o recurso precisa de uma policy, gate ou verificação equivalente no servidor.

Ambiente e ferramentas

O ambiente do projeto usa PHP e Laravel para a aplicação, Composer para as dependências, PostgreSQL para os dados e Docker Compose para a infraestrutura local. Git e GitLab guardam versões e permitem rever as alterações do grupo. Usa o template e o guia da edição atual, ligados na apresentação da cadeira.

Uma imagem Docker descreve o sistema de ficheiros e a configuração que servem para criar contentores. Um contentor é uma instância em execução dessa imagem. Um volume conserva os dados fora da camada descartável do contentor. Parar ou recriar o contentor não é o mesmo que apagar o volume da base de dados.

Blade: gerar uma vista

view('sessoes.index', ['sessoes' => $sessoes]) passa os dados à vista resources/views/sessoes/index.blade.php. Blade compila o template para PHP:

<ul>
  @foreach ($sessoes as $sessao)
    <li>{{ $sessao->evento->titulo }}: {{ $sessao->data_hora }}</li>
  @endforeach
</ul>

@foreach repete o elemento para cada sessão. {{ ... }} escreve texto escapado para HTML. O acesso a evento precisa da relação do modelo e pode provocar consultas adicionais, como veremos no caso N+1.

Ler o âmbito de uma variável em PHP

Uma variável global não fica automaticamente disponível numa função nomeada. Os parâmetros e variáveis locais pertencem à chamada. No programa abaixo, a $taxa local de calcular vale 1; a global continua a valer 4.

Âmbito local, global e captura por valor
<?php
$taxa = 4;

function calcular($base) {
    $taxa = 1;
    return $base + $taxa;
}

function aumentarTaxa() {
    global $taxa;
    $taxa++;
}

echo calcular(10) . PHP_EOL;
echo $taxa . PHP_EOL;
aumentarTaxa();
echo $taxa . PHP_EOL;

$ajustar = fn($n) => $n + $taxa;
$taxa = 9;
echo $ajustar(10) . PHP_EOL;
Resultado

Executa o código para ver a saída e os gráficos.

global $taxa em aumentarTaxa liga o nome local à variável global e permite alterá-la. A função arrow captura o valor 5 ao ser criada; mudar depois a global para 9 não muda essa captura. Uma closure escrita com function precisa de use ($taxa) para essa captura por valor, ou use (&$taxa) para uma referência.

Ler uma variável local não definida gera um aviso no PHP atual; não soma silenciosamente a global do mesmo nome. Ao prever uma saída, começa por identificar onde cada nome foi definido. Para aplicações, passar dados explicitamente torna as dependências da função mais fáceis de seguir.

Separar responsabilidades no pedido

Uma rota escolhe a operação. Middleware pode verificar sessão e proteção CSRF antes do controlador. O controlador coordena o caso de uso, a policy decide a autorização, o modelo representa dados e relações, e a vista produz a representação. Nem todos os pedidos usam uma vista HTML: uma API pode devolver JSON, e uma operação pode redirecionar ou devolver 204.

Validação pergunta se os dados têm o formato e o domínio esperados. Autorização pergunta se esta pessoa pode executar esta operação sobre este objeto. Autenticação identifica a pessoa. As três são necessárias em contextos diferentes e nenhuma substitui as restantes.

public function atualizar(Request $request, Bilhete $bilhete)
{
    Gate::authorize('update', $bilhete);
    $dados = $request->validate(['estado' => 'required|in:reservado,pago,cancelado']);
    // A transição de estado e a capacidade exigem ainda a regra de negócio.
    return $this->servicoCompras->alterarEstado($bilhete, $dados['estado']);
}

Este fragmento usa a facade Gate e mostra a separação; pressupõe uma policy e um serviço com o protocolo de compra definido. Autorizar uma alteração não torna válida qualquer transição. Por exemplo, passar de cancelado a pago pode exigir voltar a reservar capacidade.

ORM e o problema N+1

Se carregares 100 sessões e, num ciclo, acederes a $sessao->evento, o carregamento preguiçoso pode fazer uma consulta inicial e mais 100 consultas. É o problema N+1. Carregamento antecipado, como Sessao::with('evento')->get(), pode carregar as relações em consultas agrupadas. Mede as consultas e os dados transferidos; antecipar todas as relações sem necessidade também custa recursos.

O mesmo cuidado aplica-se a verificações de autorização dentro de ciclos: um @can por linha numa lista pode reavaliar a policy para cada objeto e repetir consultas que a decisão usa. Quando a decisão é igual para todas as linhas, avalia-a uma vez fora do ciclo; quando depende de cada objeto, confirma que as relações usadas pela policy já vêm carregadas. Uma verificação barata em isolamento pode dominar o tempo da página quando é repetida cem vezes.

O modelo não é a página. Não coloques decisões de autorização só no template: um pedido direto pode contorná-lo. Não aceites campos privilegiados só porque estão no $fillable; esse atributo limita a atribuição em massa, não determina quem pode alterar cada valor.

Resposta e proteção de dados

Usa parâmetros para valores nas consultas; valida escolhas de coluna e direção de ordenação por uma lista explícita. Escapa dados na vista e usa proteção CSRF nas rotas de alteração que recebem cookies. Quando a base de dados recusar uma regra conhecida, traduz o resultado para uma resposta útil sem expor SQL, credenciais ou stack traces.

Uma atualização feita em vários modelos pode precisar de DB::transaction(...), mas só envolver código nessa função não resolve a corrida do último lugar. Aplica também a coordenação de transações.

Onde cada falha aparece no percurso

Os caminhos abaixo partem de POST /sessoes num formulário web. Cada recusa interrompe o percurso; o mesmo pedido não atravessa todas as falhas.

CondiçãoCamada que recusaResultado
Sem loginMiddleware authRedirecionamento para iniciar sessão.
Com login, sem permissão de criarPolicy chamada por Gate403, antes da validação e da escrita.
Autorizado, lotação 0Validação min:1Regresso ao formulário com erros e valores preservados.
Autorizado, evento eliminado após validarFK na base de dadosEscrita recusada; a aplicação traduz a falha conhecida sem mostrar SQL.

Num cliente que espera JSON, autenticação e validação podem devolver 401 e 422 em vez de redirecionar. O contrato da resposta precisa de declarar essa diferença.

Validar uma funcionalidade num protótipo vertical

Um protótipo vertical implementa um percurso através de todas as camadas. Uma lista desenhada com dados fixos pode validar a disposição da interface, mas não demonstra que a aplicação consulta, autoriza ou altera os dados. A8 serve para experimentar a arquitetura com funcionalidades do projeto; as histórias obrigatórias vêm dos requisitos e checklists da edição atual.

Para a história de cancelar uma reserva, liga os artefactos ao mesmo comportamento:

ArtefactoDecisão que chega à implementação
A2O comprador cancela uma reserva sua e liberta um lugar uma única vez.
A3O bilhete mostra a ação; sucesso e recusa têm estados reconhecíveis.
A4 e A5Bilhete tem comprador, sessão e estado, com referências válidas.
A6Transição e capacidade são atómicas, com isolamento justificado.
A7Método, caminho, permissões, parâmetros e respostas estão especificados.
A8Interface, controlador, autorização e PostgreSQL executam esse percurso.

Um contrato possível usa POST /bilhetes/{id}/cancelamento. Num formulário web, a ação processa e redireciona para uma página de resultado, preservando os dados necessários em caso de erro. Uma operação AJAX pode devolver JSON e códigos de falha, desde que isso conste do contrato e o cliente os interprete. Não devolvas um redirecionamento para HTML a um cliente que espera sempre o JSON de um bilhete.

Para verificar o percurso, começa com uma reserva de U7 e zero lugares livres. Entra como U7, cancela e confirma na interface e nos dados que o estado mudou e há um lugar livre. Recarrega: a alteração deve persistir. Repete: não deve libertar outro lugar. Com U8, um pedido direto ao mesmo endereço deve ser recusado, mesmo que o botão nem exista na sua página.

Depois verifica uma sessão esgotada, uma falha técnica e o retorno ao formulário. Uma mensagem de sucesso isolada não prova a escrita, e uma consulta bem-sucedida não prova autorização. Este é um percurso didático; não substitui as restantes histórias exigidas para o protótipo nem a verificação do produto no ambiente indicado pela cadeira.

Exercícios

Situar o servidor HTTP

Um pedido sai de Brave, chega a Nginx, executa a aplicação PHP e consulta PostgreSQL. Identifica o papel de cada peça.

Pista

Segue a direção do pedido.

Mais uma pista

Apresentar uma página e responder por HTTP são responsabilidades diferentes.

Solução

Brave é o navegador cliente. Nginx recebe o pedido HTTP. A aplicação PHP executa a lógica e produz a resposta dinâmica; PostgreSQL consulta os dados. Um servidor também pode devolver ficheiros sem executar essa lógica.

Confere a tua resposta:

  • Coloco o navegador no cliente.
  • Identifico Nginx como servidor HTTP.
  • Distingo a aplicação PHP da base de dados.

Erros frequentes

Não confundas o motor de apresentação do navegador com o servidor web.

Voltar à explicação
Ler uma captura por valor

Em PHP, executas

$taxa = 3; $f = fn($n) => $n + $taxa; $taxa = 8; echo $f(10);

. Que número aparece?

Pista

A arrow function captura automaticamente por valor.

Mais uma pista

Observa o valor quando a função foi criada.

Solução

Aparece 13. A captura conservou 3. Alterar depois a variável exterior para 8 não altera essa captura.

Erros frequentes

Não confundas fn com uma closure que declara use (&$taxa).

Voltar à explicação
Identificar a verificação em falta

Uma rota com middleware auth carrega qualquer bilhete por id. Que falta verificar antes de o mostrar?

Pista

Entrar e poder consultar são decisões distintas.

Mais uma pista

O recurso pode pertencer a outra pessoa.

Solução

É necessário verificar uma policy ou regra equivalente sobre o objeto. Autenticação identifica; autorização concede a operação sobre esse recurso.

Confere a tua resposta:

  • Identifico o utilizador autenticado.
  • Verifico autorização para aquele bilhete.
  • Recuso leitura de recurso alheio sem permissão.

Erros frequentes

Não uses o facto de existir um id como permissão de acesso.

Voltar à explicação
Contar consultas por relação

Uma consulta lê 40 sessões e cada acesso ao evento faz outra consulta. Quantas consultas há neste cenário?

Pista

Conta a consulta inicial mais uma por sessão.

Mais uma pista

A relação não está carregada nem reutilizada neste enunciado.

Solução

São 1+40=41. Eager loading pode agrupar o carregamento, segundo o ORM e a relação.

Erros frequentes

Não assumes que navegar num objeto é uma operação sem I/O.

Voltar à explicação
Limitar campos não autoriza pessoas

Colocar estado em fillable prova que qualquer comprador pode alterar o estado de qualquer bilhete?

A tua resposta

Pista

Pergunta que pessoa e que objeto a lista conhece.

Mais uma pista

Uma lista de atributos não expressa pertença.

Solução

Não. Usa autorização sobre o recurso e valida a transição, além de limitar atributos.

  • Não. É uma regra de atribuição em massa.. Permissões e transições de negócio continuam a ser verificadas.
  • Sim, a lista é a policy.. A lista não depende do proprietário do objeto.
  • Sim, se os dados forem inteiros.. O tipo não prova autorização.

Erros frequentes

Não confundas filtragem de dados com autorização.

Voltar à explicação
Definir o contrato de uma resposta

Uma página carrega lugares disponíveis com GET /api/sessoes/{id}. Propõe o contrato do recurso para que o JavaScript distinga uma sessão visível, uma sessão ausente e uma falha técnica. Basta documentar os parâmetros?

Pista

O cliente tem de decidir como ler cada resposta.

Mais uma pista

Inclui códigos HTTP, Content-Type, campos e permissões.

Solução

Não basta documentar parâmetros. Um contrato possível tem id no caminho, 200 com JSON de campos públicos da sessão visível, 404 para ausência ou existência não revelada e 500 sem detalhes internos para uma falha técnica. O cliente verifica o estado antes de usar json(). Pode haver outro contrato válido, desde que tipo, estrutura e tratamento de erros sejam explícitos. A disponibilidade indicada pode mudar antes da reserva.

Confere a tua resposta:

  • Defino método, caminho e parâmetros.
  • Declaro permissões e campos públicos devolvidos.
  • Declaro código, tipo e estrutura do corpo em sucesso e falha.
  • Escolho json() apenas para o corpo JSON definido.

Erros frequentes

Responder 200 com uma página HTML de erro quebra um cliente que espera JSON de uma sessão. Documentar o recurso não dispensa autorização e coordenação no servidor.

Voltar à explicação
Localizar a camada que recusa
Considera quatro pedidos POST /sessoes de um formulário web: sem login; com login mas sem permissão; autorizado com lotação 0; autorizado com evento inexistente. Diz que camada recusa cada um e a resposta. Como muda a resposta se o contrato exigir JSON?

Pista

Segue a ordem do pedido.

Mais uma pista

Auth, policy, validação e base.

Solução
No formulário web, auth redireciona quem não entrou para o login. Uma pessoa autenticada sem permissão recebe 403 da policy. Para uma pessoa autorizada, lotação 0 falha na validação e volta ao formulário com erros e valores preservados. Um evento já ausente falha em exists; se for eliminado depois da validação, a FK ainda recusa a inserção. A aplicação traduz a violação conhecida sem mostrar SQL. Num contrato JSON, autenticação e validação podem devolver 401 e 422 em vez de redirecionamentos.

Confere a tua resposta:

  • Distingo auth, policy, validação e restrição.
  • Prevejo código e resposta.
  • Não exponho detalhes internos.

Erros frequentes

Não confies só na validação para concorrência.

Voltar à explicação
Distinguir uma demonstração de interface de um protótipo vertical

O grupo mostra uma página de bilhetes com dados fixos. Clicar em Cancelar muda o texto para cancelado e mostra sucesso, mas não envia qualquer pedido. A história diz que o comprador cancela uma reserva sua e liberta um lugar uma única vez. Que parte da arquitetura foi demonstrada? Descreve o percurso e três verificações que faltam para demonstrar esta história num protótipo vertical.

Pista

Segue o efeito para além do DOM, até aos dados guardados.

Solução

A demonstração exercita um estado da interface, mas não o contrato HTTP, a autorização, a regra de negócio nem a persistência. Um percurso completo envia o pedido documentado, autentica, verifica o proprietário, executa a transição e a libertação de capacidade atomicamente em PostgreSQL e interpreta a resposta na interface.

  1. Após sucesso, recarregar e consultar o estado guardado confirma bilhete cancelado e um lugar libertado. Dados fixos voltariam ao estado inicial.
  2. Repetir o pedido conserva o cancelamento e não liberta outro lugar. Uma mensagem de sucesso não basta para demonstrar essa invariante.
  3. Um comprador diferente envia diretamente o mesmo pedido: o servidor recusa e os dados mantêm-se, independentemente da visibilidade do botão.

A2 fixa a história; A3 os estados da interface; A4–A6 os dados e garantias; A7 o contrato; A8 integra esse percurso. O protótipo tem de satisfazer também as restantes histórias exigidas na edição atual.

Confere a tua resposta:

  • Mostro que dados fixos e mensagem de sucesso não provam persistência.
  • Ligo história, estado da interface, contrato, autorização e transação.
  • Verifico recarregamento, repetição e pedido direto de outra pessoa.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.