Desafio PHP

Calculadora com tipagem estrita e union types

Enunciado

Crie uma calculadora que aceita dois números (int ou float) e uma operação (string). A função calcular deve ter parâmetros tipados como int|float e string, e retorno int|float. Ela deve suportar as operações soma, subtracao, multiplicacao e divisao. Se a operação for desconhecida, lance InvalidArgumentException. Se houver divisão por zero, lance DivisionByZeroError. Use declare(strict_types=1);. Teste com as quatro operações e imprima cada resultado. Trate as exceções com try/catch para exibir mensagens amigáveis.

Requisitos

  • A função calcular deve ter parâmetros int|float $a, int|float $b, string $operacao e retorno int|float.
  • Deve lançar InvalidArgumentException para operação inválida e DivisionByZeroError para divisão por zero.
  • O programa deve capturar as exceções e imprimir mensagens claras.
  • A saída deve mostrar os resultados das quatro operações e pelo menos um erro.

Código inicial

<?php

declare(strict_types=1);

function calcular(int|float $a, int|float $b, string $operacao): int|float {
    // Implemente
}

// Testes

Saída esperada

Soma: 10
Subtração: 4
Multiplicação: 21
Divisão: 2.5
Erro: Operação desconhecida: potencia
Erro: Divisão por zero não permitida
Ver dica

Use match ou switch para escolher a operação. Para divisão, verifique se $b == 0 antes de dividir e lance DivisionByZeroError. Lembre-se de que o retorno pode ser int ou float, então não force conversão.

Mostrar solução
<?php

declare(strict_types=1);

function calcular(int|float $a, int|float $b, string $operacao): int|float {
    return match ($operacao) {
        'soma'          => $a + $b,
        'subtracao'     => $a - $b,
        'multiplicacao' => $a * $b,
        'divisao'       => $b == 0
                            ? throw new DivisionByZeroError("Divisão por zero não permitida")
                            : $a / $b,
        default         => throw new InvalidArgumentException("Operação desconhecida: $operacao"),
    };
}

// Testes
try {
    echo "Soma: ", calcular(7, 3, 'soma'), PHP_EOL;
    echo "Subtração: ", calcular(7, 3, 'subtracao'), PHP_EOL;
    echo "Multiplicação: ", calcular(7, 3, 'multiplicacao'), PHP_EOL;
    echo "Divisão: ", calcular(5, 2, 'divisao'), PHP_EOL;
} catch (DivisionByZeroError | InvalidArgumentException $e) {
    echo "Erro: ", $e->getMessage(), PHP_EOL;
}

try {
    echo calcular(10, 2, 'potencia'), PHP_EOL;
} catch (InvalidArgumentException $e) {
    echo "Erro: ", $e->getMessage(), PHP_EOL;
}

try {
    echo calcular(10, 0, 'divisao'), PHP_EOL;
} catch (DivisionByZeroError $e) {
    echo "Erro: ", $e->getMessage(), PHP_EOL;
}

Passo a passo

  1. declare(strict_types=1); garante que os tipos sejam verificados sem conversão.
  2. A função calcular aceita dois parâmetros int|float e um string, e retorna int|float.
  3. Usamos match para mapear a operação para a expressão correspondente.
  4. Na divisão, verificamos se $b == 0; se for, lançamos DivisionByZeroError com uma mensagem.
  5. Para operação desconhecida, o default lança InvalidArgumentException.
  6. O primeiro bloco try executa as quatro operações válidas e imprime os resultados.
  7. O segundo try testa uma operação inválida e captura a exceção.
  8. O terceiro try testa divisão por zero e captura a exceção.

Por que funciona

A tipagem estrita garante que apenas int ou float sejam passados para $a e $b, e que a operação seja string. O union type no retorno permite que a função devolva tanto inteiros (ex.: soma de inteiros) quanto floats (ex.: divisão). O uso de exceções específicas (InvalidArgumentException e DivisionByZeroError) torna o tratamento de erros claro e idiomático. O match é uma expressão que retorna um valor ou lança exceção, mantendo o código conciso.

Erros comuns

  • Não verificar divisão por zero e deixar o PHP emitir um warning: $a / 0 gera DivisionByZeroError nativamente em PHP 8, mas a mensagem pode não ser amigável. Melhor lançar explicitamente.
  • Usar == em vez de === para comparar a operação: 'soma' == 0 pode ser verdadeiro em versões antigas, mas com match a comparação é estrita.
  • Esquecer de declarar o retorno como int|float e usar apenas float: isso forçaria a conversão de inteiros para float, alterando a saída esperada.
  • Capturar Exception genérica em vez das exceções específicas: funciona, mas perde a semântica e pode mascarar outros erros.

Outra forma de resolver

Usar switch em vez de match:

switch ($operacao) {
    case 'soma': return $a + $b;
    case 'subtracao': return $a - $b;
    case 'multiplicacao': return $a * $b;
    case 'divisao':
        if ($b == 0) throw new DivisionByZeroError("Divisão por zero não permitida");
        return $a / $b;
    default:
        throw new InvalidArgumentException("Operação desconhecida: $operacao");
}

O switch é preferível quando se precisa de lógica mais complexa em cada caso, mas o match é mais moderno e expressivo.

Saída esperada

Soma: 10
Subtração: 4
Multiplicação: 21
Divisão: 2.5
Erro: Operação desconhecida: potencia
Erro: Divisão por zero não permitida