O PHP 8 foi oficialmente lançado para o General Availability em 26 de novembro de 2020!

Esta nova grande atualização traz muitas otimizações e funcionalidades poderosas para linguagem. Estamos empolgados para guiá-lo através das mudanças mais interessantes que nos permitirão escrever melhor código e construir aplicativos mais robustos.

PHP 8.0 Anúncio Adendo
PHP 8.0 Anúncio Adendo

Você está pronto? Vamos começar!

PHP JIT (compilador Just in Time)

A característica mais aclamada que vem com o PHP 8 é o compilador Just-in-time (JIT). Do que se trata o JIT?

A proposta da RFC descreve o JIT da seguinte forma:

“O PHP JIT é implementado como uma parte quase independente do OPcache. Ele pode ser habilitado/desabilitado em tempo de compilação e em tempo de execução do PHP. Quando habilitado, o código nativo dos arquivos PHP é armazenado em uma região adicional da memória compartilhada do OPcache e op_array→opcodes[].handler(s) mantém apontadores para os pontos de entrada do código JIT-ed”

Então, como chegamos ao JIT, e qual é a diferença entre JIT vs OPcache?

Para entender melhor o que é JIT para PHP, vamos dar uma rápida olhada em como o PHP executa o código fonte até o resultado final.

A execução do PHP é um processo de 4 etapas:

  • Compilação: O intérprete atravessa a árvore e traduz os nós AST em opcodes Zend de baixo nível, que são identificadores numéricos que determinam o tipo de instrução executada pela Zend VM.
  • Interpretação: Os opcodes são interpretados e executados na Zend VM.

A imagem a seguir mostra uma representação visual do processo básico de execução do PHP.

Processo básico de execução do PHP
Processo básico de execução do PHP

Então, como o OPcache torna o PHP mais rápido? E que mudanças no processo de execução com o JIT?

A extensão OPcache

PHP é uma linguagem interpretada. Isto significa que, quando um script PHP é executado, o intérprete analisa, compila e executa o código uma e outra vez a cada solicitação. Isto pode resultar em desperdício de recursos da CPU e tempo adicional.

É aqui que entra a extensão OPcache para jogar:

“OPcache melhora a performance do PHP ao armazenar bytecode de script pré-compilado na memória compartilhada, removendo assim a necessidade do PHP carregar e analisar scripts em cada solicitação”

Com o OPcache ativado, o interpretador PHP passa pelo processo de 4 etapas mencionado acima apenas quando o script é executado pela primeira vez. Como os bytecodes PHP são armazenados em memória compartilhada, eles estão imediatamente disponíveis como representação intermediária de baixo nível e podem ser executados na Zend VM imediatamente.

Processo de execução PHP com OPcache habilitado
Processo de execução PHP com OPcache habilitado

A partir do PHP 5.5, a extensão Zend OPcache está disponível por padrão, e você pode verificar se você a tem configurada corretamente, simplesmente ligando para phpinfo() a partir de um script em seu servidor ou verificando seu arquivo php.ini (veja as configurações de configuração do OPcache).

Leitura sugerida: Como melhorar o limite de memória PHP no WordPress.

Zend OPcache section in a phpinfo page
Zend OPcache section in a phpinfo page

Pré-carga

OPcache foi recentemente melhorado com a implementação do pré-carregamento, um novo recurso OPcache adicionado com PHP 7.4. O pré-carregamento fornece uma maneira de armazenar um conjunto específico de scripts na memória OPcache“antes que qualquer código do aplicativo seja executado”. “Ainda assim, ele não traz melhorias tangíveis de performance para aplicativos típicas baseadas na web.

Você pode ler mais sobre pré-carrgamento em nossa introdução ao PHP 7.4.

Com o JIT, o PHP dá um passo à frente.

JIT – O compilador Just in Time

Mesmo que os opcodes sejam de baixo nível de representação intermediária, eles ainda têm que ser compilados em código de máquina. JIT “não introduz nenhuma forma adicional IR (Intermediate Representation)”, mas usa DynASM (Dynamic Assembler for code generation engines) para gerar código nativo diretamente do byte-código PHP.

Em resumo, o JIT traduz as partes quentes do código intermediário em código de máquina. Contornando a compilação, ele seria capaz de trazer melhorias consideráveis na performance e uso de memória.

Zeev Surasky, co-autor da proposta PHP JIT, mostra o quanto os cálculos seriam mais rápidos com o JIT:

Mas, o JIT efetivamente melhoraria o desempenho do WordPress?

JIT para aplicativos web em tempo real

De acordo com a RFC do JIT, a implementação do compilador just in time deve melhorar o desempenho do PHP. Mas realmente experimentaríamos tais melhorias em aplicativos da vida real como WordPress?

Os primeiros testes mostram que o JIT faria com que as cargas de trabalho intensivas da CPU funcionassem significativamente mais rápido. No entanto, o RFC adverte:

“… como as tentativas anteriores – atualmente não parece melhorar significativamente aplicativos da vida real como WordPress (com opcache.jit=1235 326 req/sec vs 315 req/sec).

