PHP 8.0: Novas Funções, Classes e JIT (4/4)

há 4 anos De David Grudl  

A versão 8.0 do PHP foi lançada. Está cheia de novas funcionalidades como nenhuma outra versão antes. Sua introdução mereceu quatro artigos separados. Na última parte vamos dar uma olhada nas novas funções e classes e introduzir o Compilador Just in Time.

Novas funções

A biblioteca PHP padrão tem centenas de funções e na versão 8.0 apareceram seis novas funções. Não parece muito, mas a maioria delas corrige pontos fracos da linguagem. O que se alinha muito bem com todo o conceito da versão 8.0, que aperta e consolida o PHP como nenhuma versão antes. Uma visão geral de todas as novas funções e métodos pode ser encontrada no guia de migração.

str_contains() str_starts_with() str_ends_with()

Funções para determinar se uma corda começa, termina ou contém um substrato.

if (str_contains('Nette', 'te')) {
	...
}

Com o advento desta trindade, PHP define como lidar com uma cadeia vazia enquanto procura, que é o que todas as outras funções relacionadas aderem, e que é uma cadeia vazia é encontrada em todos os lugares:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Graças a isso, o comportamento da trindade é completamente idêntico ao dos análogos de Nette:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Por que essas funções são tão importantes? As bibliotecas padrão de todos os idiomas são sempre sobrecarregadas pelo desenvolvimento histórico; inconsistências e erros não podem ser evitados. Mas, ao mesmo tempo, é um testemunho do respectivo idioma. Surpreendentemente, o PHP de 25 anos não tem funções para operações tão básicas como retornar o primeiro ou último elemento de um array, escapar do HTML sem surpresas desagradáveis (htmlspecialchars não escapa de um apóstrofo), ou apenas procurar por uma corda em uma corda. Ele não sustenta que ele pode ser somente contornado, porque o resultado não é um código legível e compreensível. Esta é uma lição para todos os autores da API. Quando você vê que grande parte da documentação da função é ocupada por explicações de armadilhas (como os valores de retorno de strpos), é um sinal claro para modificar a biblioteca e adicionar str_contains.

get_debug_type()

Substitui o agora obsoleto get_type(). Em vez de tipos longos como integer, ele devolve o hoje utilizado int, no caso de objetos, ele devolve diretamente o tipo:

Value gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Recurso para a migração de objetos

Os valores do tipo de recurso vêm de uma época em que o PHP ainda não tinha objetos, mas na verdade precisava deles. Foi assim que nasceram os recursos. Hoje temos objetos e, comparados aos recursos, eles funcionam muito melhor com o coletor de lixo, então o plano é substituí-los todos gradualmente por objetos.

A partir do PHP 8.0, o recurso images, curl joins, openssl, xml, etc. é alterado para objetos. No PHP 8.1, as conexões FTP, etc. serão seguidas.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Estes objetos ainda não têm nenhum método, nem podem ser instanciados diretamente. Até agora, é realmente apenas uma questão de se livrar de recursos obsoletos do PHP sem mudar a API. E isso é bom, porque criar uma boa API é uma tarefa separada e desafiadora. Ninguém deseja a criação de novas classes PHP como o SplFileObject com métodos chamados fgetc() ou fgets().

PhpToken

O tokenizer e as funções em torno de token_get_all também são migrados para objetos. Desta vez não se trata de nos livrarmos de recursos, mas obtemos um objeto completo representando um token PHP.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

Método isIgnorable() retorna verdadeiro para fichas T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, e T_OPEN_TAG.

Mapas fracos

Os mapas fracos estão relacionados ao coletor de lixo, que libera todos os objetos e valores que não são mais usados da memória (ou seja, não há nenhuma variável ou propriedade que os contenha). Como os tópicos PHP são de curta duração e temos muita memória disponível em nossos servidores, normalmente não tratamos de questões relativas à liberação efetiva de memória. Mas, para scripts mais longos, eles são essenciais.

O objeto WeakMap é similar ao SplObjectStorage Ambos usam objetos como chaves e permitem que valores arbitrários sejam armazenados sob elas. A diferença é que o WeakMap não impede que o objeto seja liberado pelo coletor de lixo. Ou seja, se o único lugar onde o objeto existe atualmente for uma chave no mapa fraco, ele será removido do mapa e da memória.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

Para que é bom? Por exemplo, para o caching. Vamos ter um método loadComments() para passarmos um artigo no blog e ele retorna todos os seus comentários. Já que o método é chamado repetidamente para o mesmo artigo, criaremos outro getComments(), que guardará em cache o resultado do primeiro método:

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

A questão é que quando o objeto $article é liberado (por exemplo, o aplicativo começa a trabalhar com outro artigo), sua entrada também é liberada do cache.

PHP JIT (Compilador Just in Time)

Você deve saber que o PHP é compilado no chamado código opc, que são instruções de baixo nível que você pode ver aqui, por exemplo e que são executadas por uma máquina virtual PHP. E o que é um JIT? O JIT pode compilar de forma transparente o PHP diretamente em código de máquina, que é executado diretamente pelo processador, para que a execução mais lenta pela máquina virtual seja contornada.

O JIT tem, portanto, a intenção de acelerar o PHP.

O esforço para implementar o JIT em PHP data de 2011 e é apoiado por Dmitry Stogov. Desde então, ele tentou 3 implementações diferentes, mas nenhuma delas entrou em uma versão final do PHP por três razões: o resultado nunca foi um aumento significativo na performance para aplicações web típicas; complica a manutenção do PHP (ou seja, ninguém além de Dmitry entende isso 😉); havia outras formas de melhorar a performance sem ter que usar um JIT.

O aumento de desempenho observado no PHP versão 7 foi um subproduto do trabalho no JIT, embora paradoxalmente não tenha sido implantado. Isto só está acontecendo agora no PHP 8. Mas eu vou conter expectativas exageradas: você provavelmente não verá nenhuma aceleração.

Então por que o JIT está entrando no PHP? Primeiro, outras formas de melhorar o desempenho estão lentamente se esgotando, e o JIT é simplesmente o próximo passo. Em aplicações web comuns, ele não traz nenhuma melhoria de velocidade, mas acelera significativamente, por exemplo, os cálculos matemáticos. Isto abre a possibilidade de começar a escrever estas coisas em PHP. Na verdade, seria possível implementar algumas funções diretamente em PHP que anteriormente exigiam uma implementação direta em C devido à velocidade.

JIT é parte da extensão opcache e é habilitada junto com ela em php.ini (leia a documentação sobre esses quatro dígitos):

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

Você pode verificar se o JIT está funcionando, por exemplo, no painel de informações do Tracy Bar.

O JIT funciona muito bem se todas as variáveis tiverem tipos claramente definidos e não podem mudar mesmo quando se chama o mesmo código repetidamente. Portanto, estou me perguntando se um dia estaremos declarando tipos em PHP para variáveis também: string $s = 'Bye, this is the end of the series';