Tipagem estrita
Esta aula aborda a tipagem estrita em PHP, explicando como ativar o modo estrito com declare(strict_types=1), a utilização de type hints em parâmetros e retornos, a introdução dos union types no PHP 8 e o uso de nullable types para permitir valores nulos. O conteúdo inclui exemplos práticos, boas práticas e exercícios para fixação.
Bem-vindo à aula sobre tipagem estrita em PHP! Nesta aula, vamos explorar como o PHP lida com tipos de dados e como podemos tornar nosso código mais robusto e previsível usando a declaração de tipos. A tipagem estrita é um recurso poderoso que ajuda a evitar erros sutis e a melhorar a manutenibilidade do código.
O PHP é uma linguagem de tipagem dinâmica, mas a partir da versão 7.0, os desenvolvedores ganharam a capacidade de declarar tipos em parâmetros, retornos e propriedades. Com o PHP 8, essa funcionalidade foi ampliada com os union types e outras melhorias. Nesta aula, vamos nos aprofundar em como usar esses recursos de forma eficaz, especialmente o modo estrito, que altera o comportamento da coerção de tipos.
declare(strict_types=1)
A declaração declare(strict_types=1) deve ser a primeira instrução de um arquivo PHP (podendo vir após a tag de abertura <?php). Ela ativa o modo estrito para chamadas de função feitas dentro daquele arquivo. Isso significa que, ao passar argumentos para funções tipadas, o PHP não fará coerção automática de tipos, mas exigirá que os tipos correspondam exatamente (ou que sejam subtipos, como em herança).
Sem o modo estrito, o PHP tenta converter o valor para o tipo esperado (por exemplo, converter uma string numérica para int). Com o modo estrito, uma string como "123" não será aceita para um parâmetro int; em vez disso, será lançado um TypeError. Isso ajuda a evitar bugs silenciosos e torna o contrato da função mais claro.
É importante notar que a declaração afeta apenas o arquivo onde está presente, e não os arquivos que chamam ou são chamados. A coerção é aplicada no momento da chamada, então se você tem uma função tipada em um arquivo com strict_types=1 e a chama de outro arquivo sem essa declaração, a chamada será feita de acordo com as regras do arquivo chamador.
Vejamos um exemplo básico:
// arquivo: strict.php
<?php
declare(strict_types=1);
function soma(int $a, int $b): int {
return $a + $b;
}
// Isso funcionará:
echo soma(2, 3); // 5
// Isso causará TypeError em modo estrito:
// echo soma("2", 3); // TypeError: Argument #1 ($a) must be of type int, string given
?>
No exemplo acima, a chamada soma("2", 3) resultaria em um erro porque o modo estrito não permite a conversão automática de string para int. Sem o modo estrito, o PHP converteria "2" para 2 e funcionaria.
Uma boa prática é ativar o modo estrito em todos os arquivos que contenham funções ou métodos com type hints, especialmente em bibliotecas e aplicações de grande porte. Isso torna o comportamento esperado mais explícito e evita surpresas.
Type hints
Type hints (ou dicas de tipo) permitem especificar o tipo esperado para parâmetros, valores de retorno e propriedades (a partir do PHP 7.4). Eles servem como uma forma de documentação e uma verificação em tempo de execução. Os tipos mais comuns incluem int, float, string, bool, array, callable, classes e interfaces, e mixed (PHP 8).
Quando você declara um tipo em um parâmetro, o PHP verifica se o valor passado é do tipo compatível. Em modo não estrito, ele pode tentar converter; em modo estrito, exige o tipo exato. Para retornos, o tipo declara o que a função deve retornar; se o valor retornado não for compatível, um TypeError é lançado.
Exemplo de type hints em parâmetros e retorno:
declare(strict_types=1);
class Produto {
public function __construct(private string $nome, private float $preco) {}
public function getNome(): string {
return $this->nome;
}
public function getPreco(): float {
return $this->preco;
}
}
function calculaDesconto(Produto $produto, float $percentual): float {
return $produto->getPreco() * (1 - $percentual / 100);
}
$p = new Produto("Notebook", 2500.00);
echo calculaDesconto($p, 10.0); // 2250.0
No exemplo, a função calculaDesconto exige um objeto da classe Produto e um float para o percentual. Isso evita que você passe tipos incorretos, como uma string para o percentual.
Além disso, o PHP 7.4 introduziu propriedades tipadas, permitindo declarar o tipo de cada propriedade de classe, como visto no construtor acima. Isso melhora a segurança e a clareza do código.
Union types (PHP 8)
Os union types permitem que um parâmetro, retorno ou propriedade aceite mais de um tipo. A sintaxe usa o caractere pipe (|) entre os tipos. Por exemplo, int|string significa que o valor pode ser um inteiro ou uma string. Isso é útil quando uma função pode receber diferentes tipos de entrada e tratá-los de forma unificada.
Os union types foram introduzidos no PHP 8.0 e são uma forma mais flexível de tipagem, mas ainda mantêm a segurança de tipos. Em modo estrito, o valor deve ser exatamente um dos tipos listados. Em modo não estrito, o PHP tentará converter para um dos tipos, se possível, mas a ordem importa: ele tenta converter para o primeiro tipo da lista.
Exemplo de union types:
declare(strict_types=1);
function formatarValor(int|string $valor): string {
if (is_int($valor)) {
return number_format($valor, 2, ',', '.');
}
return $valor; // já é string
}
echo formatarValor(1234); // "1.234,00"
echo formatarValor("abc"); // "abc"
No exemplo, a função aceita tanto int quanto string. Se for int, formata; se for string, retorna como está. Isso evita a necessidade de overloads ou conversões manuais.
Também é possível usar union types com null (como int|null), mas isso é equivalente a usar nullable types, como veremos a seguir.
Nullable types
Nullable types permitem que um tipo aceite também o valor null. Eles são úteis para indicar que um parâmetro ou retorno pode ser opcional ou que um valor pode estar ausente. Antes do PHP 7.1, era necessário usar um valor padrão null para simular isso, mas agora podemos declarar explicitamente com um ponto de interrogação antes do tipo, como ?int, ou usando int|null (equivalente).
Em modo estrito, se você declarar ?int, o parâmetro aceita null ou um inteiro, mas não uma string numérica, por exemplo. Isso ajuda a evitar a passagem acidental de valores inválidos.
Exemplo de nullable type:
declare(strict_types=1);
function saudacao(?string $nome): string {
return "Olá, " . ($nome ?? "visitante") . "!";
}
echo saudacao("Maria"); // Olá, Maria!
echo saudacao(null); // Olá, visitante!
No exemplo, $nome pode ser uma string ou null. Se for null, usamos o operador de coalescência nula (??) para fornecer um valor padrão.
É importante notar que a ordem dos tipos em union types com null não importa (null|int é igual a int|null), mas a sintaxe com ? é mais concisa.
Boas práticas e observações finais
Ao usar tipagem estrita, é importante ter em mente que ela afeta apenas o arquivo onde está declarada. Para garantir consistência, recomenda-se ativar declare(strict_types=1) em todos os arquivos que usam type hints. Além disso, é útil combinar type hints com PHPDoc para documentação mais rica, embora os próprios type hints já forneçam uma documentação executável.
Outra boa prática é evitar o uso excessivo de mixed (que aceita qualquer tipo), pois isso reduz a segurança de tipos. Prefira union types ou classes específicas quando possível. Também é importante lembrar que, em modo estrito, a coerção de tipos não é aplicada, então você deve garantir que os valores passados sejam do tipo correto, possivelmente usando funções de conversão explícitas quando necessário.
Por fim, teste seu código em ambos os modos (estrito e não estrito) para entender as diferenças, mas escolha um padrão e siga-o consistentemente em seu projeto.
Referências
- PHP: Declarações de tipo
- PHP: Modo estrito
- PHP: Union types
- PHP: Nullable types
- PHP: Type declarations em argumentos
- PHP: Propriedades tipadas
Exercícios
- Crie uma função chamada
divideque aceite dois floats e retorne um float. Ative o modo estrito e teste a chamada com inteiros, observando o erro. - Escreva uma função
concatenarque aceite dois parâmetros: um string e um int, e retorne uma string concatenando-os. Use type hints e teste com valores corretos e incorretos. - Crie uma função
calcularAreaque aceite um union typeint|floatpara o raio e retorne a área do círculo (float). Teste com um int e um float. - Implemente uma função
obterNomeque aceite um parâmetro do tipo?stringe retorne uma string. Se o valor for null, retorne "Anônimo". - Crie uma classe
Pessoacom propriedades tipadas: nome (string), idade (int) e email (?string). Implemente métodos getters e setters com type hints e teste a criação de objetos.
declare(strict_types=1);
function divide(float $a, float $b): float {
return $a / $b;
}
// Teste com inteiros:
try {
echo divide(10, 2); // TypeError: Argument #1 ($a) must be of type float, int given
} catch (TypeError $e) {
echo $e->getMessage();
}
declare(strict_types=1);
function concatenar(string $texto, int $numero): string {
return $texto . $numero;
}
echo concatenar("Número: ", 42); // Número: 42
// echo concatenar("Número: ", "42"); // TypeError
declare(strict_types=1);
function calcularArea(int|float $raio): float {
return pi() * $raio * $raio;
}
echo calcularArea(5); // 78.539816339745
echo calcularArea(2.5); // 19.634954084936
declare(strict_types=1);
function obterNome(?string $nome): string {
return $nome ?? "Anônimo";
}
echo obterNome("João"); // João
echo obterNome(null); // Anônimo
declare(strict_types=1);
class Pessoa {
public function __construct(
private string $nome,
private int $idade,
private ?string $email = null
) {}
public function getNome(): string {
return $this->nome;
}
public function setNome(string $nome): void {
$this->nome = $nome;
}
public function getIdade(): int {
return $this->idade;
}
public function setIdade(int $idade): void {
$this->idade = $idade;
}
public function getEmail(): ?string {
return $this->email;
}
public function setEmail(?string $email): void {
$this->email = $email;
}
}
$p = new Pessoa("Maria", 30, "maria@example.com");
echo $p->getNome(); // Maria