Está previsto um esforço adicional, melhorando o JIT para aplicativos em tempo real, usando perfis e otimizações especulativas”

Com o JIT habilitado, o código não seria executado pela Zend VM, mas pela própria CPU, o que melhoraria a velocidade de cálculo. Aplicativos Web como WordPress também dependem de outros fatores como TTFB, otimização de banco de dados, solicitações HTTP, etc.

Contribuição relativa do JIT para o desempenho do PHP 8
Contribuição relativa do JIT para o desempenho do PHP 8 (Fonte de imagem: PHP 8.0 Announcement Addendum)

Portanto, não devemos esperar um aumento significativo na velocidade de execução do PHP quando se trata de WordPress e aplicativos semelhantes. No entanto, o JIT poderia trazer vários benefícios para os desenvolvedores.

De acordo com Nikita Popov:

“Os benefícios do compilador JIT são, (e como já foi delineado no RFC):

  • Desempenho significativamente melhor para código numérico.
  • Desempenho ligeiramente melhor para código “típico” de aplicativo web PHP.
  • O potencial para mover mais código de C para PHP, porque o PHP agora será suficientemente rápido”

Então, enquanto o JIT dificilmente trará grandes melhorias na performance do WordPress, ele estará atualizando o PHP para o próximo nível, fazendo dele uma linguagem na qual muitas funções poderiam agora ser escritas diretamente.

O lado negativo seria a maior complexidade que pode aumentar a manutenção, estabilidade e custos de depuração. De acordo com Dmitry Stogov:

“O JIT é extremamente simples, mas de qualquer forma ele aumenta o nível de toda a complexidade do PHP, o risco de novos tipos de bugs e o custo de desenvolvimento e manutenção”

A proposta de incluir o JIT no PHP 8 foi aprovada com 50 a 2 votos.

Melhorias e novas funcionalidades do PHP 8

Além do JIT, nós podemos esperar muitas funcionalidades e melhorias com o PHP 8. A lista a seguir é nossa seleção das próximas adições e mudanças que devem tornar o PHP mais confiável e eficiente.

Promoção de propriedade dos construtores

Como resultado de uma discussão contínua sobre a melhoria da ergonomia dos objetos em PHP, a Constructor Property Promotion RFC propõe uma nova e mais concisa sintaxe que simplificará a declaração de propriedade, tornando-a mais curta e menos redundante.

Esta proposta se refere apenas aos parâmetros promovidos, ou seja, aqueles parâmetros do método prefixados com palavras-chave de visibilidade  publicprotected, e private.

Atualmente, todas as propriedades têm que ser repetidas várias vezes (pelo menos quatro vezes) antes que possamos usá-las com objetos. Considere o seguinte exemplo da RFC:

class Point {
    public int $x;
    public int $y;
    public int $z;

