PHP Projeto Calculadora Web

O primeiro projeto é uma calculadora que funciona no navegador: dois campos para os números, uma lista com as quatro operações e um botão Calcular. É pequeno, mas tem tudo o que um sistema web de verdade tem: um formulário, o PHP lendo o que chegou, a decisão sobre o que fazer, a checagem do que o usuário digitou e uma resposta bem apresentada.

Você vai aprender
  • Montar uma página que mostra um formulário e também processa o envio dele (o mesmo arquivo faz as duas coisas).
  • Saber se a página foi aberta normalmente (GET) ou enviada pelo botão (POST).
  • Escolher a conta com match e entender o erro UnhandledMatchError.
  • Validar números com is_numeric, aceitar vírgula e impedir a divisão por zero.
  • Mostrar o resultado no jeito brasileiro (1.234,50) com number_format.
  • Manter o que o usuário digitou nos campos depois de enviar, sem abrir brecha de segurança.

Antes de começar

Este projeto junta aulas dos níveis 1 e 2. Se algum termo parecer novo, a aula do assunto está ao lado:

O que usamosPara quêAula
<form method="post">O formulário com os camposFormulários HTML
$_POST, $_SERVER['REQUEST_METHOD']Ler o que foi enviado$_GET e $_POST
??Valor padrão quando o campo não veio?? e ?->
matchEscolher a conta pela operaçãomatch
is_numeric, trimValidar o que foi digitadoValidar Dados
htmlspecialcharsMostrar texto do usuário com segurançaEscapar a Saída (XSS)
number_formatFormatar 2469 como 2.469,00Formatar Números
<?= ?>, if: … endif;Misturar PHP e HTMLPHP Dentro do HTML

O projeto inteiro é um arquivo só, o index.php. Em cada etapa ele cresce um pouco. Você pode seguir de dois jeitos:

  • No editor do guia: cada etapa tem uma caixa "Experimente" com o index.php daquela etapa, no modo web: a aba Resultado mostra a página como no navegador, e o formulário funciona de verdade.
  • No seu computador: crie a pasta, salve o arquivo e suba o servidor embutido do PHP (veja O Servidor Embutido):
