Última atualização:

PHP 8.1 e suas novidades para 2022

Nawarian
Nawarian php

A equipe que gerencia o PHP lançou a versão 8.1 em Novembro de 2021, e na mesma data deixou de dar suporte à versão 7.3.

Portanto se você ainda está na versão 7.3 ou abaixo, é importantíssimo que você atualize! Bugs e falhas de segurança não serão mais corrigidas para estas versões!

😉 Veja também o PHP 8.2!

Em Novembro de 2022 é lançada a versão 8.2 do PHP. Eu escrevi uma publicação detalhada sobre as mudanças da linguagem para esta versão também.

Conteúdos deste artigo

Melhorias de performance

O PHP 8.1 vem com algumas otimizações de performance, mas nada absurdo como o que aconteceu entre as versões 5.6 e 7.0.

A realidade é que a máquina virtual do PHP já é bem otimizada e a partir da versão 8.0 vai ser difícil ver outro salto de performance como o que vimos no passado.

Alguns números oficiais que tirei da página oficial de lançamento do PHP:

Tempo de requisição necessário para carregar a aplicação Demo do framework Symfomy. Portanto quanto menor o tempo, melhor. Fonte: https://www.php.net/releases/8.1/pt_BR.php

Tempo de requisição necessário para carregar a aplicação Demo do framework Symfomy. Quanto menos, melhor.
Fonte: https://www.php.net/releases/8.1/pt_BR.php

Note que entre o PHP 7.4 há uma redução média de 392ms. Em comparação à versão 8.0, o PHP teve performance 23% mais rápida com a aplicação Demo do Symfony, e 3,5% mais rápida no WordPress.

Para quem olha estes números crus parece pouco, mas num ambiente onde se precisa trafegar milhares ou dezenas de milhares de requisições por segundo, isto faz uma diferença enorme!

Novas funcionalidades da linguagem

Quando o PHP 8.0 saiu, nós tivemos uma recepção muito bacana pela comunidade e várias funcionalidades promissoras como o Compilador Just in Time.

O PHP 8.1 não deixou a desejar e trouxe ainda mais coisas que a comunidade vem pedindo há bastante tempo. Enums, imutabilidade, avanços no motor de verificações de tipos, desempacotamento de arrays...

Vamos à lista:

Enumerações (enums)

A proposta para lançar enums foi escrita no final de 2020 e conduzida por Larry Garfield (@Crell) e Ilija Tovilo (@IlijaTovilo). Você pode visitar a RFC através deste link.

O principal efeito de ter enums integrados à linguagem, é que nós vamos poder parar de usar constantes de classe para indicar quais os valores possíveis para uma função/método.

Para quem não vê, ou para quem quer copiar e colar, abaixo a transcrição do código presente na imagem acima:

enum Status {
case Draft;
case Published;
case Archived;
}

function setStatus(Status $status)
{
// ...
}

setStatus(Status::Draft); // OK
setStatus(Status::Archived); // OK
setStatus('codamos.com.br'); // TypeError X_X'

No código acima a função setStatus() recebe um argumento do tipo enum Status, que só pode ter três valores: Status::Draft, Status::Published ou Status::Archived.

Qualquer valor diferente que for passado para aquela função vai gerar uma exceção do tipo TypeError.

Desempacotamento de arrays com chaves do tipo string

Desde o PHP 7.4 já era possível desempacotar arrays, mas as chaves do tipo string seriam ignoradas.

Agora no PHP 8.1 é possível desempacotar arrays com chaves do tipo string, e a precedência segue a mesma regra do array_merge().

$array1 = ['a' => 1];
$array2 = ['b' => 2];
$final = ['a' => 0, ...$array1, ...$array2];

var_dump($final); // ['a' => 1, 'b' => 2]

Fibers ou Fibras

A RFC de fibers causou um alvoroço enorme na comunidade, especialmente no time que desenvolve o Swoole.

Fibras são um mecanismo de paralelismo também conhecido como green threads. A ideia deste mecanismo é que você possa gerenciar interrupções no fluxo de execução do seu código.

Provavelmente você não vai utilizar este mecanismo no seu dia a dia, mas frameworks assíncronos nativos como o React PHP e Amphp vão tirar muito proveito desta funcionalidade.

A RFC trás um exemplo simples de como as fibras funcionam. O segredo aqui é pensar que o código está constantemente interrompendo a execução um do outro:

$fiber = new Fiber(function (): void {
  $value = Fiber::suspend('fiber');
  echo "Valor utilizado ao retomar a execução da fibra: {$value}\n";
});

