PHP 8.0: Visão geral completa das novidades (1/4)

há 5 anos De David Grudl  

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 primeiro, veremos o que há de novo em termos de linguagem.

Antes de mergulharmos no PHP, saiba que a versão atual do Nette está totalmente preparada para o PHP 8. Além disso, como presente, foi lançado também o Nette 2.4, que é totalmente compatível com ele, então, do ponto de vista do framework, nada impede que você comece a usar a nova versão.

Argumentos nomeados

E começamos logo com uma bomba, que pode ser considerada um game changer. Agora é possível passar argumentos para funções e métodos não apenas posicionalmente, mas também pelos seus nomes. O que é absolutamente ótimo no caso de um método ter muitos parâmetros:

class Response implements IResponse
{
	public function setCookie(
		string $name,
		string $value,
		string|DateInterface|null $time,
		string $path = null,
		string $domain = null,
		bool $secure = null,
		bool $httpOnly = null,
		string $sameSite = null
	) {
		...
	}
}

Passamos os dois primeiros argumentos posicionalmente, os seguintes pelo nome: (os nomeados devem sempre vir depois dos posicionais)

$response->setCookie('lang', $lang, sameSite: 'None');

// em vez do insano
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Antes da chegada deste recurso, estava planeado criar uma nova API no Nette para enviar cookies, porque o número de parâmetros de setCookie() realmente cresceu e a notação posicional era confusa. Agora isso não é mais necessário, porque os argumentos nomeados são, neste caso, a API mais prática. O IDE irá sugeri-los e eles têm verificação de tipo.

Também são ótimos para esclarecer parâmetros lógicos, onde seu uso não é necessário, mas true ou false por si só não dizem muito:

// antes
$db = $container->getService(Database::class, true);

// agora
$db = $container->getService(Database::class, need: true);

Os nomes dos parâmetros agora se tornam parte da API pública. Não é possível alterá-los arbitrariamente como antes. Por esse motivo, o Nette também está passando por uma auditoria para verificar se todos os parâmetros têm nomes apropriados.

Argumentos nomeados também podem ser usados em combinação com variadics:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// em $args estará ['b' => 2, 'c' => 3]

Agora, o array $args pode conter chaves não numéricas, o que é uma certa quebra de compatibilidade (BC break). O mesmo se aplica ao comportamento da função call_user_func_array($func, $args), onde as chaves no array $args agora desempenham um papel significativo. Por outro lado, as funções da família func_*() estão isoladas dos argumentos nomeados.

Com os argumentos nomeados também está intimamente relacionado o fato de que o operador splat ... agora pode desempacotar arrays associativos:

variadics(...['b' => 2, 'c' => 3]);

Curiosamente, isso ainda não funciona dentro de arrays:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

A combinação de argumentos nomeados e variadics oferece a possibilidade de finalmente ter uma sintaxe fixa, por exemplo, para o método link() do presenter, ao qual agora podemos passar argumentos nomeados da mesma forma que os posicionais:

// antes
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // tinha que ser um array

// agora
$presenter->link('Product:detail', $id, page: 1);

A sintaxe dos argumentos nomeados é muito mais sexy do que escrever arrays, portanto Latte adotou-o imediatamente, onde pode ser usado, por exemplo, nas tags {include} e {link}:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Voltaremos aos parâmetros nomeados na terceira parte em conexão com atributos.

Uma expressão pode lançar uma exceção

Lançar uma exceção agora é uma expressão. Você pode, por exemplo, envolvê-la em parênteses e adicioná-la a uma condição if. Hmmm, isso não parece muito prático. Mas isto já é mais interessante:

// antes
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('valor não definido');
}
$value = $arr['value'];


// agora, quando throw é uma expressão
$value = $arr['value'] ?? throw new \InvalidArgumentException('valor não definido');

Como as arrow functions até agora só podiam conter uma única expressão, graças a este recurso, elas podem lançar exceções:

// apenas uma expressão
$fn = fn() => throw new \Exception('ops');

Expressões Match

A construção switch-case tem duas grandes desvantagens:

  • usa comparação não estrita == em vez de ===
  • você precisa ter cuidado para não esquecer acidentalmente o break

Portanto, o PHP apresenta uma alternativa na forma da nova construção match, que usa comparação estrita e, por outro lado, não usa break.

Exemplo de código switch:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('ok');
        break;
    case 400:
        $message = $this->formatMessage('not found');
        break;
    case 500:
        $message = $this->formatMessage('server error');
        break;
    default:
        $message = 'unknown status code';
        break;
}

E o mesmo (apenas com comparação estrita) escrito usando match:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('not found'),
    500 => $this->formatMessage('server error'),
    default => 'unknown status code',
};

Note que match não é uma estrutura de controle como switch, mas uma expressão. No exemplo, atribuímos seu valor resultante a uma variável. Ao mesmo tempo, as “opções” individuais também são expressões, portanto, não é possível escrever vários passos, como no caso de switch.

Se nenhuma das opções corresponder (e não houver cláusula default), uma exceção UnhandledMatchError será lançada.

