PHP 8.0: Visão geral completa das novidades (1/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 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.
Faça o login para enviar um comentário