$value = $fiber->start();
echo "Valor da suspensão da fibra: {$value}\n";
$fiber->resume('test');

O código acima executa na seguinte ordem:

  1. Instancia um objeto $fiber com uma função callback que será executada assim que o método start() for chamado;

  2. Chama o método start(), que efetivamente executa o callback definido no objeto $fiber e armazena seu retorno;

  3. O callback executa e, em sua primeira linha, suspende a fibra utilizando o método Fiber::suspend() e passando string(fiber) como parâmetro. Isto retorna a execução para a linha anterior e joga o valor “fiber” para a variável $value;

  4. Um echo executa, utilizando a variável $value, que neste momento possui o valor string(fiber);

  5. Retomamos a execução do callback ao chamar o método resume(). Passamos o valor string(test) como parâmetro, que será transmitido àquela variável $value dentro do callback;

  6. O echo de dentro do callback executa, utilizando a variável $value, que neste momento possui o valor string(test).

Propriedades imutáveis (somente leitura)

Lembra daquela época quando a gente escrevia classes como essa abaixo?

class Person
{
public string $name;

public static function create(string $name): self
{
$instance = new self();
$instance->name = $name;

return $instance;
}

public function setName(string $name): Self
{
return self::create($name);
}
}

De acordo com a imagem acima, sempre que quiséssemos modificar uma propriedade da classe, criaríamos uma nova instância para defender a imutabilidade do objeto atual.

A partir do PHP 8.1 isto já não é mais necessário, porque podemos definir propriedades como imutáveis desde a sua criação. Segue o snippet abaixo:

class Person
{
public function __construct(public readonly string $name)
{}
}

Aqui eu utilizei o constructor promotion, que foi introduzido no PHP 8.0. Mas adicionei uma palavra-chave ali: readonly.

Com a palavra-chave readonly nós podemos indicar que propriedades das classes só podem ser escritas uma vez, dentro do método __construct().

Portanto tanto faz se você deixa aquela propriedade com visibilidade pública ou não, porque nada vai poder alterar aquela propriedade. Adeus getters e setters!

Nova sintaxe de Callables

O PHP já tem uma sintaxe para referenciar classes, que funciona mais ou menos assim: $name = MinhaClasse::class;, onde $name armazena a string "\MinhaClasse".

Isso é extremamente útil pra fazer referência ao código de forma dinâmica para quem escreve. Por exemplo, o framework Slim nos permite definir rotas da seguinte forma:

$app->get('/', MinhaClasse::class);

O problema com a forma acima é que para aquela rota funcionar, a classe \MinhaClasse precisa implementar o método público __invoke(). Existe uma forma de contornar a situação, como em [MinhaClasse::class, 'meuMetodo'] - mas esta sintaxe se confunde com a de arrays.

Com o PHP 8.1 é possível indicar a classe e método de forma nativa e distinta na linguagem. A sintaxe é a seguinte:

$len = strlen(...); // função
$invoke = $obj(...); // objeto com método __invoke()
$metodo = $obj->metodo(...); // método de um objeto
// método estático de uma classe
$metodoEstatico = MinhaClasse::metodoEstatico(...);

Desta forma, é possível imaginar que num release futuro do framework Slim a gente consiga ver algo assim:

$app->get('/person', $person->list(...));
$app->post('/person', $person->create(...));

Eu esperaria ver esta sintaxe criar impacto direto em frameworks e bibliotecas que lidam com:

  • Rotas (Slim, Mezzio...)
  • Injeção de dependências (PHP DI)
  • Event Loop (ReactPHP, Swoole, AMP...)
  • Logging (Monolog)
  • Testes (PHPUnit, Pest...)

Utilizar new como valor padrão e em atributos

Você já deve ter visto que para argumentos opcionais é possível dar um valor padrão na assinatura da função ou método. Algo assim:

function minhaFuncao(int $a = 10) {
var_dump($a);
}

minhaFuncao(); // int(10)

Repare que ao chamar minhaFuncao() sem parâmetro algum, a máquina virtual do PHP assume que deveria utilizar o valor padrão 10. Mas esta sintaxe só funciona com valores primitivos.

🧙🏻‍♂️ Se você ainda não se familiarizou com os termos sobre tipos em PHP, dá uma olhada neste artigo que eu escrevi sobre tipos no PHP 😉

Desde o PHP 8.1 também é possível utilizar objetos como valor padrão. Um caso de uso que eu acho particularmente útil é o de utilização de datas. Veja um exemplo de como isso muda no PHP 8.1:

