PHP

Tipos no PHP

Nesse post explicamos com exemplos tudo o que você precisa saber sobre definição de tipos em parâmetros e retornos de funções e métodos no PHP, além do novo recurso de propriedades de classes tipadas do PHP 7.4.

há 4 anos 1 mês

Formação Desenvolvedor PHP
Conheça a formação em detalhes

Quando começamos a aprender PHP uma das primeiras características que notamos e que é ensinado na maioria dos cursos sobre a linguagem é a tipagem dinâmica e fraca do PHP.

Caso não saiba o que é tipagem dinâmica e fraca, aconselho a leitura do artigo que falamos sobre tipagem.

Essa característica da linguagem é amada por muitos desenvolvedores, que inclusive defendem que deve ser a filosofia da linguagem e que ela deve ser mantida. Já outra parte das pessoas acreditam que ela gera mais insegurança, uma vez que o programador precisa realizar uma programação mais defensiva, com mais verificações de tipo e validações.

Independente do que os desenvolvedores acham, a verdade é que o PHP hoje caminha cada vez mais para o meio termo. A linguagem não permite definir diretamente o tipo em uma variável, porém possui muitos mecanismos para controlarmos os tipos de dados, principalmente em entradas e saídas de funções e métodos e a partir da versão 7.4 com a tipagem das propriedades de classes. Não fiquem ansiosos, vamos ver na prática como o PHP nos permite trabalhar com esses recursos.

PHP - Fundamentos
Curso PHP - Fundamentos
Conhecer o curso

Tipos no PHP 5

Desde as primeiras versões do PHP 5 ele nos permite fazer o que chamamos de type hints. Esse recurso nos possibilita definir o tipo de um parâmetro de entrada de uma função ou método. Na versão 5.0.0 era possível definir o tipo classe, interface ou a palavra reservada self que obriga o parâmetro de entrada ser do mesmo tipo que a classe atual:

class Produto
{
	public $nome;
}

class Cliente
{
	public function comprar(Produto $produto)
	{
		echo "O cliente comprou o " . $produto->nome;
	}
}

$prod = new Produto;
$prod->nome = "Carro";

$cli = new Cliente;
$cli->comprar($prod);

Note que no método comprar, antes do nome do parâmetro informamos que ele deve ser um objeto do tipo Produto. E se fosse passado um outro tipo de dado, era gerado um erro, mas o programa continuava a execução normalmente.

Na versão 5.1.0 foi adicionado o tipo array que permite verificar se um parâmetro de entrada é um array:

function listar(array $cursos)
{
	//
}

listar(array(1, 2, 3));

E na versão 5.4.0 a palavra callable para verificar se o parâmetro é invocável. Na prática ele é usado para verificar se podemos executar o parâmetro que recebemos como uma função. Todos os exemplos abaixo são válidos:

//Função que recebe um invocável
function executar(callable $executavel) {
    echo $executavel();
}

$funcao = function() {
    echo "executou a função<br>";
};

//passa a função que está dentro da variável $funcao
executar($funcao);

//passa uma função anônima
executar(function() {
    echo "executou a função<br>";
});

function funcao() {
    echo "executou a função funcao<br>";
};

//passa o nome da função
executar("funcao");

class ClasseExecutavel {
    public function __invoke() {
        return "executou a função dentro da classe<br>";
    }
}

//passa a instancia de uma classe executável
executar(new ClasseExecutavel());

Nas versões seguintes, PHP 5.5 e 5.6 nenhuma nova palavra para type hint foi adicionada. Já na versão 7 o PHP trouxe uma série de mudanças importantes.

Desenvolvedor PHP
Formação Desenvolvedor PHP
Conhecer a formação

Tipos primitivos

A versão 7.0 além de várias outras features, trouxe importantes mudanças em relação a tipos. A partir dessa versão é possível declarar tipos primários (string, inteiro, float e booleano), além dos que já era possível anteriormente:

function pedir(
    string $nome,
    int $quantidade,
    float $valor,
    bool $entregar
) {
    //corpo da função
}

pedir('X Salada', 2, 12.80, true);

Declaração de retorno

A partir do PHP 7.0 é possível indicar o tipo de retorno em funções e métodos, tornando o controle de tipos ainda mais estrito:

function somar(int $num1, int $num2): int
{
    return $num1 + $num2;
}

var_dump(somar(2, 3));

Tratamento de erro para tipo

O erro caso o valor não seja do tipo esperado, tanto na entrada de funções e métodos, quanto no retorno, passou a ser tratado como exceção do tipo TypeError a partir do PHP 7.0:

Fatal error:  Uncaught TypeError: Argument 1 passed to somar() must be of the type integer, string given, called in [...][...] on line 8 and defined in [...][...]:3

Se nenhum tratamento for realizado o script é interrompido, mas existe a possibilidade muito importante de tratar a exceção gerada:

function somar(int $num1, int $num2): int
{
    return $num1 + $num2;
}

try {
    somar("uma string qualquer", 3);
} catch(\TypeError $e) {
    //tratamento necessário

    echo "Tivemos uma exceção de tipos";
}

Tipagem estrita

Sabemos que o PHP por padrão é uma linguagem de tipagem fraca. Isso significa que sempre que possível ele tenta converter o tipo de dado. Veja o exemplo abaixo:

function somar(int $num1, int $num2): int
{
    return $num1 + $num2;
}


echo somar(true, 3); //4

A função somar espera dois valores do tipo inteiro, porém no exemplo acima passamos o primeiro valor como sendo um booleano. O PHP converte o valor true para 1 e por isso retornou na função o valor 4 sem nenhum tipo de erro.

A característica de tipagem fraca da linguagem acaba meio que invalidando a declaração de tipos. Baseado nisso foi criado a tipagem estrita, uma declaração no início de cada arquivo que torna a tipagem do PHP forte, para valores de entradas e saídas de métodos. Veja o exemplo abaixo:

declare(strict_types=1);

function somar(int $num1, int $num2): int
{
    return $num1 + $num2;
}

echo somar(true, 3);

O exemplo acima passará a gerar uma exceção do tipo TypeError. A única conversão que foge a regra da tipagem estrita é quando o PHP espera um float, nesse caso é possível enviar um inteiro.

A definição de tipagem estrita não pode ser declarada para o projeto inteiro, deve sempre ser feita por arquivo. Se um arquivo que não declara tipo estrito, chamar uma função em um arquivo que declara tipo estrito a preferência é do arquivo que está chamando, ou seja, sem tipo estrito.

Anuláveis

A partir da versão 7.1 o PHP permite definir parâmetros de entrada e retornos que aceitam o tipo de dado especificado ou null, usando o sinal ? antes do tipo:

declare(strict_types=1);

function somar(?int $num1, ?int $num2): ?int
{
    if ($num1 === null || $num2 == null) {
        return null;
    }

    return $num1 + $num2;
}

var_dump(somar(null, 3));

Novos tipos

O PHP 7.1 e 7.2 adicionaram mais dois novos tipos, iterable e object respectivamente. O primeiro garante que vamos receber um item que seja possível ser iterado e o segundo que vamos receber um objeto, independente do tipo.

declare(strict_types=1);

function varrer(iterable $numeros): void
{
   foreach($numeros as $chave => $valor) {
       echo "$valor <br>";
   }
}

varrer([1, 2, 3]);

function ler(object $dados): void
{
   echo $dados->nome;
}

$objeto = new stdClass;
$objeto->nome = 'X salada';
ler($objeto);

Note também que no retorno declaramos uma saída chamada void. Essa saída é específica para o retorno e significa que não vamos retornar nada na função ou método. Está disponível também a partir da versão 7.1.

Propriedades tipadas

O PHP 7.4 lançado no final de 2019 implementou uma grande novidade para a linguagem, o suporte a propriedades tipadas. Esse recursos em conjunto com todos os outros que já vimos permite um controle ainda maior de tipos:

declare(strict_types=1);

class Produto
{
    public string $nome;

    public float $preco;

    public int $quantidade;
}

$prod = new Produto;
$prod->nome = "Bike";
$prod->preco = 856.99;
$prod->quantidade = 10;

//TypeError
$prod->preco = "uma string";

Uso de tipos

Conforme falado no início do artigo, muitos desenvolvedores gostam da ideia de usar o PHP com tipagem fraca. Um exemplo de desenvolvedor é o Taylor Otwell, criador do Laravel de um dos frameworks mais usados na linguagem. Em tweets e discussões ele já apontou que não acha que tornar a tipagem forte melhora a qualidade do código E que não pretende usar tipos em parâmetros e retornos no core Laravel. Esses registros são de 2016 e 2017, infelizmente não encontrei mais novos.

Já o Symfony, outro projeto gigante com vários componentes super importantes para o ecossistema PHP, está modificando sua base de código para adicionar os recursos de tipagem mais novos do PHP. Conforme falam nesse post https://symfony.com/blog/symfony-type-declarations-return-types-and-phpunit-compatibility#added-compatibility-with-all-php-and-phpunit-versions.

Vale lembrar que independente de escolher usar tipos na sua aplicação ou não, é possível usar frameworks e bilbiotecas de ambos os modos, esse é um dos motivos do tipo estrito ser definido por arquivo. Outro ponto é que usar tipos ou não no PHP não define se uma aplicação é melhor que a outra.

Considerações Finais

O PHP nos permite trabalhar tanto em modo tipagem fraca e sem especificar tipos em absolutamente nenhum lugar, como já é o clássico na linguagem há muito tempo. Quanto usando todos os recursos que vimos nesse post para realizar um controle mais justo dos tipos na aplicação.

Um ponto importante que deve ser observado é a adaptação dos desenvolvedores ao novo modo de programar, principalmente os que já trabalham com PHP há bastante tempo. É natural que exista uma barreira de aceitação, já que a mudança geralmente causa medo, mas também é importante praticar em projetos de teste ou menores, antes de começar a trabalhar em projetos maiores usando essa abordagem.

Autor(a) do artigo

Elton Fonseca
Elton Fonseca

Professor e desenvolvedor. Formado em análise e desenvolvimento de sistema, pós graduado em engenharia e arquitetura de software. É autor de cursos em diversos temas, como, desenvolvimento back-end, cloud computing e CMSs. Nas horas vagas adora estudar sobre o mercado financeiro, cozinhar e brincar com pequeno Daniel. @eltonfonsecadev

Todos os artigos

Artigos relacionados Ver todos