    public function __construct(
        int $x = 0,
        int $y = 0,
        int $z = 0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

De acordo com Nikita Popov, a autora do RFC, nós temos que escrever o nome da propriedade pelo menos quatro vezes em três lugares diferentes: a declaração da propriedade, os parâmetros do construtor e a atribuição da propriedade. Esta sintaxe não é particularmente utilizável, especialmente em classes com muitas propriedades e nomes mais descritivos.

Esta RFC se propõe a fundir o construtor e a definição dos parâmetros. Assim, a partir do PHP 8, nós temos uma maneira mais utilizável de declarar parâmetros. O código visto acima pode mudar como mostrado abaixo:

class Point {
    public function __construct(
        public int $x = 0,
        public int $y = 0,
        public int $z = 0,
    ) {}
}

E é isso aí. Então nós temos uma nova maneira de promover propriedades que são mais curtas, mais legíveis, e menos propensas a erros. De acordo com Nikita:

É uma simples transformação sintática que estamos fazendo. Mas isso reduz a quantidade de código de placa de caldeira que você tem que escrever para objetos de valor em particular…

A declaração de propriedade é transformada como tínhamos declarado explicitamente essas propriedades, e podemos usar a API Reflection para introspectar definições de propriedade antes da execução (veja Desugaring):

Reflection (e outros mecanismos de introspecção) observará o estado após o desugaring. Isto significa que as propriedades promovidas aparecerão da mesma forma que as propriedades explicitamente declaradas, e os argumentos dos construtores promovidos aparecerão como argumentos dos construtores comuns.

// before desugaring
class Point {
    public function __construct(public int $x = 0) {}
}

// after desugaring
class Point {
    public int $x;

    public function __construct(int $x = 0) {
        $this->x = $x;
    }
}

Herança

Nós não temos nenhuma limitação no uso da herança em conjunto com os parâmetros promovidos. De qualquer forma, não há uma relação particular entre pais e filhos construtores de classes. De acordo com Nikita:

Normalmente, nós dizemos que os métodos sempre têm que ser compatíveis com o método dos pais. […] mas esta regra não se aplica para o construtor. Então o construtor realmente pertence a uma única classe, e construtores entre classe pai e criança não têm que ser compatíveis de nenhuma forma.

Aqui está um exemplo:

class Test {
    public function __construct(
        public int $x = 0
    ) {}
}

class Child extends Test {
    public function __construct(
        $x, 
        public int $y = 0,
        public int $z = 0,
    ) {
        parent::__construct($x);
    }
}

O que não é permitido com as propriedades promovidas

As propriedades promovidas são permitidas em construtores não abstratos e funcionalidades, mas há várias limitações que vale a pena mencionar aqui.

Construtores abstratos

Propriedades promovidas não são permitidas em classes e interfaces abstratas:

abstract class Test {
    // Error: Abstract constructor.
    abstract public function __construct(private $x);
}
 
interface Test {
    // Error: Abstract constructor.
    public function __construct(private $x);
}
Nullability

Uma das restrições mais notáveis está relacionada à nulidade. Anteriormente, nós usávamos um tipo que não era explicitamente anulável. Mas com um valor padrão nulo, o tipo era implicitamente anulável. Mas com tipos de propriedade, nós não temos este comportamento implícito porque os parâmetros promovidos requerem uma declaração de propriedade, e o tipo nulo deve ser declarado explicitamente. Veja o seguinte exemplo da RFC:

class Test {
    // Error: Using null default on non-nullable property
    public function __construct(public Type $prop = null) {}

    // Correct: Make the type explicitly nullable instead
    public function __construct(public ?Type $prop = null) {}
}
Tipo Callable

Como o tipo chamável não é um tipo suportado para propriedades, não estamos autorizados a usar o tipo chamável em propriedades promovidas:

class Test {
    // Error: Callable type not supported for properties.
    public function __construct(public callable $callback) {}
}
A palavra-chave var não é permitida

Somente uma palavra-chave de visibilidade pode ser usada com parâmetros promovidos, portanto não é permitido declarar propriedades do construtor com a palavra-chave var (veja o seguinte exemplo do RFC):

class Test {
    // Error: "var" keyword is not supported.
    public function __construct(var $prop) {}
}
Sem duplicações permitidas

Nós podemos combinar propriedades promovidas e propriedades explícitas na mesma classe, mas as propriedades não podem ser declaradas duas vezes:

class Test {
    public string $prop;
    public int $explicitProp;

    // Correct
    public function __construct(public int $promotedProp, int $arg) {
        $this->explicitProp = $arg;
    }

    // Error: Redeclaration of property.
    public function __construct(public string $prop) {}
}
Parâmetros Variadic não são permitidos

A razão aqui é que o tipo declarado é diferente do parâmetro variádico, que na verdade é um array:

class Test {
    // Error: Variadic parameter.
    public function __construct(public string ...$strings) {}
}

Leituras adicionais

Para uma visão mais próxima na Costructor Property Promotion, ouça esta entrevista com Nikita Popov. Para uma visão aprofundada da ergonomia dos objetos em PHP, veja este post e a seguinte entrevista com Larry Garfield.

Validação para métodos de traços (trait ) abstratos

Os traços são definidos como “um mecanismo para reutilização de código em linguagens de herança única, como PHP” Tipicamente, eles são usados para declarar métodos que podem ser usados em múltiplas classes.

Um traço também pode conter métodos abstratos. Estes métodos simplesmente declaram a assinatura do método, mas a implementação deve ser feita dentro da classe que utiliza o traço.

De acordo com o manual PHP,

“Traços apoiam o uso de métodos abstratos a fim de impor exigências à classe expositora”

Isto também significa que as assinaturas dos métodos devem coincidir. Em outras palavras, o tipo e o número de argumentos requeridos precisam ser os mesmos.

De qualquer forma, de acordo com Nikita Popov, autora do RFC, a validação da assinatura é atualmente aplicada apenas de forma pontual:

O seguinte exemplo de Nikita se refere ao primeiro caso (assinatura não forçada):

trait T {
	abstract public function test(int $x);
}
 
class C {
	use T;

	// Allowed, but shouldn't be due to invalid type.
	public function test(string $x) {}
}

Dito isto, esta RFC propõe sempre lançar um erro fatal se o método de implementação não for compatível com o método do traço abstrato, independentemente de sua origem:

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

Esta RFC foi aprovada por unanimidade.

Assinaturas incompatíveis do método

Em PHP, erros de herança devido a assinaturas de métodos incompatíveis lançam ou um erro fatal ou um aviso, dependendo do que está causando o erro.

Se uma classe está implementando uma interface, assinaturas de métodos incompatíveis lançam um erro fatal. De acordo com a documentação da interface de objetos:

“A classe implementando a interface deve usar uma assinatura de método que seja compatível com LSP (Liskov Substitution Principle). Não fazer isso resultará em um erro fatal”

Aqui está um exemplo de um erro de herança com uma interface:

interface I {
	public function method(array $a);
}
class C implements I {
	public function method(int $a) {}
}

No PHP 7.4, o código acima lançaria o seguinte erro:

Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7

Uma função em uma child classe com uma assinatura incompatível lançaria um aviso. Veja o seguinte código da RFC:

class C1 {
	public function method(array $a) {}
}
class C2 extends C1 {
	public function method(int $a) {}
}

No PHP 7.4, o código acima simplesmente lançaria um aviso:

Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Agora, esta RFC propõe sempre atirar um erro fatal para assinaturas de métodos incompatíveis. Com o PHP 8, o código que vimos anteriormente acima iria provocar o seguinte:

Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

Arrays começando com um índice negativo

Em PHP, se um array começa com um índice negativo (start_index < 0), os seguintes índices começarão a partir de 0 (mais sobre isso na documentaçãoarray_fill ). Veja o exemplo a seguir:

$a = array_fill(-5, 4, true);
var_dump($a);

No PHP 7.4, o resultado seria o seguinte:

array(4) {
	[-5]=>
	bool(true)
	[0]=>
	bool(true)
	[1]=>
	bool(true)
	[2]=>
	bool(true)
}

Agora, este RFC propõe mudar as coisas para que o segundo índice seja start_index + 1, qualquer que seja o valor de start_index.

No PHP 8, o código acima resultaria na seguinte matriz:

array(4) {
	[-5]=>
	bool(true)
	[-4]=>
	bool(true)
	[-3]=>
	bool(true)
	[-2]=>
	bool(true)
}

Com o PHP 8, arrays começando com um índice negativo mudam seu comportamento. Leia mais sobre as incompatibilidades para trás na RFC.

Union Types 2.0

Union Types 2.0 aceitam valores que podem ser de diferentes tipos. Atualmente, o PHP não oferece suporte a tipos de união, exceto para a sintaxe ?Type e para o tipo especial iterable.

Antes do PHP 8, os tipos de união só podiam ser especificados em anotações phpdoc, como mostrado no exemplo a seguir da RFC:

class Number {
	/**
	 * @var int|float $number
	 */
	private $number;

	/**
	 * @param int|float $number
	 */
	public function setNumber($number) {
		$this->number = $number;
	}

	/**
	 * @return int|float
	 */
	public function getNumber() {
		return $this->number;
	}
}

Agora, a RFC do tipo 2. 0 propõe adicionar suporte para tipos de sindicatos em assinaturas de funções, para que não confiemos mais na documentação em linha, mas definamos os tipos de sindicatos com uma sintaxe T1|T2|...:

class Number {
	private int|float $number;

	public function setNumber(int|float $number): void {
		$this->number = $number;
	}

	public function getNumber(): int|float {
		return $this->number;
	}
}

Como explicado por Nikita Popov no RFC,

“Apoiar tipos de sindicatos no idioma nos permite mover mais informações do tipo phpdoc para assinaturas de função, com as vantagens habituais que isso traz:

  • Os tipos são realmente aplicados, então os erros podem ser pegos cedo.
  • Como eles são aplicados, é menos provável que as informações do tipo se tornem desatualizadas ou percam os casos de bordas.
  • Os tipos são verificados durante a herança, aplicando o Princípio de Substituição Liskov.
  • Os tipos estão disponíveis através do Reflection.
  • A sintaxe é muito menos “boilerplate-y” do que “phpdoc”

Os tipos de União suportam todos os tipos disponíveis, com algumas limitações:

  • O tipo void não poderia ser parte de um sindicato, pois void significa que uma função não retorna qualquer valor.
  • O tipo null só é suportado em tipos de união, mas seu uso como um tipo autônomo não é permitido.
  • A notação do tipo nulo (?T) também é permitida, significando T|null, mas não é permitido incluir a notação ?T nos tipos de união (?T1|T2 não é permitido e devemos usar T1|T2|null em seu lugar).
  • Como muitas funções (isto é, strpos(), strstr(), substr(), etc.) incluem false entre os possíveis tipos de retorno, o pseudo-tipo false também é suportado.

Você pode ler mais sobre a Union Types V2 na RFC.

Erros consistentes do tipo funções internas

Ao passar um parâmetro de tipo ilegal, funções internas e funções definidas pelo usuário se comportam de forma diferente.

Funções definidas pelo usuário lançam um TypeError, mas funções internas se comportam de várias maneiras, dependendo de várias condições. De qualquer forma, o comportamento típico é lançar um aviso e retornar null. Veja o seguinte exemplo no PHP 7.4:

var_dump(strlen(new stdClass));

Isto resultaria no seguinte aviso:

Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL

Se strict_types estiver habilitado, ou se a informação do argumento especificar tipos, o comportamento seria diferente. Em tais cenários, o erro de tipo é detectado e resulta em um TypeError.

Esta situação levaria a uma série de problemas bem explicados na seção de questões do RFC.

Para remover estas inconsistências, esta RFC propõe fazer a análise interna dos parâmetros APIs para sempre gerar um ThrowError no caso de um descasamento do tipo de parâmetro.

No PHP 8, o código acima lança o seguinte erro:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
  thrown in /path/to/your/test.php on line 4

Expressão throw Come

Em PHP, throw é uma declaração, portanto não é possível usá-la em lugares onde apenas uma expressão é permitida.

Esta RFC propõe converter a declaraçãothrow em uma expressão para que ela possa ser usada em qualquer contexto onde as expressões são permitidas. Por exemplo, funções de seta, operador de coalescência nula, operadores ternários e elvis, etc.

Veja os seguintes exemplos da RFC:

$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
 
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

Weak Maps

Um Weak Maps é uma coleção de dados (objetos) nos quais as chaves são fracamente referenciadas, significando que elas não são impedidas de serem coletadas.

PHP 7.4 adicionou suporte a referências fracas como uma forma de reter uma referência a um objeto que não impede que o objeto em si seja destruído. Como apontado por Nikita Popov,

“Referências fracas em bruto são apenas de utilidade limitada por si mesmas e mapas fracos são muito mais comumente usados na prática. Não é possível implementar um Weak Maps eficiente em cima de referências fracas em PHP porque a habilidade de registrar uma chamada de retorno de destruição não é fornecida”

É por isso que esta RFC introduz uma classe WeakMap para criar objetos a serem usados como chaves fracas de mapas que podem ser destruídos e removidos do Weak Maps se não houver mais referências ao objeto chave.

Em processos de longa duração, isto evitaria vazamentos de memória e melhoraria o desempenho. Veja o seguinte exemplo da RFC:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

Com o PHP 8, o código acima produziria o seguinte resultado (veja o código em ação aqui):

object(WeakMap)#1 (1) {
	[0]=>
	array(2) {
		["key"]=>
		object(stdClass)#2 (0) {
		}
		["value"]=>
		int(42)
	}
}

