Tipos em parâmetros e retorno de funções

1. Introdução à Tipagem em Funções no PHP

A tipagem em parâmetros e retornos de funções no PHP evoluiu de um recurso opcional para uma prática essencial no desenvolvimento profissional. Quando você declara explicitamente os tipos que uma função aceita e retorna, está criando contratos claros que previnem erros em tempo de execução, melhoram a legibilidade do código e servem como documentação viva.

O histórico dessa evolução é marcante: no PHP 5, a tipagem era limitada a objetos, arrays e interfaces. O PHP 7 revolucionou ao introduzir tipos escalares (int, float, string, bool) e tipos de retorno. O PHP 8 elevou o nível com tipos union, intersection, mixed e never, além do atributo #[ReturnTypeWillChange] para retrocompatibilidade.

Os benefícios práticos incluem: captura precoce de bugs, IDEs com autocomplete mais preciso, redução de testes de validação manual e código que se auto-documenta. Uma função com assinatura function calcularTotal(array $itens): float já comunica sua intenção sem precisar ler o corpo.

2. Tipos de Parâmetros: Sintaxe e Exemplos

O PHP suporta uma variedade de tipos para parâmetros:

// Tipos escalares
function processarIdade(int $idade): void {
    echo "Idade: $idade";
}

function calcularMedia(float $nota1, float $nota2): float {
    return ($nota1 + $nota2) / 2;
}

function saudacao(string $nome): string {
    return "Olá, $nome!";
}

function isAtivo(bool $status): bool {
    return $status;
}

// Tipos compostos
function somarNumeros(array $numeros): int {
    return array_sum($numeros);
}

function executarCallback(callable $callback, $valor) {
    return $callback($valor);
}

// Tipos especiais
function logMisto(mixed $dado): void {
    var_dump($dado);
}
// Exemplo completo com chamada
function criarUsuario(string $nome, string $email, int $idade): array {
    return [
        'nome' => $nome,
        'email' => $email,
        'idade' => $idade,
        'criado_em' => date('Y-m-d H:i:s')
    ];
}

$usuario = criarUsuario('Maria', 'maria@email.com', 28);
// TypeError se passar tipos incorretos

3. Tipos de Retorno: Garantindo o Valor de Saída

A declaração de tipo de retorno usa a sintaxe : tipo após os parênteses dos parâmetros:

// Retorno void - função que não retorna valor
function logErro(string $mensagem): void {
    file_put_contents('erros.log', $mensagem . PHP_EOL, FILE_APPEND);
}

// Retorno never - função que nunca retorna (lança exceção ou loop infinito)
function abortar(string $mensagem): never {
    throw new \RuntimeException($mensagem);
}

function loopInfinito(): never {
    while (true) {
        // processamento contínuo
    }
}

// Retorno tipado com objeto
class Pessoa {
    public function __construct(
        public string $nome,
        public int $idade
    ) {}
}

function criarPessoa(string $nome, int $idade): Pessoa {
    return new Pessoa($nome, $idade);
}
// Exemplo prático com validação
function dividir(int $a, int $b): float|false {
    if ($b === 0) {
        return false;
    }
    return $a / $b;
}

$resultado = dividir(10, 3); // 3.333...
$erro = dividir(10, 0);      // false

4. Tipos Nullable e Valores Padrão

Tipos nullable permitem que um parâmetro ou retorno aceite null além do tipo especificado:

// Parâmetros nullable com valor padrão
function buscarUsuario(?int $id = null): ?array {
    if ($id === null) {
        return null; // ou buscar todos
    }
    // lógica para buscar por ID
    return ['id' => $id, 'nome' => 'Usuário'];
}

// Diferença entre ?string e string|null (equivalente no PHP 8+)
function formatar(?string $texto): string {
    return $texto ?? 'texto padrão';
}

