PHP 8.0: Visão geral completa das notícias (1/4)

há 4 anos De David Grudl  

A versão 8.0 do PHP está sendo lançada agora mesmo. Ela está cheia de coisas novas como nenhuma outra versão antes. Sua introdução merecia quatro artigos separados. No primeiro, vamos dar uma olhada no que ele traz no nível do idioma.

Antes de mergulharmos no PHP, saiba que a versão atual da Nette está totalmente preparada para a oitava versão. Além disso, como um presente, um Nette 2.4 totalmente compatível foi lançado, portanto do ponto de vista da estrutura não há nada que o impeça de usá-lo.

Argumentos nomeados

Vamos começar imediatamente com uma bomba, que poderia ser ousadamente designada como um jogo de mudança. Os argumentos podem agora ser passados a funções e métodos não apenas posicionados, mas de acordo com seu nome. O que é absolutamente legal no caso de um método ter realmente parâmetros demais:

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
	) {
		...
	}
}

Os dois primeiros argumentos são passados em posição, outros pelo nome: (os nomes devem seguir os posicionais)

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

// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Antes da introdução deste recurso, havia planos para criar uma nova API para envio de cookies na Nette, porque o número de argumentos para setCookie() realmente cresceu e a notação posicional era confusa. Isso não é mais necessário, porque os argumentos nomeados são, neste caso, a API mais conveniente. A IDE vai insinuá-los e há um tipo de segurança.

Eles são ideais mesmo para explicar argumentos lógicos, onde seu uso não é necessário, mas um simples true ou false não o corta:

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

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

Os nomes dos argumentos agora fazem parte do API público. Não é mais possível alterá-los à vontade. Por causa disso, até mesmo Nette está passando por uma auditoria determinando se todos os argumentos têm um nome adequado.

Os argumentos nomeados também podem ser usados em combinação com os variadíssimos:

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

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

A matriz $args pode agora conter até mesmo chaves não numéricas, o que é uma espécie de quebra BC. O mesmo se aplica ao comportamento de call_user_func_array($func, $args), onde as chaves da matriz $args desempenham agora um papel muito mais significativo. Pelo contrário, as funções da família func_*() estão protegidas de argumentos nomeados.

Os argumentos indicados estão intimamente relacionados ao fato de que o operador de splat ... pode agora expandir as matrizes associativas:

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

Surpreendentemente, não funciona dentro de matrizes no momento:

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

A combinação de argumentos nomeados e variadíssimos dá a opção de finalmente ter uma sintaxe fixa, por exemplo, para o método link(), ao qual podemos passar argumentos nomeados, bem como argumentos posicionais:

// before
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // had to be an array

// now
$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 argumentos nomeados na terceira parte da série em relação aos atributos.

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

Lançar uma exceção é agora uma expressão. Você pode embrulhá-la entre parênteses e acrescentá-la a uma condição if. Hmmm, isso não soa muito prático. No entanto, isto é muito mais interessante:

// before
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// now, when throw is an expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

Como até agora as funções de seta só podem conter uma expressão, agora elas podem lançar exceções graças a esta característica:

// only single expression
$fn = fn() => throw new \Exception('oops');

Expressão de jogo

A declaração switch-case tem dois vícios principais:

  • ela usa uma comparação não restrita == em vez de ===
  • você tem que ter cuidado para não esquecer break por engano

Por causa disso, o PHP vem com uma alternativa na forma de uma nova expressão match, que usa uma comparação rigorosa e, inversamente, não usa break.

switch exemplo de código:

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 uma comparação rigorosa) 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, nós 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 mais passos, como no caso do switch.

Em caso de não haver correspondência (e não há cláusula padrão), a exceção UnhandledMatchError é lançada.

A propósito, há também {switch}, {case} e {default} tags em Latte. Sua função corresponde exatamente ao novo match. Eles utilizam comparação rigorosa, não exigem break e é possível especificar múltiplos valores separados por vírgulas em case.

Operador Nullsafe

O encadeamento opcional permite escrever uma expressão, cuja avaliação cessa se encontrar nula. Isso é graças ao novo operador ?->. Ela substitui muitos códigos que, de outra forma, teriam que ser repetidamente checados por nulos:

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

// approximately translates to
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Por que “aproximadamente”? Porque na realidade a expressão está sendo avaliada de forma mais engenhosa para que nenhum passo seja repetido. Por exemplo, $user->getAddress() é chamado apenas uma vez, portanto o problema causado pelo método que devolve algo diferente pela primeira e segunda vez não pode ser encontrado.

Esta característica foi trazida pelo Latte há um ano. Agora o próprio PHP está adotando-o. Ótimo.

Promoção da propriedade dos construtores

Açúcar sintático que economiza a escrita do tipo duas vezes e a variável quatro vezes. É uma pena que não tenha vindo numa época em que não tínhamos idéias tão inteligentes que hoje o escrevemos para 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,
	) {}
}

Ele funciona com Nette DI, você pode começar a usá-lo.

Verificações mais rígidas para operadores aritméticos e bitwise

O que uma vez ajudou as linguagens dinâmicas de script a se destacar se tornou seu ponto mais fraco. Uma vez, o PHP se livrou de “citações mágicas”, o registro global de variáveis e agora o comportamento relaxado está sendo substituído pelo rigor. O tempo, quando em PHP você podia adicionar, multiplicar, etc. quase qualquer tipo de dado para o qual não fazia sentido, já se foi há muito tempo. A partir da versão 7.0, o PHP está se tornando cada vez mais rigoroso e desde a versão 8.0, uma tentativa de usar qualquer operador aritmético/bitwise em arrays, objetos ou recursos termina com o TypeError. A exceção são as adições de arrays.

// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Comparações entre números de cordel a cordel saner

Ou tornar o operador solto ótimo novamente.

Parece que não há mais espaço para o operador solto ==, que é apenas uma gralha ao escrever ===, mas esta mudança a devolve de volta ao mapa novamente. Se já o temos, deixe-o comportar-se razoavelmente. Como resultado da comparação anterior “irracional”, por exemplo, in_array() poderia te derrubar de forma desagradável:

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

dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns false

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

Comparação Antes do 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

Surpreendentemente, é uma pausa BC no cerne da linguagem, que foi aprovada sem qualquer resistência. E isso é bom. O JavaScript poderia ser muito invejoso a este respeito.

Relatório de erros

Muitas funções internas agora acionam o TypeError e ValueError ao invés de avisos que eram fáceis de ignorar. Uma série de avisos do kernel foram reclassificados. O operador de desligamento @ agora não silencia erros fatais. E a DOP lança exceções por padrão.

A Nette sempre tentou resolver essas coisas de alguma forma. Tracy modificou o comportamento do operador de desligamento, banco de dados trocou o comportamento das DOPs, Utils contêm substituições para funções padrão, que lançam exceções ao invés de avisos fáceis de serem ignorados, etc. É bom ver que a direção estrita que a Nette tem em seu DNA se torna a direção nativa do idioma.

Incrementos chave de matriz negativa

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

Qual será a chave do segundo elemento? Costumava ser 0, since PHP 8 it’s -4.

Vírgula de fuga

O último lugar, onde a vírgula de fuga não podia estar, era a definição de argumentos de função. Isto é uma coisa do passado:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // trailing comma
	) {
		....
	}

$object::class

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

Apanhar exceções apenas por tipo

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

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

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

  • Nas próximas partes, veremos grandes inovações em relação aos tipos de dados, mostraremos o que são atributos, que novas funções e classes apareceram no PHP e apresentaremos o Compilador Just in Time.*