Se você desajustar o objeto, a chave é automaticamente removida do Weak Maps:

unset($obj);
var_dump($map);

Agora o resultado seria o seguinte:

object(WeakMap)#1 (0) {
}

Para uma visão mais detalhada dos mapas Fracos, veja a RFC. A proposta foi aprovada por unanimidade.

Vírgula de rastreamento na lista de parâmetros

As vírgulas de rastreamento são vírgulas anexadas a listas de itens em diferentes contextos. O PHP 7.2 introduziu vírgulas de rastreamento em sintaxe de lista, o PHP 7.3 introduziu vírgulas de rastreamento em chamadas de função.

O PHP 8 agora introduz vírgulas de rastreamento em listas de parâmetros com funções, métodos e fechamentos, como mostrado no exemplo a seguir:

class Foo {
	public function __construct(
		string $x,
		int $y,
		float $z, // trailing comma
	) {
		// do something
	}
}

Esta proposta foi aprovada com 58 votos a 1 voto.

Sintaxe ::classe permitida em objetos

Para buscar o nome de uma classe, nós podemos usar a sintaxe Foo\Bar::class. Esta RFC propõe estender a mesma sintaxe aos objetos para que agora seja possível ir buscar o nome da classe de um determinado objeto, como mostrado no exemplo abaixo:

$object = new stdClass;
var_dump($object::class); // "stdClass"
 
$object = null;
var_dump($object::class); // TypeError

Com PHP 8, $object::class fornece o mesmo resultado que get_class($object). Se $object não é um objeto, ele lança uma exceção a TypeError.

Esta proposta foi aprovada por unanimidade.

Atributos v2

Atributos, também conhecidos como anotações, são metadados estruturados que podem ser usados para especificar propriedades para objetos, elementos ou arquivos.

Até o PHP 7.4, os doc-comments eram a única maneira de adicionar metadados às declarações de classes, funções, etc. O Attributes v2 RFC introduz atributos PHP, definindo-os como uma forma de metadados estruturados e sintáticos que podem ser adicionados a declarações de classes, propriedades, funções, métodos, parâmetros e constantes.

Atributos são adicionados antes das declarações a que se referem. Veja os seguintes exemplos da RFC:

<<ExampleAttribute>>
class Foo
{
	<<ExampleAttribute>>
	public const FOO = 'foo';

	<<ExampleAttribute>>
	public $x;

	<<ExampleAttribute>>
	public function foo(<<ExampleAttribute>> $bar) { }
}

$object = new <<ExampleAttribute>> class () { };

<<ExampleAttribute>>
function f1() { }

$f2 = <<ExampleAttribute>> function () { };

$f3 = <<ExampleAttribute>> fn () => 1;

Atributos podem ser adicionados antes ou depois de um comentário em bloco de documentos:

<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}

Cada declaração pode ter um ou mais atributos, e cada atributo pode ter um ou mais valores associados:

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

Veja a RFC para uma visão mais profunda dos atributos PHP, casos de uso e sintaxe alternativa.

Argumentos nomeados

Os argumentos nomeados fornecem uma nova maneira de passar argumentos para uma função em PHP:

Os argumentos nomeados permitem passar argumentos para uma função com base no nome do parâmetro, ao invés da posição do parâmetro.

Nós podemos passar argumentos nomeados para uma função simplesmente adicionando o nome do parâmetro antes de seu valor:

callFunction(name: $value);

Nós também podemos usar palavras-chave reservadas, como mostrado no exemplo abaixo:

callFunction(array: $value);

Mas nós não temos permissão para passar um nome de parâmetro dinamicamente. O parâmetro deve ser um identificador, e a seguinte sintaxe não é permitida:

callFunction($name: $value);

De acordo com Nikita Popov, a autora deste RFC, os argumentos nomeados oferecem várias vantagens.

Em primeiro lugar, argumentos nomeados nos ajudarão a escrever um código mais compreensível porque seu significado é auto documentar. O exemplo abaixo da RFC é autoexplicativo:

array_fill(start_index: 0, num: 100, value: 50);

Os argumentos indicados são independentes da ordem. Isto significa que não somos forçados a passar argumentos para uma função na mesma ordem que a assinatura da função:

array_fill(value: 50, num: 100, start_index: 0);

Também é possível combinar argumentos nomeados com argumentos posicionais:

htmlspecialchars($string, double_encode: false);

Outra grande vantagem dos argumentos nomeados é que eles permitem especificar apenas aqueles argumentos que queremos mudar. Nós não temos que especificar os argumentos padrão se não quisermos sobrescrever os valores padrão. O seguinte exemplo da RFC deixa isso claro:

htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);

Os argumentos nomeados podem ser usados com atributos PHP, como mostrado no exemplo a seguir da RFC:

<<MyAttribute('A', b: 'B')>>
class Test {}

Entretanto, não é permitido passar argumentos posicionais após argumentos nomeados e resultará em um erro de compilação em tempo de compilação. O mesmo acontece quando você passa o mesmo nome de parâmetro duas vezes.

Os argumentos nomeados são úteis com declarações de classe porque os construtores geralmente têm muitos parâmetros e os argumentos nomeados fornecem uma maneira mais “ergonômica” de declarar uma classe.

Para uma visão mais próxima em Named Arguments, com restrições, incompatibilidades para trás, e vários exemplos, veja o Named Arguments RFC.

Operador Nullsafe

Este RFC apresenta o operador nullsafe $-> com avaliação completa de curto-circuito.

Na avaliação de curto-circuito, o segundo operador só é avaliado se o primeiro operador não avaliar para null. Se um operador em uma cadeia avalia para null, a execução de toda a cadeia pára e avalia para null.

Considere os seguintes exemplos da RFC:

$foo = $a?->b();

Se $a é nulo, o método b() não é chamado e $foo está configurado para null.

Veja o RFC do operador nullsafe para exemplos adicionais, exceções e escopo futuro.

Comparações Saner String to Number

Em versões anteriores do PHP, ao fazer uma comparação não restrita entre strings e números, o PHP primeiro lança a string para um número, depois faz a comparação entre números inteiros ou flutuantes. Mesmo que este comportamento seja bastante útil em vários cenários, ele pode produzir resultados errados que também podem levar a bugs e/ou problemas de segurança.

Considere o seguinte exemplo da RFC:

$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)

PHP 8 introduz o Saner string to number comparisons, com o objetivo de tornar as comparações string to number mais razoáveis. Nas palavras de Nikita Popov,

Esta RFC pretende dar às comparações numéricas um comportamento mais razoável: Ao comparar com uma cadeia numérica, use uma comparação de números (igual a agora). Caso contrário, converta o número para string e use uma comparação de string.

A tabela a seguir compara o comportamento da comparação de strings com a comparação de números em versões anteriores do PHP e no PHP 8:

Comparação | Antes | Depois
------------------------------
 0 == "0" | true | true
 0 == "0.0"   | true | true | true
 0 == "foo" | true | false
 0 == ""      | verdadeiro | falso
42 == " 42" | true | true
42 == "42foo" | true | false

Leia mais sobre as muitas implicações desta mudança e como as comparações de cadeia para número mudam no PHP 8 na RFC oficial de Nikita Popov.

Saner Numeric Strings

No PHP, as strings contendo números se encaixam em três categorias:

  • Numeric strings: strings contendo um número opcionalmente precedido por espaços em branco.
  • Leading-numeric string: strings cujos caracteres iniciais são numéricos e os caracteres finais são não-numéricos.
  • Non-numeric string: strings que não se enquadram em nenhuma das categorias anteriores.

Strings numéricas e strings numéricas principais são tratadas de forma diferente dependendo da operação realizada. Por exemplo, as strings numéricas são tratadas de forma diferente dependendo da operação realizada:

  • Conversões explícitas de cadeia de caracteres para números (isto é, (int) e (float) type casts) convertem números de cadeias de caracteres numéricas e não-numéricas principais. A conversão explícita de uma cadeia de caracteres não numérica para um número produz 0.
  • Conversões implícitas de string para número (ou seja, sem declaração strict_type ) levam a resultados diferentes para strings numéricas e não-numéricas. As conversões não-numéricas de cadeia de caracteres para número lançam um TypeError.
  • is_numeric() retorna verdadeiro somente para cadeias de caracteres numéricas.

