PHP 8.0: Tipos de dados (2/4)
A versão 8.0 do PHP foi lançada. Está tão cheia de novidades como nenhuma versão anterior. A apresentação delas exigiu quatro artigos separados. Neste segundo, veremos os tipos de dados.

Voltemos à história. O avanço fundamental do PHP 7 foi a introdução de
type hints escalares. Quase não aconteceu. A autora da solução incrível, Andrea Faulds, que graças a
declare(strict_types=1)
era totalmente retrocompatível e opcional,
foi rudemente rejeitada pela comunidade. Felizmente, na época, Anthony Ferrara defendeu ela e sua
proposta, lançou uma campanha e a RFC passou por muito pouco. Ufa. A maioria
das novidades no PHP 8 é obra do lendário Nikita Popov e passaram na votação como
manteiga. O mundo está mudando para melhor.
O PHP 8 leva os tipos à perfeição. A grande maioria das anotações
phpDoc @param
, @return
e @var
no código
desaparecerá e será substituída pela notação nativa e, principalmente, pela
verificação do engine PHP. Nos comentários, restarão apenas descrições de
estruturas como string[]
ou anotações mais complexas para
o PHPStan.
Union Types
Union types são uma enumeração de dois ou mais tipos que um valor pode assumir:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
O PHP já conhecia alguns union types especiais antes. Por exemplo, tipos
anuláveis como ?string
, que é equivalente ao union type
string|null
, e a notação de interrogação pode ser considerada
apenas uma abreviação. Obviamente, também funciona no PHP 8, mas não pode
ser combinado com barras verticais, ou seja, em vez de
?string|object
, é necessário escrever o
string|object|null
completo. Além disso, iterable
sempre foi equivalente a array|Traversable
. Talvez você se
surpreenda ao saber que float
também é, na verdade, um union type
que aceita int|float
, mas converte para float
.
Não é possível usar os pseudotipos void
e mixed
em uniões, pois isso não faria sentido algum.
Nette está pronta para os tipos de união. Em Schema, Expect::from()
aceita-os, e os apresentadores também os aceitam. Você pode usá-los, por
exemplo, nos métodos de renderização e de ação:
public function renderDetail(int|array $id)
{
...
}
Por outro lado, o autowiring no Nette DI rejeita union types. Ainda falta um caso de uso onde faria sentido, por exemplo, que um construtor aceitasse um ou outro objeto. Claro, se surgir, será possível ajustar o comportamento do contêiner de acordo.
Os métodos getParameterType()
, getReturnType()
e
getPropertyType()
em Nette\Utils\Reflection lançam uma exceção
no caso de um union type (na versão 3.1; na versão 3.0 mais antiga, retornam
null por compatibilidade).
mixed
O pseudotipo mixed
diz que o valor pode ser absolutamente
qualquer coisa.
No caso de parâmetros e propriedades, na verdade é o mesmo comportamento
de quando não indicamos nenhum tipo. Para que serve então? Para poder
distinguir quando o tipo simplesmente está ausente e quando é realmente
mixed
.
No caso do valor de retorno de uma função ou método, não indicar o tipo
difere de indicar o tipo mixed
. Na verdade, é o oposto de
void
, pois diz que a função deve retornar algo. A ausência de
return
é então um erro fatal.
Na prática, você deve usá-lo raramente, porque graças aos union types, você pode especificar o valor com mais precisão. Portanto, é útil em situações excepcionais:
function dump(mixed $var): mixed
{
// imprime variável
return $var;
}
false
O novo pseudotipo false
só pode ser usado em union types.
Surgiu da necessidade de descrever nativamente o tipo do valor de retorno de
funções nativas que, historicamente, retornam false
em caso
de falha:
function strpos(string $haystack, string $needle): int|false
{
}
Por esse motivo, não existe o tipo true
, nem é possível usar
false
sozinho, false|null
ou
bool|false
.
static
O pseudotipo static
só pode ser usado como tipo de retorno de
um método. Ele diz que o método retorna um objeto do mesmo tipo que
o próprio objeto (enquanto self
diz que retorna a classe na qual
o método está definido). O que é excelente para descrever fluent
interfaces:
class Item
{
public function setValue($val): static
{
$this->value = $val;
return $this;
}
}
class ItemChild extends Item
{
public function childMethod()
{
}
}
$child = new ItemChild;
$child->setValue(10)
->childMethod();
resource
Este tipo não existe no PHP 8 e nem será introduzido no futuro. Resources
são uma relíquia histórica dos tempos em que o PHP ainda não tinha objetos.
Gradualmente, os resources serão substituídos por objetos e, com o tempo,
esse tipo desaparecerá completamente. Por exemplo, o PHP 8.0 substitui
o resource que representa uma imagem pelo objeto GdImage
e
o resource de conexão curl
pelo objeto
CurlHandle
.
Stringable
É uma interface que é implementada automaticamente por todo objeto com
o método mágico __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
Na definição da classe, é possível indicar explicitamente
class Email implements Stringable
, mas não é necessário.
Este estilo de nomenclatura também reflete o Nette\Utils\Html
,
que implementa a interface Nette\HtmlStringable
em vez da anterior
IHtmlString
. Objetos deste tipo, por exemplo, não são escapados
pelo Latte.
Variância de tipo, contravariância, covariância
O Princípio de Substituição de Liskov (Liskov Substitution Principle – LSP) diz que os descendentes de uma classe e as implementações de uma interface nunca devem exigir mais e fornecer menos que o pai. Ou seja, que o método do descendente não deve exigir mais argumentos ou aceitar uma gama mais restrita de tipos para os parâmetros do que o ancestral e, inversamente, não deve retornar uma gama mais ampla de tipos. Mas pode retornar uma gama mais restrita. Por quê? Porque, caso contrário, a herança não funcionaria de forma alguma. Uma função aceitaria um objeto de um determinado tipo, mas não saberia quais parâmetros podem ser passados aos métodos e o que eles realmente retornarão, porque qualquer descendente poderia quebrar isso.
Portanto, em OOP, vale que o descendente pode:
- aceitar uma gama mais ampla de tipos nos parâmetros (isso é chamado de contravariância)
- retornar uma gama mais restrita de tipos (covariância)
- e as propriedades não podem mudar de tipo (são invariantes)
O PHP sabe fazer isso desde a versão 7.4 e todos os tipos recém-introduzidos no PHP 8.0 também suportam contravariância e covariância.
No caso de mixed
, o descendente pode restringir o valor de
retorno para qualquer tipo, exceto void
, porque não se trata de um
tipo de valor, mas de sua ausência. Nem o descendente pode omitir o tipo,
porque isso também permite a ausência.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Também os union types podem ser ampliados nos parâmetros e restringidos nos valores de retorno:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Além disso, false
pode ser estendido para bool
no
parâmetro ou, inversamente, bool
no valor de retorno pode ser
restringido para false
.
Todas as violações de covariância/contravariância levam a um erro fatal no PHP 8.0.
Nas próximas partes, mostraremos o que são atributos, quais novas funções e classes apareceram no PHP e apresentaremos o Just in Time Compiler.
Faça o login para enviar um comentário