A propósito, no Latte também existem as tags {switch}, {case} e {default}. Seu funcionamento corresponde exatamente ao novo match. Elas usam comparação estrita, não exigem break e em case é possível indicar vários valores separados por vírgulas.

Operador Nullsafe

O encadeamento opcional (optional chaining) permite escrever uma expressão cuja avaliação para se encontrar null. E isso graças ao novo operador ?->. Ele substitui muito código que, de outra forma, verificaria repetidamente por null:

$user?->getAddress()?->street

// significa aprox.
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Por que “significa aprox.”? Porque, na realidade, a expressão é avaliada de forma mais inteligente e nenhum passo é repetido. Por exemplo, $user->getAddress() é chamado apenas uma vez, portanto, não pode ocorrer o problema causado pelo método retornar algo diferente na primeira e na segunda vez.

Esta novidade fresca foi trazida pelo Latte há um ano. Agora chega ao próprio PHP. Ótimo.

Promoção de propriedade do construtor

Açúcar sintático que economiza a escrita dupla do tipo e a escrita quádrupla da variável. Pena que não chegou na época em que não tínhamos IDEs tão inteligentes, que hoje escrevem isso por nós 🙂

class Facade
{
	private Nette\Database\Connection $db;
	private Nette\Mail\Mailer $mailer;

	public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
	{
		$this->db = $db;
		$this->mailer = $mailer;
	}
}
class Facade
{
	public function __construct(
		private Nette\Database\Connection $db,
		private Nette\Mail\Mailer $mailer,
	) {}
}

Funciona com o Nette DI, você pode começar a usar imediatamente.

Comportamento estrito de operadores aritméticos e bitwise

O que antes catapultou as linguagens de script dinâmicas para o estrelato, com o tempo se tornou seu ponto mais fraco. O PHP se livrou das “magic quotes”, do registro de variáveis globais e agora o comportamento relaxado está sendo substituído pela estrita. A época em que você podia somar, multiplicar, etc., em PHP quase qualquer tipo de dados para os quais isso não fazia o menor sentido, já passou há muito tempo. A partir da versão 7.0, o PHP está se tornando cada vez mais estrito e, a partir da versão 8.0, a tentativa de usar qualquer operador aritmético/bitwise em arrays, objetos ou resources resulta em um TypeError. A exceção é a adição de arrays.

// operadores aritméticos e bitwise
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Comparação mais sensata de strings e números

Ou seja, make loose operator great again.

Pareceria que para o operador frouxo == já não há lugar, que é apenas um erro de digitação ao escrever ===, mas esta mudança o coloca de volta no mapa. Já que o temos, que se comporte de forma sensata. A consequência da comparação “insensata” anterior foi, por exemplo, o comportamento de in_array(), que poderia te pegar de surpresa:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// surpreendentemente retornava true
// a partir do PHP 8.0 retorna false

A mudança no comportamento de == diz respeito à comparação de números e strings “numéricas” e é mostrada na tabela a seguir:

Comparação Antes PHP 8.0
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42 " true true
42 == "42foo" true false
42 == "abc42" false false
"42" == " 42" true true
"42" == "42 " false true

Surpreendente é que se trata de uma quebra de compatibilidade (BC break) na própria base da linguagem, que foi aprovada sem qualquer resistência. E isso é bom. Aqui o JavaScript poderia ter muita inveja.

Relatório de erros

Muitas funções internas agora lançam TypeError e ValueError em vez de avisos, que podiam ser facilmente ignorados. Vários avisos do núcleo foram reclassificados. O operador shutup @ agora não silencia erros fatais. E o PDO no modo padrão lança exceções.

O Nette sempre tentou resolver essas coisas de alguma forma. O Tracy modificava o comportamento do operador shutup, o Database alternava o comportamento do PDO, o Utils contém substitutos para funções padrão que lançam exceções em vez de avisos discretos, etc. É bom ver que a direção estrita, que está no DNA do Nette, está se tornando a direção nativa da linguagem.

Arrays com índice negativo

$arr[-5] = 'first';
$arr[] = 'second';

Qual será a chave do segundo elemento? Antes era 0, a partir do PHP 8 é -4.

Vírgula final

O último lugar onde não podia haver uma vírgula final era na definição de parâmetros de função. Isso já é passado:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // vírgula final
	) {
		....
	}

$object::class

A constante mágica ::class também funciona com objetos $object::class, substituindo completamente a função get_class().

catch sem variável

E finalmente: na cláusula catch não é necessário indicar uma variável para a exceção:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // sem $e
	$logger->log('....');
}

Nas próximas partes, veremos novidades significativas nos tipos de dados, mostraremos o que são atributos, quais novas funções e classes apareceram no PHP e apresentaremos o Just in Time Compiler.

David Grudl An artificial intelligence and web technology specialist, creator of the Nette Framework and other popular open-source projects. He writes for Uměligence, phpFashion, and La Trine blogs. He conducts AI training workshops and hosts the Tech Guys show. He's passionate about making artificial intelligence accessible through clear, practical explanations. Creative and pragmatic, he has a keen eye for real-world technology applications.