Compensações de strings, operações aritméticas, operações de incremento e decremento, comparações de strings com strings, e operações bitwise também levam a resultados diferentes.

Esta RFC se propõe a isso:

Unificar os vários modos de strings numéricas em um único conceito: Caracteres numéricos somente com espaço em branco permitido, tanto com espaço em branco principal quanto com o trailing. Qualquer outro tipo de string é não-numérico e lançará TypeErrors quando usado em um contexto numérico.

Isto significa que todas as strings que atualmente emitem o E_NOTICE “A non well formed numeric value encountered” serão reclassificadas no E_WARNING “A non-numeric value encountered”, exceto se a string numérica principal contivesse apenas espaços em branco. E os vários casos que atualmente emitem um AVISO E_WARNING serão promovidos para TypeErrors

.
Para uma visão mais profunda das strings numéricas no PHP 8, com exemplos de código, exceções e problemas de compatibilidade retroativa, veja a RFC.

Expressão Match v2

A nova expressão match é bem parecida com switch mas com uma semântica mais segura e permitindo retornar valores.

Para entender a diferença entre as duas estruturas de controle, considere o seguinte exemplo switch da RFC:

switch (1) {
	case 0:
		$result = 'Foo';
		break;
	case 1:
		$result = 'Bar';
		break;
	case 2:
		$result = 'Baz';
		break;
}
 
echo $result;
//> Bar

Agora podemos obter o mesmo resultado que o código acima com a seguinte expressão match:

echo match (1) {
	0 => 'Foo',
	1 => 'Bar',
	2 => 'Baz',
};
//> Bar

Uma vantagem significativa de usar a nova expressão match é que enquanto switch compara valores vagamente (==) potencialmente levando a resultados inesperados, com match a comparação é uma verificação de identidade (===).

A expressão match também pode conter múltiplas expressões separadas por vírgula, permitindo uma sintaxe (fonte) mais concisa:

$result = match ($x) {
	// This match arm:
	$a, $b, $c => 5,
	// Is equivalent to these three match arms:
	$a => 5,
	$b => 5,
	$c => 5,
};

Para exemplos e casos de uso adicionais, veja a expressão Match v2 RFC e a documentação PHP.

Verificações mais rígidas do tipo para operadores aritméticos/bitswise

Nas versões anteriores do PHP, o aplicativo de operadores aritméticos e bitwise a um array, recurso, ou objeto não sobrecarregado era permitida. De qualquer forma, o comportamento às vezes era inconsistente.

Neste RFC, Nikita Popov mostra como esse comportamento pode ser irracional com um simples exemplo:

var_dump([] % [42]);
// int(0)

Nikita explica como o aplicativo do operador aritmético ou bitwise a arrays, recursos ou objetos não sobrecarregados levou a resultados diferentes:

Operadores +, -, *, /, **:

  • Exceção de erro no operando de array. (Excluindo + se ambos os operandos forem array)
  • Converta silenciosamente um operando de recurso para o ID do recurso como um número inteiro.
  • Converta um operando objeto para um inteiro, enquanto lança um aviso.

Operadores %, <<, >>, &, |, ^:

  • Converta silenciosamente um operando de array para zero inteiro se vazio ou um inteiro se não vazio.
  • Converta silenciosamente um operando de recurso para o ID do recurso como um número inteiro.
  • Converta um operando de objeto para um inteiro, enquanto lança um aviso.

Operador ~:

  • Lance uma exceção de erro para a array, resource e object operands.

Operadores ++ e –:

  • Silenciosamente não faça nada se o operando for um array, recurso ou objeto.

Com o PHP 8, as coisas mudam, e o comportamento é o mesmo para todos os operadores aritméticos e bitwise:

Lance uma exceção TypeError para a array, resource, e object operands.

Novas funções do PHP

O PHP 8 traz várias novas funções para a linguagem:

str_contains

Antes do PHP 8, strstr e strpos eram as opções típicas para os desenvolvedores procurarem por uma agulha dentro de uma determinada string. O problema é que ambas as funções não são consideradas muito intuitivas, e seu uso pode ser confuso para novos desenvolvedores de PHP. Veja o exemplo a seguir:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);

if ($pos !== false) {
	echo "The string has been found";
} else {
	echo "String not found";
}

No exemplo acima, nós usamos o operador de comparação !==, que verifica se dois valores são do mesmo tipo. Isto nos impede de obter um erro se a posição da agulha for 0:

“Esta função pode retornar FALSE Booleano, mas também pode retornar um valor não-Booleano que avalia para FALSE

“. […] Use o operador === para testar o valor de retorno desta função”
Além disso, várias estruturas fornecem funções de ajuda para buscar um valor dentro de uma determinada string (veja a documentação do Laravel Helpers como exemplo).

Agora, este RFC propõe a introdução de uma nova função que permite pesquisar dentro de uma string: str_contains.

str_contains ( string $haystack , string $needle ) : bool

