PHP 8.0: Notícias em tipos de dados (2/4)

há 3 anos De David Grudl  

A versão 8.0 do PHP acaba de ser lançada. Está cheio de novas funcionalidades, como nenhuma versão antes. Sua introdução merece quatro artigos separados. Na segunda parte, vamos dar uma olhada nos tipos de dados.

Vamos voltar à história. A introdução de dicas do tipo escalar foi um avanço significativo no PHP 7. Isso quase não aconteceu. Andreu Faulds, o autor da engenhosa solução, totalmente compatível com o passado e opcional graças a declare(strict_types=1), foi duramente rejeitada pela comunidade. Felizmente, Anthony Ferrara a defendeu e a proposta da época, lançou uma campanha e o RFC passou muito perto. Whew. A maioria das notícias no PHP 8 são cortesia do legendário Nikita Popov e todos passaram a votação como uma faca através da manteiga. O mundo está mudando para melhor.

O PHP 8 leva os tipos à perfeição. A grande maioria das anotações do phpDoc como @param, @return e @var serão substituídas por notações nativas. Mas o mais importante, os tipos serão verificados pelo mecanismo PHP. Somente descrições de estruturas como string[] ou anotações mais complexas para o PHPStan permanecerão nos comentários.

Tipos de União

Os tipos de união são uma enumeração de dois ou mais tipos que uma variável pode aceitar:

class Button
{
	private string|object $caption;

	public function setCaption(string|object $caption)
	{
		$this->caption = $caption;
	}
}

Certos tipos de sindicatos já foram introduzidos ao PHP antes. Os tipos nulas, por exemplo. Tais como ?string, que é equivalente ao tipo de sindicato string|null. A notação do ponto de interrogação pode ser considerada uma abreviação. Claro, também funciona no PHP 8, mas não se pode combiná-lo com barras verticais. Portanto, ao invés de ?string|object, você tem que escrever string|object|null. Além disso, iterable sempre foi equivalente a array|Traversable. Você pode se surpreender que float também seja um tipo de sindicato, pois aceita ambos int|float, mas dá o valor para float.

Você não pode usar pseudotipos void e mixed em sindicatos porque não faria sentido.

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, a fiação automática em Nette DI rejeita os tipos de união. Até agora, não há nenhum caso de uso em que faria sentido para o construtor aceitar um ou outro objeto. Naturalmente, se tal caso de uso aparecer, será possível ajustar o comportamento do recipiente de acordo.

Os métodos getParameterType(), getReturnType() e getPropertyType() em Nette\Utils\Reflection lançam uma exceção no caso do tipo de união (isto é, na versão 3.1; na versão anterior 3.0, estes métodos retornam para manter a compatibilidade nula).

mixed

O pseudotipo mixed aceita qualquer valor.

No caso de parâmetros e propriedades, isso resulta no mesmo comportamento como se não especificássemos nenhum tipo. Então, qual é a sua finalidade? Distinguir quando falta apenas um tipo e quando este é intencionalmente mixed.

No caso de valores de retorno de função e método, não especificar o tipo difere do uso do mixed. Isso significa o oposto de void, pois requer a função para retornar algo. Um retorno ausente produz então um erro fatal.

Na prática, você raramente deve usá-lo, pois graças aos tipos de sindicatos, você pode especificar o valor com precisão. Portanto, ele só é adequado em situações únicas:

function dump(mixed $var): mixed
{
	// print variable
	return $var;
}

false

Ao contrário de mixed, você pode usar o novo pseudotipo false exclusivamente em tipos de sindicatos. Ele surgiu da necessidade de descrever o tipo de retorno das funções nativas, que historicamente retornam falsas em caso de falha:

function strpos(string $haystack, string $needle): int|false
{
}

Portanto, não há nenhum tipo true. Não se pode usar somente false, nem false|null, nem bool|false.

static

Pseudotipo static só pode ser usado como um tipo de método de retorno. Ele diz que o método retorna um objeto do mesmo tipo que o próprio objeto (enquanto que self diz que retorna a classe na qual o método é definido). É excelente para descrever interfaces fluentes:

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 não será introduzido no futuro. Os recursos são uma relíquia da época em que o PHP não tinha objetos. Gradualmente, os recursos serão substituídos por objetos. Eventualmente, eles desaparecerão completamente. Por exemplo, o PHP 8.0 substitui o recurso de imagem pelo objeto GdImage, e o recurso de ondulação pelo objeto CurlHandle.

Stringable

É uma interface que é implementada automaticamente por cada objeto com um método mágico __toString().

class Email
{
	public function __toString(): string
	{
		return $this->value;
	}
}

function print(Stringable|string $s)
{
}

print('abc');
print(new Email);

É possível afirmar explicitamente class Email implements Stringable na definição de classe, mas não é necessário.

Nette\Utils\Html também reflete este esquema de nomenclatura, implementando a interface Nette\HtmlStringable em vez da anterior IHtmlString. Objetos deste tipo, por exemplo, não são escapados por Latte.

Tipo variância, contravariância, covariância

O Princípio de Substituição Liskov (LSP) afirma que as classes de extensão e implementações de interface nunca devem exigir mais e fornecer menos do que o pai. Ou seja, o método infantil não deve exigir mais argumentos ou aceitar uma gama mais restrita de tipos para parâmetros do que o pai, e vice-versa, não deve retornar uma gama mais ampla de tipos. Mas ele pode retornar menos. Por quê? Porque, caso contrário, a herança se quebraria. Uma função aceitaria um objeto de um tipo específico, mas não teria idéia de quais parâmetros pode passar para seus métodos e que tipos retornariam. Qualquer criança poderia quebrá-lo.

Assim, no OOP, a classe infantil pode:

  • aceitar uma gama mais ampla de tipos nos parâmetros (isso é chamado de contravariação)
  • retornar uma gama mais restrita de tipos (covariância)
  • e as propriedades não podem mudar de tipo (são invariantes)

O PHP tem sido capaz de fazer isso desde a versão 7.4, e todos os tipos recentemente introduzidos no PHP 8.0 também suportam contravariância e covariância.

No caso do mixed, a criança pode reduzir o valor de retorno para qualquer tipo, mas não void, pois não representa um valor, mas sim sua ausência. A criança também não pode omitir uma declaração de tipo, pois isto também permite uma ausência de valor.

class A
{
    public function foo(mixed $foo): mixed
    {}
}

class B extends A
{
    public function foo($foo): string
    {}
}

Os tipos de união também podem ser estendidos em parâmetros e reduzidos em 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 vice versa bool para false no valor de retorno.

Todas as ofensas contra a covariância/contravariância levam a um erro fatal no PHP 8.0.

  • Nas próximas partes desta série, mostraremos quais são os atributos, que novas funções e classes apareceram no PHP e apresentaremos o Compilador Just in Time.*