// Cuidados com nullable - evite ambiguidades
function processar(?string $dado): void {
    if ($dado === null) {
        echo "Nenhum dado fornecido";
        return;
    }
    echo "Processando: $dado";
}
// Combinação com valores padrão
function configurar(string $chave, mixed $valor = null): void {
    if ($valor === null) {
        // usar valor padrão do sistema
    }
}

5. Tipos Union e Intersection (PHP 8+)

Os tipos union permitem que um parâmetro ou retorno aceite múltiplos tipos:

// Tipos union
function processarEntrada(int|string $entrada): string {
    return match(true) {
        is_int($entrada) => "Número: $entrada",
        is_string($entrada) => "Texto: $entrada",
        default => throw new \InvalidArgumentException()
    };
}

function buscarRegistro(int|string $id): array|null {
    // Aceita ID numérico ou string
}

// Tipos intersection (para classes e interfaces)
interface Logavel {}
interface Serializavel {}

class Logger implements Logavel, Serializavel {}

function processarLog(Logavel&Serializavel $objeto): void {
    // $objeto deve implementar ambas interfaces
}
// Quando usar union vs herança
// Union é melhor para tipos não relacionados
function calcular(string|int|float $valor): float {
    return (float) $valor;
}

// Herança para tipos relacionados
abstract class Forma {}
class Circulo extends Forma {}
class Quadrado extends Forma {}

function calcularArea(Forma $forma): float {
    // polimorfismo
}

6. Tipos em Parâmetros Variadic e Named Arguments

Funções variadic podem receber um número variável de argumentos tipados:

// Tipagem em funções variadic
function somar(int ...$numeros): int {
    return array_sum($numeros);
}

echo somar(1, 2, 3, 4, 5); // 15

function concatenar(string ...$partes): string {
    return implode(' ', $partes);
}

// Comportamento com spread operator
$valores = [1, 2, 3];
echo somar(...$valores); // 6

// Named arguments e verificação de tipos
function configurarBD(
    string $host,
    int $porta = 3306,
    string $usuario = 'root',
    string $senha = ''
): void {
    // configuração
}

// Chamada com named arguments
configurarBD(
    host: 'localhost',
    usuario: 'admin',
    senha: '123456'
);
// Exemplo completo combinando variadic com tipagem
function processarTransacoes(
    string $moeda,
    float ...$valores
): array {
    $total = array_sum($valores);
    return [
        'moeda' => $moeda,
        'valores' => $valores,
        'total' => $total,
        'quantidade' => count($valores)
    ];
}

$resultado = processarTransacoes('BRL', 10.50, 25.00, 100.75);

7. Boas Práticas e Erros Comuns

// Sempre declarar tipos em funções públicas/API
// RUIM
function soma($a, $b) {
    return $a + $b;
}

// BOM
function soma(int $a, int $b): int {
    return $a + $b;
}

// Evitar mixed quando possível
// RUIM
function processar(mixed $dado): mixed {
    return $dado;
}

// BOM (usar union)
function processar(string|int|array $dado): string|int|array {
    return $dado;
}

// Tratamento de TypeError
function dividirSeguro(int $a, int $b): float {
    if ($b === 0) {
        throw new \DivisionByZeroError('Divisão por zero');
    }
    return $a / $b;
}

try {
    echo dividirSeguro(10, 0);
} catch (\DivisionByZeroError $e) {
    echo "Erro: " . $e->getMessage();
}

Uso de declare(strict_types=1):

declare(strict_types=1);

function somar(int $a, int $b): int {
    return $a + $b;
}

// Isso causará TypeError, não conversão automática
// somar(5, '10'); // TypeError: Argument 2 must be of type int, string given

Dicas para migração de código legado:
1. Comece pelas funções mais críticas (API pública)
2. Use tipos nullable durante a transição
3. Adicione declare(strict_types=1) gradualmente
4. Utilize @param e @return em PHPDoc como documentação temporária
5. Teste exaustivamente após cada adição de tipo

Referências