$ mkdir calculadora
$ cd calculadora
$ nano index.php                       « cole o código da etapa e salve
$ php -S localhost:8000
[Fri Oct 16 20:44:15 2026] PHP 8.5.8 Development Server (http://localhost:8000) started

Deixe esse terminal aberto (ele é o servidor) e abra http://localhost:8000 no navegador. Para parar o servidor, volte a ele e aperte Ctrl+C. Neste guia, as saídas foram conferidas com o curl num segundo terminal: curl -s localhost:8000/ busca a página como o navegador faria, e curl -s -d 'a=10&b=4&op=dividir' localhost:8000/ simula um clique em Calcular com esses valores (o -d faz um POST).


As etapas

  1. Etapa 1 · O formulário

    Primeiro, a "cara" da calculadora. Por enquanto é quase só HTML: o PHP aparece em um lugar, para provar que o arquivo está passando pelo PHP.

    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <p>Hoje é <?= date('d/m/Y') ?>.</p>
    </body>
    </html>
    
    TrechoO que faz
    <form method="post">Quando o botão for clicado, o navegador envia os campos com o método POST (eles vão "dentro" da requisição, não na barra de endereço). Sem action, o formulário é enviado para a mesma página: o próprio index.php vai receber os dados.
    name="a", name="b"O name é o nome com que o campo chega ao PHP. Sem name, o campo nem é enviado.
    <select name="op">A lista de operações. O que chega ao PHP é o value da opção escolhida (somar, dividir…), não o símbolo que aparece na tela.
    <button>Calcular</button>Um botão dentro de um formulário envia o formulário.
    <?= date('d/m/Y') ?>O único PHP da etapa: escreve a data de hoje. Se você vir 16/10/2026 (ou a sua data), o PHP está funcionando; se vir o código <?= na página, você abriu o arquivo direto, sem servidor.
    Por que value="dividir" e não value="/"?

    Símbolos como + e / têm significado especial em endereços e formulários (o +, por exemplo, vira espaço). Palavras simples chegam sempre iguais. O símbolo bonito fica só no texto da opção.

    Com o servidor rodando, o curl mostra o final da página que o navegador recebe:

    $ curl -s localhost:8000/ | tail -5
      <button>Calcular</button>
    </form>
    <p>Hoje é 16/10/2026.</p>          « o PHP trocou o date('d/m/Y') pela data
    </body>
    </html>

    Clicar em Calcular agora não faz nada visível: o formulário é enviado, mas o PHP ainda não lê os campos. A página volta igual (e vazia, porque o navegador não "lembra" o que estava nos campos). Isso muda nas próximas etapas.

    Missão: acrescente uma quinta opção na lista, com o valor potencia e o texto ^, logo depois da divisão. Rode e abra a lista na aba Resultado.

    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <p>Hoje é <?= date('d/m/Y') ?>.</p>
    </body>
    </html>
    
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
        <option value="potencia">^</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <p>Hoje é <?= date('d/m/Y') ?>.</p>
    </body>
    </html>
    
  2. Etapa 2 · Ler o que foi enviado

    Agora o PHP vai pegar os três campos. A parte nova fica no topo do arquivo, antes de qualquer HTML: primeiro o PHP prepara os dados, depois o HTML mostra. Essa ordem vai acompanhar você em todos os projetos.

    O começo do arquivo (novo):

    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    ?>
    <!DOCTYPE html>
    ...

    E, logo depois do </form> (novo):

    <?php if ($_SERVER['REQUEST_METHOD'] === 'POST'): ?>
      <p>Recebi: a = "<?= htmlspecialchars($a) ?>", b = "<?= htmlspecialchars($b) ?>", operação = "<?= htmlspecialchars($op) ?>"</p>
    <?php endif; ?>

    O arquivo inteiro desta etapa está na caixa "Experimente" logo abaixo.

    $a
    A variável que vai guardar o primeiro número.
    =
    Recebe o valor da direita.
    $_POST['a']
    O campo name="a" enviado por POST. $_POST é um array associativo que o PHP enche sozinho a cada envio.
    ??
    "Se não existir, use…". Quando a página é aberta pela primeira vez, ninguém enviou nada e $_POST['a'] não existe.
    ''
    O valor padrão: texto vazio. Para a operação, o padrão é 'somar'.

    No fim da página, um if no estilo "dois-pontos" (if (…): … endif;, mais fácil de ler no meio do HTML) só mostra a linha "Recebi" quando a página chegou por POST, isto é, quando o botão foi clicado. $_SERVER['REQUEST_METHOD'] diz qual foi o método: GET quando você digita o endereço ou segue um link, POST quando um formulário method="post" é enviado.

    Sempre htmlspecialchars ao mostrar o que o usuário digitou

    Os valores vêm de fora: qualquer pessoa pode digitar <script> no campo. O htmlspecialchars troca <, >, & e as aspas pelas versões "inofensivas" (&lt;…), e o navegador mostra o texto em vez de executá-lo. A aula Escapar a Saída (XSS) mostra o ataque que isso evita.

    $ curl -s -d 'a=10&b=4&op=dividir' localhost:8000/ | tail -3
      <p>Recebi: a = "10", b = "4", operação = "dividir"</p>
    </body>
    </html>
    $ curl -s -d 'a=abc&b=2&op=somar' localhost:8000/ | tail -3
      <p>Recebi: a = "abc", b = "2", operação = "somar"</p>      « o PHP aceita qualquer texto
    </body>
    </html>

    Repare: tudo chega como texto (string), até o 10. Um formulário não tem "campo de número" para o PHP; quem decide se aquilo é um número é você, e é isso que a etapa 4 faz.

    Missão: a caixa já começa com um envio (10, 4, dividir). Acrescente, logo abaixo da linha "Recebi", um parágrafo que mostre Método: seguido de $_SERVER['REQUEST_METHOD']. Depois preencha o formulário na aba Resultado e envie de novo.

    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php if ($_SERVER['REQUEST_METHOD'] === 'POST'): ?>
      <p>Recebi: a = "<?= htmlspecialchars($a) ?>", b = "<?= htmlspecialchars($b) ?>", operação = "<?= htmlspecialchars($op) ?>"</p>
    <?php endif; ?>
    </body>
    </html>
    
    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php if ($_SERVER['REQUEST_METHOD'] === 'POST'): ?>
      <p>Recebi: a = "<?= htmlspecialchars($a) ?>", b = "<?= htmlspecialchars($b) ?>", operação = "<?= htmlspecialchars($op) ?>"</p>
      <p>Método: <?= $_SERVER['REQUEST_METHOD'] ?></p>
    <?php endif; ?>
    </body>
    </html>
    
  3. Etapa 3 · Fazer a conta com match

    Hora de calcular. Os textos viram números com (float) e o match escolhe a conta pela operação:

    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
        };
    }
    ?>
    ...
    <?php if ($resultado !== null): ?>
      <p class="resultado">Resultado: <?= $resultado ?></p>
    <?php endif; ?>

    A linha "Recebi" da etapa 2 saiu: no lugar dela, o resultado. Ele só aparece quando existe (!== null).

    $resultado =
    O valor que o match devolver vai para $resultado. Antes do envio, ele fica null ("ainda não calculei").
    match ($op)
    Compara $op com cada "braço", usando === (valor e tipo iguais).
    'somar' => $x + $y,
    Um braço: se $op for 'somar', o resultado é $x + $y.
    };
    O match é uma expressão: devolve um valor, por isso termina com ; depois da chave.

    Por que match e não switch? Porque aqui queremos um valor para cada operação, sem precisar de break e sem o risco de "cair" no caso de baixo. A comparação entre os dois está na aula match.

    $ curl -s -d 'a=10&b=4&op=dividir' localhost:8000/ | tail -3
      <p class="resultado">Resultado: 2.5</p>
    </body>
    </html>

    Funciona! Mas esta versão é ingênua: ela confia no usuário. Veja o que acontece com três envios "maldosos":

    $ curl -s -d 'a=abc&b=2&op=somar' localhost:8000/ | grep resultado
      <p class="resultado">Resultado: 2</p>                « "abc" virou 0 em silêncio!
    $ curl -s -d 'a=7&b=0&op=dividir' localhost:8000/ | tail -6
    <br />
    <b>Fatal error</b>:  Uncaught DivisionByZeroError: Division by zero in /home/ana/calculadora/index.php:14
    Stack trace:
    #0 {main}
      thrown in <b>/home/ana/calculadora/index.php</b> on line <b>14</b><br />
    $ curl -s -d 'a=7&b=2&op=resto' localhost:8000/ | tail -5
    <br />
    <b>Fatal error</b>:  Uncaught UnhandledMatchError: Unhandled match case 'resto' in /home/ana/calculadora/index.php:10
    Stack trace:
    #0 {main}
      thrown in <b>/home/ana/calculadora/index.php</b> on line <b>10</b><br />
    
    • (float) 'abc' é 0: a conversão forçada não reclama, só devolve zero. A calculadora deu uma resposta errada sem avisar ninguém. Esse é o pior tipo de erro.
    • DivisionByZeroError: Division by zero (erro de divisão por zero): desde o PHP 8, dividir por zero com / para o programa. A página morre no meio, e o usuário vê uma mensagem técnica.
    • UnhandledMatchError: Unhandled match case 'resto' (caso não tratado no match): o match não tinha braço para 'resto'. "Mas a lista só tem quatro opções!" Na tela, sim; mas qualquer pessoa pode enviar o que quiser, como fizemos com o curl. Nunca confie que o valor veio da sua lista.

    No navegador, a mensagem aparece com <b> porque o servidor embutido mostra os erros em HTML; no terminal (php index.php) ela sai em texto puro. As mensagens completas estão em Lendo Erros do PHP.

    Missão: a caixa começa enviando op=potencia, que ainda não existe: rode e veja o UnhandledMatchError. Depois acrescente o braço 'potencia' => $x ** $y, no match (e a opção potencia na lista). Rodando de novo, 2 elevado a 10 deve dar 1024.

    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
        };
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php if ($resultado !== null): ?>
      <p class="resultado">Resultado: <?= $resultado ?></p>
    <?php endif; ?>
    </body>
    </html>
    
    <?php
    $a  = $_POST['a']  ?? '';
    $b  = $_POST['b']  ?? '';
    $op = $_POST['op'] ?? 'somar';
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
            'potencia'    => $x ** $y,
        };
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
        <option value="potencia">^</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php if ($resultado !== null): ?>
      <p class="resultado">Resultado: <?= $resultado ?></p>
    <?php endif; ?>
    </body>
    </html>
    
  4. Etapa 4 · Validar antes de calcular

    A regra de ouro de todo formulário: primeiro confira, depois use. Vamos guardar os problemas encontrados numa lista $erros e só calcular se ela terminar vazia.

    O bloco do topo fica assim:

    <?php
    $a  = trim($_POST['a'] ?? '');
    $b  = trim($_POST['b'] ?? '');
    $op = $_POST['op'] ?? 'somar';
    $erros = [];
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // aceita vírgula como separador decimal: "2,5" vira "2.5"
        $a = str_replace(',', '.', $a);
        $b = str_replace(',', '.', $b);
    
        if (!is_numeric($a)) {
            $erros[] = 'O primeiro número não é válido.';
        }
        if (!is_numeric($b)) {
            $erros[] = 'O segundo número não é válido.';
        }
        if (!in_array($op, ['somar', 'subtrair', 'multiplicar', 'dividir'], true)) {
            $erros[] = 'Operação desconhecida.';
        }
        if (!$erros && $op === 'dividir' && (float) $b == 0) {
            $erros[] = 'Não dá para dividir por zero.';
        }
    
        if (!$erros) {
            // ... o mesmo match da etapa 3 ...
        }
    }
    ?>

    E, antes do resultado, as mensagens de erro:

    <?php foreach ($erros as $erro): ?>
      <p class="erro"><?= htmlspecialchars($erro) ?></p>
    <?php endforeach; ?>
    LinhaO que faz
    trim($_POST['a'] ?? '')Tira espaços das pontas: " 10 " vira "10". Quem cola um número de outro lugar costuma trazer espaços junto.
    $erros = [];Começa a lista de problemas vazia.
    str_replace(',', '.', $a)No Brasil, escrevemos 2,5; o PHP só entende 2.5. Trocamos a vírgula pelo ponto antes de validar.
    !is_numeric($a)is_numeric ("é numérico?") responde true para textos como "10", "2.5", "-8" e "1e3", e false para "abc", "" e "2,5". O ! inverte: "se não for número…".
    $erros[] = '…';Acrescenta uma mensagem no fim da lista.
    in_array($op, [...], true)A operação está na lista das permitidas? O true faz a comparação estrita (tipo e valor).
    !$erros && $op === 'dividir' && (float) $b == 0Só se os números já estiverem bons: dividir por zero vira uma mensagem amigável, não um Fatal error. Uma lista vazia vale false, então !$erros quer dizer "nenhum erro até agora".
    foreach ($erros as $erro)Mostra cada mensagem num parágrafo (com htmlspecialchars, por hábito: amanhã a mensagem pode incluir algo que o usuário digitou).
    $ curl -s -d 'a=abc&b=2&op=somar' localhost:8000/ | tail -4
    </form>
      <p class="erro">O primeiro número não é válido.</p>
    </body>
    </html>
    $ curl -s -d 'a=7&b=0&op=dividir' localhost:8000/ | grep -E 'resultado|erro'
      <p class="erro">Não dá para dividir por zero.</p>
    $ curl -s -d 'a=7&b=2&op=resto' localhost:8000/ | grep -E 'resultado|erro'
      <p class="erro">Operação desconhecida.</p>
    $ curl -s -d 'a=&b=&op=somar' localhost:8000/ | grep -E 'resultado|erro'
      <p class="erro">O primeiro número não é válido.</p>
      <p class="erro">O segundo número não é válido.</p>      « os dois problemas de uma vez
    $ curl -s -d 'a=10&b=4&op=dividir' localhost:8000/ | tail -3
      <p class="resultado">Resultado: 2.5</p>
    </body>
    </html>

    Os três envios que derrubavam ou enganavam a calculadora agora recebem uma resposta clara. Juntar todos os erros numa lista, em vez de parar no primeiro, é mais gentil com o usuário: ele corrige tudo de uma vez. Mais formas de validar estão em Validar Dados.

    Missão: "não é válido" é uma mensagem estranha para um campo que ficou vazio. Mude a validação: se $a === '', a mensagem deve ser Preencha o primeiro número.; senão, se não for numérico, continua "não é válido" (use if … elseif). Faça o mesmo para o $b.

    <?php
    $a  = trim($_POST['a'] ?? '');
    $b  = trim($_POST['b'] ?? '');
    $op = $_POST['op'] ?? 'somar';
    $erros = [];
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // aceita vírgula como separador decimal: "2,5" vira "2.5"
        $a = str_replace(',', '.', $a);
        $b = str_replace(',', '.', $b);
    
        if (!is_numeric($a)) {
            $erros[] = 'O primeiro número não é válido.';
        }
        if (!is_numeric($b)) {
            $erros[] = 'O segundo número não é válido.';
        }
        if (!in_array($op, ['somar', 'subtrair', 'multiplicar', 'dividir'], true)) {
            $erros[] = 'Operação desconhecida.';
        }
        if (!$erros && $op === 'dividir' && (float) $b == 0) {
            $erros[] = 'Não dá para dividir por zero.';
        }
    
        if (!$erros) {
            $x = (float) $a;
            $y = (float) $b;
            $resultado = match ($op) {
                'somar'       => $x + $y,
                'subtrair'    => $x - $y,
                'multiplicar' => $x * $y,
                'dividir'     => $x / $y,
            };
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php foreach ($erros as $erro): ?>
      <p class="erro"><?= htmlspecialchars($erro) ?></p>
    <?php endforeach; ?>
    <?php if ($resultado !== null): ?>
      <p class="resultado">Resultado: <?= $resultado ?></p>
    <?php endif; ?>
    </body>
    </html>
    
    <?php
    $a  = trim($_POST['a'] ?? '');
    $b  = trim($_POST['b'] ?? '');
    $op = $_POST['op'] ?? 'somar';
    $erros = [];
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // aceita vírgula como separador decimal: "2,5" vira "2.5"
        $a = str_replace(',', '.', $a);
        $b = str_replace(',', '.', $b);
    
        if ($a === '') {
            $erros[] = 'Preencha o primeiro número.';
        } elseif (!is_numeric($a)) {
            $erros[] = 'O primeiro número não é válido.';
        }
        if ($b === '') {
            $erros[] = 'Preencha o segundo número.';
        } elseif (!is_numeric($b)) {
            $erros[] = 'O segundo número não é válido.';
        }
        if (!in_array($op, ['somar', 'subtrair', 'multiplicar', 'dividir'], true)) {
            $erros[] = 'Operação desconhecida.';
        }
        if (!$erros && $op === 'dividir' && (float) $b == 0) {
            $erros[] = 'Não dá para dividir por zero.';
        }
    
        if (!$erros) {
            $x = (float) $a;
            $y = (float) $b;
            $resultado = match ($op) {
                'somar'       => $x + $y,
                'subtrair'    => $x - $y,
                'multiplicar' => $x * $y,
                'dividir'     => $x / $y,
            };
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" placeholder="Primeiro número">
      <select name="op">
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar">×</option>
        <option value="dividir">÷</option>
      </select>
      <input name="b" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php foreach ($erros as $erro): ?>
      <p class="erro"><?= htmlspecialchars($erro) ?></p>
    <?php endforeach; ?>
    <?php if ($resultado !== null): ?>
      <p class="resultado">Resultado: <?= $resultado ?></p>
    <?php endif; ?>
    </body>
    </html>
    
  5. Etapa 5 · Resultado formatado e campos que lembram

    Dois acabamentos que fazem diferença para quem usa: o resultado no formato brasileiro, e os campos que continuam preenchidos depois do envio (ninguém gosta de redigitar tudo para trocar só a operação).

    No topo, a lista de operações vira um array e aparece uma função de formatação:

    <?php
    // Calculadora da Ana — versão final
    $operacoes = [
        'somar'       => '+',
        'subtrair'    => '−',
        'multiplicar' => '×',
        'dividir'     => '÷',
    ];
    // ... leitura, validação e match como na etapa 4, mas a operação
    //     agora é validada com: !array_key_exists($op, $operacoes)
    
    function br(float $n): string
    {
        return number_format($n, 2, ',', '.');
    }
    ?>

    No HTML, os campos ganham value, as opções são geradas pelo array e o resultado mostra a conta inteira:

    <form method="post">
      <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
      <select name="op">
    <?php foreach ($operacoes as $valor => $simbolo): ?>
        <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
    <?php endforeach; ?>
      </select>
      <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    ...
    <?php if ($resultado !== null): ?>
      <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
    <?php endif; ?>
    NovidadeO que faz
    $operacoes = ['somar' => '+', …]Um array associativo com as operações: a chave é o valor enviado, o valor é o símbolo. Ele serve para três coisas: montar a lista do formulário, validar (array_key_exists) e mostrar o símbolo no resultado. Uma operação nova entra num lugar só (mais o braço do match).
    function br(float $n): stringUma função pequena para não repetir number_format($n, 2, ',', '.') três vezes: 2 casas decimais, vírgula nos decimais, ponto nos milhares (veja Funções e Formatar Números).
    value="<?= htmlspecialchars($a) ?>"Põe de volta no campo o que foi digitado. Aqui o htmlspecialchars é obrigatório: uma aspa " no texto fecharia o atributo value e deixaria o usuário escrever HTML na sua página.
    foreach ($operacoes as $valor => $simbolo)Gera as quatro <option> a partir do array.
    $valor === $op ? ' selected' : ''Marca como selected (selecionada) a opção que foi enviada. O operador ternário está em Operador Ternário.
    $ curl -s -d 'a=10&b=3&op=dividir' localhost:8000/ | grep -E 'resultado|erro'
      <p class="resultado">10,00 ÷ 3,00 = <strong>3,33</strong></p>
    $ curl -s -d 'a=1000000&b=3&op=multiplicar' localhost:8000/ | grep -E 'resultado|erro'
      <p class="resultado">1.000.000,00 × 3,00 = <strong>3.000.000,00</strong></p>
    $ curl -s -d 'a=2,5&b=3&op=multiplicar' localhost:8000/ | grep -E 'resultado|erro|value'
      <input name="a" value="2.5" placeholder="Primeiro número">      « o campo voltou preenchido
        <option value="somar">+</option>
        <option value="subtrair">−</option>
        <option value="multiplicar" selected>×</option>                « a operação também
        <option value="dividir">÷</option>
      <input name="b" value="3" placeholder="Segundo número">
      <p class="resultado">2,50 × 3,00 = <strong>7,50</strong></p>

    Repare que o campo voltou com 2.5 (com ponto), porque a variável $a já tinha passado pelo str_replace. Um dos desafios do fim pede para devolver o texto exatamente como foi digitado.

    E a prova de que o htmlspecialchars no value funciona: um envio com aspas e tags aparece "desarmado":

    $ curl -s -d 'a="><b>oi</b>&b=2&op=somar' localhost:8000/ | grep 'name="a"'
      <input name="a" value="&quot;&gt;&lt;b&gt;oi&lt;/b&gt;" placeholder="Primeiro número">

    Sem ele, o "> fecharia o campo e o <b>oi</b> viraria HTML de verdade na sua página. Com ele, o navegador mostra no campo exatamente o que foi digitado.

    Missão: abaixo do resultado, mostre também o valor arredondado para inteiro, no formato (arredondado: 2.469). Dica: number_format($resultado, 0, ',', '.'), com zero casas decimais.

    <?php
    // Calculadora da Ana — versão final
    $operacoes = [
        'somar'       => '+',
        'subtrair'    => '−',
        'multiplicar' => '×',
        'dividir'     => '÷',
    ];
    
    $a  = trim($_POST['a'] ?? '');
    $b  = trim($_POST['b'] ?? '');
    $op = $_POST['op'] ?? 'somar';
    $erros = [];
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $a = str_replace(',', '.', $a);
        $b = str_replace(',', '.', $b);
    
        if (!is_numeric($a)) {
            $erros[] = 'O primeiro número não é válido.';
        }
        if (!is_numeric($b)) {
            $erros[] = 'O segundo número não é válido.';
        }
        if (!array_key_exists($op, $operacoes)) {
            $erros[] = 'Operação desconhecida.';
        }
        if (!$erros && $op === 'dividir' && (float) $b == 0) {
            $erros[] = 'Não dá para dividir por zero.';
        }
    
        if (!$erros) {
            $x = (float) $a;
            $y = (float) $b;
            $resultado = match ($op) {
                'somar'       => $x + $y,
                'subtrair'    => $x - $y,
                'multiplicar' => $x * $y,
                'dividir'     => $x / $y,
            };
        }
    }
    
    function br(float $n): string
    {
        return number_format($n, 2, ',', '.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
      <select name="op">
    <?php foreach ($operacoes as $valor => $simbolo): ?>
        <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
    <?php endforeach; ?>
      </select>
      <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php foreach ($erros as $erro): ?>
      <p class="erro"><?= htmlspecialchars($erro) ?></p>
    <?php endforeach; ?>
    <?php if ($resultado !== null): ?>
      <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
    <?php endif; ?>
    </body>
    </html>
    
    <?php
    // Calculadora da Ana — versão final
    $operacoes = [
        'somar'       => '+',
        'subtrair'    => '−',
        'multiplicar' => '×',
        'dividir'     => '÷',
    ];
    
    $a  = trim($_POST['a'] ?? '');
    $b  = trim($_POST['b'] ?? '');
    $op = $_POST['op'] ?? 'somar';
    $erros = [];
    $resultado = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $a = str_replace(',', '.', $a);
        $b = str_replace(',', '.', $b);
    
        if (!is_numeric($a)) {
            $erros[] = 'O primeiro número não é válido.';
        }
        if (!is_numeric($b)) {
            $erros[] = 'O segundo número não é válido.';
        }
        if (!array_key_exists($op, $operacoes)) {
            $erros[] = 'Operação desconhecida.';
        }
        if (!$erros && $op === 'dividir' && (float) $b == 0) {
            $erros[] = 'Não dá para dividir por zero.';
        }
    
        if (!$erros) {
            $x = (float) $a;
            $y = (float) $b;
            $resultado = match ($op) {
                'somar'       => $x + $y,
                'subtrair'    => $x - $y,
                'multiplicar' => $x * $y,
                'dividir'     => $x / $y,
            };
        }
    }
    
    function br(float $n): string
    {
        return number_format($n, 2, ',', '.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
    <meta charset="utf-8">
    <title>Calculadora</title>
    </head>
    <body>
    <h1>Calculadora da Ana</h1>
    <form method="post">
      <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
      <select name="op">
    <?php foreach ($operacoes as $valor => $simbolo): ?>
        <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
    <?php endforeach; ?>
      </select>
      <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
      <button>Calcular</button>
    </form>
    <?php foreach ($erros as $erro): ?>
      <p class="erro"><?= htmlspecialchars($erro) ?></p>
    <?php endforeach; ?>
    <?php if ($resultado !== null): ?>
      <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
      <p>(arredondado: <?= number_format($resultado, 0, ',', '.') ?>)</p>
    <?php endif; ?>
    </body>
    </html>
    

Quando dá errado

Warning: Undefined array key "a"

Você escreveu $a = $_POST['a']; sem o ?? e abriu a página pela primeira vez (um GET, sem formulário enviado):

$ curl -s localhost:8000/
<br />
<b>Warning</b>:  Undefined array key "a" in <b>/home/ana/calculadora/index.php</b> on line <b>2</b><br />
<br />
<b>Warning</b>:  Undefined array key "b" in <b>/home/ana/calculadora/index.php</b> on line <b>3</b><br />
Recebi  e 

O que significa: "chave de array indefinida": não existe 'a' dentro de $_POST, porque nada foi enviado. Como consertar: $_POST['a'] ?? '', que dá um valor padrão quando a chave não existe.

Fatal error: Uncaught DivisionByZeroError: Division by zero

Apareceu ao dividir por zero (etapa 3). Como consertar: confira o divisor antes da conta e transforme o caso numa mensagem para o usuário, como na etapa 4. O resto da divisão com % tem o seu próprio erro: DivisionByZeroError: Modulo by zero.

Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'resto'

O match recebeu um valor que não tem braço. Como consertar: valide a operação antes (lista de permitidas) ou acrescente um braço default => …. Na calculadora preferimos validar, porque assim o usuário recebe "Operação desconhecida." em vez de um resultado inventado.

O resultado sai errado, sem erro nenhum

Por exemplo, abc + 2 dá 2. É o (float) transformando texto inválido em 0. Como consertar: is_numeric antes de converter. E se em vez de (float) você fizer a conta direto com os textos, o PHP 8.5 reclama de verdade: "abc" + 1 dá Fatal error: Uncaught TypeError: Unsupported operand types: string + int, e "5 maçãs" + 1 dá Warning: A non-numeric value encountered e o resultado 6 (veja Conversão de Tipos).

A página aparece com o código <?php à mostra

Você abriu o index.php com dois cliques (endereço file:///…). O navegador não sabe rodar PHP. Como consertar: suba o php -S localhost:8000 na pasta e abra http://localhost:8000.


O código completo

A versão final, com tudo das cinco etapas. São cerca de 70 linhas.

<?php
// Calculadora da Ana — versão final
$operacoes = [
    'somar'       => '+',
    'subtrair'    => '−',
    'multiplicar' => '×',
    'dividir'     => '÷',
];

$a  = trim($_POST['a'] ?? '');
$b  = trim($_POST['b'] ?? '');
$op = $_POST['op'] ?? 'somar';
$erros = [];
$resultado = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $a = str_replace(',', '.', $a);
    $b = str_replace(',', '.', $b);

    if (!is_numeric($a)) {
        $erros[] = 'O primeiro número não é válido.';
    }
    if (!is_numeric($b)) {
        $erros[] = 'O segundo número não é válido.';
    }
    if (!array_key_exists($op, $operacoes)) {
        $erros[] = 'Operação desconhecida.';
    }
    if (!$erros && $op === 'dividir' && (float) $b == 0) {
        $erros[] = 'Não dá para dividir por zero.';
    }

    if (!$erros) {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
        };
    }
}

function br(float $n): string
{
    return number_format($n, 2, ',', '.');
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Calculadora</title>
</head>
<body>
<h1>Calculadora da Ana</h1>
<form method="post">
  <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
  <select name="op">
<?php foreach ($operacoes as $valor => $simbolo): ?>
    <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
<?php endforeach; ?>
  </select>
  <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
  <button>Calcular</button>
</form>
<?php foreach ($erros as $erro): ?>
  <p class="erro"><?= htmlspecialchars($erro) ?></p>
<?php endforeach; ?>
<?php if ($resultado !== null): ?>
  <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
<?php endif; ?>
</body>
</html>

Desafios extras

Sem resposta pronta; use a caixa livre abaixo ou o seu computador.

  1. Devolver o que foi digitado: guarde o texto original em $aTexto antes do str_replace e use-o no value, para o campo voltar com 2,5 e não 2.5.
  2. Mais operações: potência (**) e resto (fmod($x, $y), que funciona com decimais). Não esqueça de proteger o resto contra zero também.
  3. Casas decimais à escolha: um terceiro campo (um <select name="casas"> com 0, 2 e 4) que decide quantas casas o br() usa. Valide com in_array.
  4. Números que aparecem como digitados: mostre 10 ÷ 4 = 2,50 (sem ,00 nos números inteiros). Dica: $n == (int) $n.
  5. Histórico: guarde as últimas cinco contas numa sessão e mostre-as abaixo do formulário (veja Sessões; o projeto de login usa sessão do começo ao fim).

Missão final: acrescente a operação porcentagem: "a % de b". A chave é porcentagem, o símbolo é % de e a conta é $x * $y / 100. A caixa já envia "15 % de 200"; a resposta deve ser 15,00 % de 200,00 = 30,00. Repare em quantos lugares você precisou mexer (spoiler: dois).

<?php
// Calculadora da Ana — versão final
$operacoes = [
    'somar'       => '+',
    'subtrair'    => '−',
    'multiplicar' => '×',
    'dividir'     => '÷',
];

$a  = trim($_POST['a'] ?? '');
$b  = trim($_POST['b'] ?? '');
$op = $_POST['op'] ?? 'somar';
$erros = [];
$resultado = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $a = str_replace(',', '.', $a);
    $b = str_replace(',', '.', $b);

    if (!is_numeric($a)) {
        $erros[] = 'O primeiro número não é válido.';
    }
    if (!is_numeric($b)) {
        $erros[] = 'O segundo número não é válido.';
    }
    if (!array_key_exists($op, $operacoes)) {
        $erros[] = 'Operação desconhecida.';
    }
    if (!$erros && $op === 'dividir' && (float) $b == 0) {
        $erros[] = 'Não dá para dividir por zero.';
    }

    if (!$erros) {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
        };
    }
}

function br(float $n): string
{
    return number_format($n, 2, ',', '.');
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Calculadora</title>
</head>
<body>
<h1>Calculadora da Ana</h1>
<form method="post">
  <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
  <select name="op">
<?php foreach ($operacoes as $valor => $simbolo): ?>
    <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
<?php endforeach; ?>
  </select>
  <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
  <button>Calcular</button>
</form>
<?php foreach ($erros as $erro): ?>
  <p class="erro"><?= htmlspecialchars($erro) ?></p>
<?php endforeach; ?>
<?php if ($resultado !== null): ?>
  <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
<?php endif; ?>
</body>
</html>
<?php
// Calculadora da Ana — versão final
$operacoes = [
    'somar'       => '+',
    'subtrair'    => '−',
    'multiplicar' => '×',
    'dividir'     => '÷',
    'porcentagem' => '% de',
];

$a  = trim($_POST['a'] ?? '');
$b  = trim($_POST['b'] ?? '');
$op = $_POST['op'] ?? 'somar';
$erros = [];
$resultado = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $a = str_replace(',', '.', $a);
    $b = str_replace(',', '.', $b);

    if (!is_numeric($a)) {
        $erros[] = 'O primeiro número não é válido.';
    }
    if (!is_numeric($b)) {
        $erros[] = 'O segundo número não é válido.';
    }
    if (!array_key_exists($op, $operacoes)) {
        $erros[] = 'Operação desconhecida.';
    }
    if (!$erros && $op === 'dividir' && (float) $b == 0) {
        $erros[] = 'Não dá para dividir por zero.';
    }

    if (!$erros) {
        $x = (float) $a;
        $y = (float) $b;
        $resultado = match ($op) {
            'somar'       => $x + $y,
            'subtrair'    => $x - $y,
            'multiplicar' => $x * $y,
            'dividir'     => $x / $y,
        'porcentagem' => $x * $y / 100,
        };
    }
}

function br(float $n): string
{
    return number_format($n, 2, ',', '.');
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Calculadora</title>
</head>
<body>
<h1>Calculadora da Ana</h1>
<form method="post">
  <input name="a" value="<?= htmlspecialchars($a) ?>" placeholder="Primeiro número">
  <select name="op">
<?php foreach ($operacoes as $valor => $simbolo): ?>
    <option value="<?= $valor ?>"<?= $valor === $op ? ' selected' : '' ?>><?= $simbolo ?></option>
<?php endforeach; ?>
  </select>
  <input name="b" value="<?= htmlspecialchars($b) ?>" placeholder="Segundo número">
  <button>Calcular</button>
</form>
<?php foreach ($erros as $erro): ?>
  <p class="erro"><?= htmlspecialchars($erro) ?></p>
<?php endforeach; ?>
<?php if ($resultado !== null): ?>
  <p class="resultado"><?= br((float) $a) ?> <?= $operacoes[$op] ?> <?= br((float) $b) ?> = <strong><?= br($resultado) ?></strong></p>
<?php endif; ?>
</body>
</html>

Exercícios

Complete para ler o campo op com o valor padrão 'somar' quando ele não vier:

$op = $_POST['op'] 'somar';

Complete para formatar 2469 como 2.469,00:

number_format(2469, , , )

Um usuário envia op=resto para a versão da etapa 3 (sem validação). O que acontece?

Por que o value="<?= htmlspecialchars($a) ?>" precisa do htmlspecialchars?

Quanto vale is_numeric("2,5")?

Resumo

  • Um só index.php mostra o formulário (GET) e processa o envio (POST); $_SERVER['REQUEST_METHOD'] diz qual dos dois aconteceu.
  • Todo campo chega como texto e pode não chegar: $_POST['x'] ?? '' e trim.
  • Valide antes de usar: is_numeric, lista de operações permitidas, divisor diferente de zero. Junte os erros numa lista.
  • match escolhe o valor; sem braço para o valor, UnhandledMatchError.
  • number_format($n, 2, ',', '.') deixa o número no formato brasileiro.
  • Tudo que veio do usuário e volta para a página passa por htmlspecialchars, inclusive dentro de value="…".

Próximo projeto: o Formulário de Contato, que guarda as mensagens num arquivo e usa o padrão Post/Redirect/Get.