class MinhaClasse
{
public function __construct(
public DateTimeInterface $createdAt = new DateTimeImmutable('now')
) {}
}

$obj1 = new MinhaClasse(new DateTimeImmutable('tomorrow'));
$obj2 = new MinhaClasse(); // $obj2->createdAt é um DateTimeImmutable('now')

Com o construtor acima nós definimos a propriedade DateTimeInterface $createdAt, e caso ela seja omitida ao instanciar o objeto nós adotamos como valor padrão uma nova instância de DateTimeImmutable.

Interseção de tipos

Desde o PHP 8.0 nós recebemos suporte à União de Tipos (union types). Que nos permite dizer que determinada variável é do tipo A ou B, como abaixo:

function minhaFuncao(int|float $numero) {}

No escopo da função acima, a variável $numero pode ser do tipo int ou float, mas nunca os dois ao mesmo tempo.

A versão 8.1 do PHP trouxe a Interseção de tipos (intersection types), que nos permite indicar que uma variável é de tipo A e B ao mesmo tempo. Um claro exemplo que interseção de tipos que me vem à mente são mocks em testes, veja o exemplo abaixo:

class MyTest extends TestCase
{
private MinhaClasse&MockObject $mock;

...
public function testAlgumaCoisa()
{
$this->mock = $this->prophesize(MinhaClasse::class)->reveal();
...
}
}

No exemplo acima a propriedade $mock é do tipo MinhaClasse e MockObject ao mesmo tempo.

O tipo de retorno never

Já se viu numa situação em que você chama uma função, escreve mais algumas operações em seguida mas o código nunca executa estas operações? Eu já caí em algo assim:

// ...

$redirect = redirect('/');
$this->logger->log('Redirecting to "/"');

E a linha $this->logger->log() não executava NUNCA. O motivo? A função redirect() enviava os cabeçalhos HTTP e depois dava um exit(0), simples assim.

Como adivinhar uma coisa dessas? Lendo código alheio, usando o depurador... e testar isso é um inferno na terra!

O tipo de retorno never vem justamente pra deixar este comportamento mais explícito. Se usa assim:

function redirect(string $path): never
{
header("Location: {$path}");
exit(0);
}

Ao utilizar o tipo de retorno never você diz à máquina virtual do PHP, e à próxima pessoa que ler a assinatura da sua função/método, que aquela função necessariamente vai finalizar o processo PHP.

🧙🏻‍♂️ Eu já comentei no meu artigo sobre como programar por intenção o quão poderoso uma coisa dessas é. Com esta sintaxe você consegue comunicar intenção e comportamento para quem lê o código.

Constantes de classe finais

Até o PHP 8.0 era possível sobrescrever constantes de classes, e agora com a versão 8.1 da linguagem é possível adicionar o modificador final para as constantes de classe de forma que nunca sejam sobrescritas.

A utilização fica assim:

class Primeira
{
  final public const MINHA_CONST = 'foo';
}

class Segunda
{
  public const MINHA_CONST = 'bar'; // Erro fatal
}

Nova sintaxe para números octais

A linguagem PHP permite escrever números em diferentes bases numéricas, seguindo as sintaxes a seguir:

  • Base binária, usando o prefixo 0b como em 0b10010;
  • Base octal, usando o prefixo 0 como em 016;
  • Base decimal, sem prefixo algum como em 16;
  • Base hexadecimal, usando o prefixo 0x como em 0x16

🧙🏻‍♂️ Se você não entende bem o que essas bases todas significam, dá uma olhada no meu artigo sobre operações binárias (bitwise) em PHP 😉.

O atual prefixo para números octais faz parecer com que o primeiro 0 é parte do número em si, e pode gerar resultados confusos como abaixo:

016 === 16 // false -> 016 (octal) é 14 (decimal)

Para resolver isto, um novo prefixo foi adicionado à linguagem para representar números octais: 0o.

Desta forma o código acima fica menos confuso, porque fica claro que o lado esquerdo usa a notação octal:

0o16 === 16 // false
0o16 === 14 // true

Conclusão

O PHP 8.1 está uma delícia, com diversas adições na linguagem e ferramentas para tornar o nosso fluxo de trabalho bem mais agradável, seguro e performático!

Num futuro eu pretendo escrever sobre Fibers com maior atenção, porque este conteúdo simplesmente não cabe neste artigo. Me segue no Twitter pra ficar sabendo quando o artigo sair 😉.

Até a próxima 👋

Comentários