Seu uso é bastante simples. str_contains verifica se $needle é encontrado em $haystack e retorna true ou false de acordo.

Então, graças a str_contains, podemos escrever o seguinte código:

$mystring = 'Managed WordPress Hosting';
$findme   = 'WordPress';

if (str_contains($mystring, $findme)) {
	echo "The string has been found";
} else {
	echo "String not found";
}

O que é mais legível e menos propenso a erros (veja este código em ação aqui).
No momento em que escrevemos este artigo, str_contains é sensível a casos, mas isto pode mudar no futuro.

A proposta str_contains foi aprovada com 43 a 9 votos.

str_starts_with() e str_ends_with()

Além da função str_contains, duas novas funções permitem a busca por uma agulha dentro de uma determinada corda: str_starts_with e str_ends_with.

Estas novas funções verificam se uma determinada string começa ou termina com outra string:

str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool

Ambas as funções retornam false se $needle for mais longo que $haystack.

De acordo com Will Hudgins, o autor deste RFC,

“A funcionalidade str_starts_with e str_ends_with é tão comumente necessária que muitos dos principais frameworks PHP a suportam, incluindo Symfony, Laravel, Yii, FuelPHP, e Phalcon

Graças a eles, agora poderíamos evitar o uso de funções sub-ótimas e menos intuitivas como substr, strpos. Ambas as funções são sensíveis a maiúsculas e minúsculas:

$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";

if (str_starts_with($str, "word")) echo "Not found!";

Você pode ver este código em ação aqui.

Esta RFC foi aprovada com 51 a 4 votos.

get_debug_type

get_debug_type é uma nova função PHP que retorna o tipo de uma variável. A nova função funciona de forma bastante similar à funçãogettype , mas get_debug_type retorna nomes de tipos nativos e resolve nomes de classes.

Isso é uma boa melhoria para o idioma, já que gettype() não é útil para a verificação de tipo.

O RFC fornece dois exemplos úteis para entender a diferença entre a nova função get_debug_type() e gettype(). O primeiro exemplo mostra gettype no trabalho:

$bar = [1,2,3];

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

Com o PHP 8, nós poderíamos usar get_debug_type, ao invés disso:

if (!($bar instanceof Foo)) { 
	throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

A tabela seguinte mostra os valores de retorno de get_debug_type e gettype:

Value gettype() get_debug_type()
1 integer int
0.1 double float
true boolean bool
false boolean bool
null NULL null
“WordPress” string string
[1,2,3] array array
A class with name “Foo\Bar” object Foo\Bar
An anonymous class object class@anonymous

RFCs adicionais
Aqui está uma lista rápida de melhorias adicionais aprovadas que vêm com o PHP 8:

  1. Interface Stringable: esta RFC introduz uma interface Stringable que é automaticamente adicionada às classes que implementam o método__to String() . O principal objetivo aqui é usar o tipo de união string|Stringable.
  2. Novas APIs DOM Living Standard em ext/dom: esta RFC propõe implementar o DOM Living Standard atual para a extensão PHP DOM, introduzindo novas interfaces e propriedades públicas.
  3. Tipo de retorno estático: O PHP 8 introduz o uso do static como tipo de retorno ao lado dos tipos self e parent.
  4. Ajustes de sintaxe variável: esta RFC resolve algumas inconsistências residuais na sintaxe variável do PHP.

Desempenho do PHP 8: Benchmarks

Se você está se perguntando quão rápido é o PHP 8, temos a resposta. Realizamos benchmarks em 20 plataformas/configurações de PHP em 7 versões diferentes do PHP (5.6, 7.0, 7.1, 7.2, 7.3 e 8.0).

O PHP 8.0 se destacou como o vencedor na maioria das plataformas que o suportam, incluindo WordPress e Laravel.

Benchmarks compilados das principais plataformas de PHP
Benchmarks compilados das principais plataformas de PHP

Por exemplo, o WordPress no PHP 8.0 pode lidar com 18,4% mais solicitações por segundo do que o PHP 7.4. Da mesma forma, o Laravel no PHP 8.0 pode executar 8,5% mais solicitações por segundo do que o PHP 7.3.

Se o seu site ou aplicativo é totalmente compatível com o PHP 8.0, você deve planejar atualizar o ambiente do seu servidor para o PHP 8.0 o mais rápido possível. Você (e seus usuários) com certeza vão apreciar os benefícios de desempenho. No entanto, teste seu site completamente antes de atualizar.

Você pode ler nosso artigo sobre benchmarks de PHP para obter mais informações, como dados de desempenho detalhados, percepções e gráficos interessantes!

Resumo

Que passeio! Neste artigo, cobrimos as otimizações e características mais interessantes que vêm com o PHP 8. O mais esperado certamente é o compilador Just in Time, mas há muito mais com o PHP 8.

Certifique-se de marcar este artigo no blog para sua referência futura. 🤓

Agora é a sua vez: você está pronto para testar as novas funcionalidades do PHP? Qual delas é a sua favorita? Escreva nos comentários abaixo.

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.