Novidades do PHP 8

Já faz um tempo que a versão 8.1 do PHP foi lançada, por isso os plugins de ferramentas como o vs code já começaram a atualizar, e algumas empresas também já devem ter começado a migrar os seus sistemas para dar suporte as novas versões do PHP. Por isso hoje vamos explorar as principais novidades que essa linguagem maravilhosa nos trouxe desde a sua última major version (o PHP 8) até a sua última versão: PHP 8.1.

PHP 8

Lançado em Novembro de 2020, o PHP 8 trouxe consigo as seguintes features:

  • Argumentos nomeados;
  • Promoção de propriedade de construtor;
  • Atributos;
  • União de tipos (Union types);
  • Tipo mixed;
  • Expressão match;
  • Operador Nullsafe;
  • Comparações mais inteligentes entre strings e números;
  • Novas funções e interfaces para strings;
  • Erros consistentes para tipos de dados em funções internas;
  • JIT (compilação Just In Time);

Argumentos nomeados (doc)

Agora podemos referenciar diretamente os parâmetros de funções e métodos, assim não precisamos nos preocupar mais com a ordem dos parâmetros ou parâmetros opcionais. Ex:

// Ordem origial
in_array("Lucas", ["Bia", "Tiago", "Lucas"]);

// Ordem invertida
in_array(haystack: ["Bia", "Tiago", "Lucas"], neddle: "Lucas");

Isso ajuda as funções serem mais auto-documentadas, pois podemos passar seus argumentos pelo nome deles, eu achei essa nova feature particularmente útil nos construtores de objetos, alguns exemplos:

$connection = new PDO(
    dsn: "sqlite:./posts.db",
    options: [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]
);

No exemplo acima, por estarmos criando uma conexão com um banco de dados sqlite, que não tem nem usuário e nem senha, mas queremos passar o último parâmetro para configurar a nossa conexão, teríamos que colocar null em dois parâmetros até chegar no options, mas com argumentos nomeados, podemos ir e passar ele diretamente.

Outro exemplo interessante é na criação de um objeto, por exemplo um usuário:

class User
{
    public function __construct(
        private string $email,
        private string $password,
        private ?int $id = null,
    ) {
    }
}

$newUser = new User(
    email: '[email protected]',
    password: 'secret123'
);

$alreadyExistentUser = new User(
    id: 1,
    email: '[email protected]',
    password: 'secret12345'
);

Promoção de propriedade de construtor (doc)

Você já deve ter notado que eu utilizei uma sintaxe um pouco diferente na hora de definir o construtor da classe User no exemplo anterior, e é exatamente sobre ela que iremos falar agora. No PHP 8 você pode pegar todo aquele código que nós fazemos para definir uma propriedade na classe e depois preencher ela no construtor, por exemplo:

class Person
{
		private string $name;
		private int $age;

    public function __construct(string $name, int $age = 1)
    {
				$this->name = $name;
				$this->age = $age;
		}

		public function __toString(): string
		{
				return "{$this->name} is {$this->age} years old.";
		}
}

E resumir ele assim:

class Person
{
    public function __construct(private string $name, private int $age = 1)
    {
		}

		public function __toString(): string
		{
				return "{$this->name} is {$this->age} years old.";
		}
}

Assim o PHP entende que o nome do parâmetro definido no construtor vai ser atribuido para uma propriedade de mesmo nome na classe, qual a visibilidade dele (por isso você precisa utilizar o public, private ou protected no início para que ele entenda que é para promover a propriedade no construtor), além das outras informações que você pode definir com a sintaxe de definição de propriedades na classe.

Atributos (doc)

Essa é uma das grandes adições da versão 8 do PHP, atributos é algo que até então era feito por meio de comentários no PHP, e agora podemos ter eles como um recurso nativo da linguagem. Atributos são uma forma de adicionar metadados na sua classe, assim podemos adicionar novas funcionalidades a elas sem mexer em sua estrutura interna. Usos comuns para eles são a definição de rotas ou a criação de classes por meio de ORMs em frameworks de PHP, alguns exemplos são:

A definição de rotas no framework Symfony:

// src/Controller/BlogController.php
namespace App\\Controller;

use Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;
use Symfony\\Component\\Routing\\Annotation\\Route;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list()
    {
        // ...
    }
}

A interação com o banco de dados usando o ORM Doctrine:

use Doctrine\\DBAL\\Types\\Types;
use Doctrine\\ORM\\Mapping AS ORM;

#[ORM\\Entity(repositoryClass: PostRepository::class)]
class Post
{
    #[ORM\\Column(type: Types::INTEGER)]
    #[ORM\\Id, ORM\\GeneratedValue(strategy: 'AUTO')]
    private ?int $id;

    #[ORM\\Column(type: Types::BOOLEAN)]
    private bool $published = false;

    #[ORM\\Column(type: Types::SIMPLE_ARRAY)]
    private array $text = [];

    #[ORM\\ManyToOne(targetEntity: User::class)]
    public $author;

    #[ORM\\ManyToMany(targetEntity: Tag::class)]
    #[ORM\\JoinTable(name: "post_tags")]
    #[ORM\\JoinColumn(name: "post_id", referencedColumnName: "id")]
    #[ORM\\InverseJoinColumn(name: "tag_id", referencedColumnName: "id")]
    public Collection $tags;
}

E com esses metadados adicionados as classes, outra classe pode ler esses atributos e então fazer coisas com eles, assim esses frameworks conseguem jogar toda a lógica de definição de rotas ou configurar o ORM para os códigos deles e só deixar para nós usuários finais a parte de configurar essas ferramentas.

União de tipos (doc)

Agora é possível utilizar | para tipar as coisas no PHP, logo podemos fazer uma função que aceita que um parâmetro possa ser de um tipo ou de outro, ex:

function printStringOrArray(string|array $thing): void
{
    echo match (gettype($thing)) {
        'string' => $thing,
        'array' => join(', ', $thing)
    };
}

No exemplo acima a função printStringOrArray só aceita um parâmetro e ele só pode ser do tipo string ou do tipo array, qualquer outra coisa resultaria em um TypeError do PHP.

Tipo Mixed (doc)

Com o advento dos union types, o PHP introduziu um novo tipo que é o equivalente ao union type de todos os tipos base do PHP, o tipo mixed, que é basicamente o tipo que representa todos os tipos, ele equivale ao seguinte union type:

object|resource|array|string|int|float|bool|null

Expressão Match (doc)

Seguinto no uso do próximo tema na explicação do tema anterior, a expressão match é praticamente um switch só que na forma de expressão (assim como o operador ternário pode ser considerado um if na forma de expressão) só que sem todos os péssimos problemas do switch.

Ela é uma expressão então você pode utilizar na atribuição de variáveis, na passagem de parâmetros de uma função, no retorno de funções e etc. Fora isso ela não precisa do break, e também faz comparações estritas enquanto o switch não (é como se a match expression usa-se === e o switch ==).

Um exemplo legal de uso dela é a resolução do algoritmo de FizzBuzz que é um algoritimo que deve converter múltiplos de 3 em ‘fizz’, múltiplos de 5 em ‘buzz’ e múltiplos de ambos em ‘fizz-buzz’. Números não divisíveis, são retornados normalmente. Que antes poderia ser escrito assim:

function fizzBuzz(int $number): string|int
{
    if ($number % 15 === 0) {
        return "fizz-buzz";
    } elseif ($number % 3 === 0) {
        return "fizz";
    } elseif ($number % 5 === 0) {
        return 'buzz';
    }

    return $number;
}

E agora podemos escrever ela assim:

function fizzBuzz(int $number): string|int
{
    return match (0) {
        $number % 15 => "fizz-buzz",
        $number % 3 => "fizz",
        $number % 5 => "buzz",
        default => $number
    };
}

// E podemos resumir ela mais ainda se utilizarmos as arrow functions do PHP 7.4
$fizzBuzz = fn (int $number): string|int => match (0) {
    $number % 15 => "fizz-buzz",
    $number % 3 => "fizz",
    $number % 5 => "buzz",
    default => $number
};

Operador Nullsafe (doc exemplo 18)

Com esse operador nós podemos acessar várias propriedades aninhadas de um objeto sem medo de receber aquele erro: Attempt to read property "nome da propriedade" on null. Ou ficar fazendo programação defensiva em propriedades ou métodos que são potencialmente nulos ao acessar um objeto. Acredito que o exeplo dado pela própria documentação do PHP já ilustra como a vida pode ser melhor depois dele:

// Antes
$country =  null;

if ($session !== null) {
  $user = $session->user;

  if ($user !== null) {
    $address = $user->getAddress();
 
    if ($address !== null) {
      $country = $address->country;
    }
  }
}
// Depois
$country = $session?->user?->getAddress()?->country;

Comparações mais inteligentes entre strings e números

É algo que pode espantar muita gente, mas foi uma correção em como strings e números são comparados na linguagem ao utilizar o ==. Antes independente de ser uma string numérica (ou seja uma string que pode ser convertida em um número) ou uma string normal, o PHP convertia a string em número e depois comparava. E isso gerava casos como:

0 == "foobar" // true

Sendo que foobar nem ao menos é um número, então agora ao comparar números com strings não numéricas, quem é convertido é o número (de número para string), assim o 0 é convertido em “0”, o que torna a comparação mais correta.

Erros consistentes para tipos de dados em funções internas

A maioria das funções internas agora lançam uma exceção Error se a validação do parâmetro falhar. Assim se errarmos na passagem de parâmetro de alguma função nativa do PHP, então podemos tratar esse erro em um bloco try catch.

Features relacionadas a strings

Foram adicionadas as funções:

  • str_contains
  • str_starts_with
  • str_ends_with

Assim podemos utilizar elas para verificar se uma string existe dentro de outra string, se uma string é o início de outra string ou se ela é o final de outra string respectivamente. E foi uma adicionada uma nova interface chamada de Stringable que permite que nós identifiquemos objetos em que nós sobrescrevemos o método mágico __toString que está presente em qualquer objeto, assim o motivo da adição dessa interface é deixar bem claro os objetos que sobrescreveram o __toString padrão.

JIT

Aqui é uma feature que foi o hype na época do lançamento do PHP 8, muitas linguagens possuem algo chamado compilação Just In Time, que significa que elas compilam o código enquanto executam ele, o que torna a linguagem bem mais rápida em vários casos onde ela precisa fazer processamentos muito pesados ou execuções muito longas (o que ajudaria o PHP a executar num modelo como o do Node por exemplo).

Então o PHP que já vem desde a versão 7 ganhando vários recursos que melhoram muito a performance do código recebeu essa nova adição do JIT que vai melhorar ainda mais a performance da linguagem em cenários de processamento pesado, embora aplicações normais tenham um ganho de performance, só que não muito significativo.

Ajustes na sintaxe da linguagem

  • Permitir vírgula no final da lista de parâmetros (RFC) e listas de uso em closures RFC;
  • Catches sem variável na captura de exceção RFC;
  • Ajustes de sintaxe para variáveis RFC;
  • Tratamento de nomes de namespace como token único RFC;
  • Throw como expressão RFC;
  • Permitir ::class em objetos RFC

PHP 8.1

O PHP 8.1 foi lançado em Novembro de 2021, e dessa vez trouxe as seguintes novidades:

  • Enums;
  • Propriedades Readonly;
  • Sintaxe de callables de primeira classe;
  • New em inicializadores;
  • Tipos de interseção puros;
  • O tipo de retorno never;
  • Constantes de classes finais;
  • Notação para números octais;
  • Fibers API;
  • Suporte a unpacking para arrays associativos;
  • Aumento significativo de performance em relação ao PHP 8.0

Enums (docs)

Essa é uma estrutura muito comum em várias outras linguagens de programação como o Java, o C# ou o TypeScript. E é utilizada para poder representar um conjunto de constantes relacionadas, por exemplo dias da semana, os planetas do sistema solar, opções de um menu de uma linha de comando, estados de um documento e etc. Até a versão 8.0 do PHP, nós simulavamos enums através de classes que tinham um conjunto de constantes, ex:

class HttpStatus
{
    const OK = 200;
		const CREATED = 201;
    const BAD_REQUEST = 400;
    const UNAUTHORIZED = 401;
    const NOT_FOUND = 404;
    const METHOD_NOT_ALLOWED = 405;
    const INTERNAL_SERVER_ERROR = 500;
}

E agora nós temos uma estrutura na linguagem própria para isso:

enum HttpStatus: int
{
    case OK = 200;
    case CREATED = 201;
    case BAD_REQUEST = 400;
    case UNAUTHORIZED = 401;
    case NOT_FOUND = 404;
    case METHOD_NOT_ALLOWED = 405;
    case INTERNAL_SERVER_ERROR = 500;
}

Propriedades readonly (doc)

Esse é um novo modificador que foi adicionado as nossas definições de propriedades quando criamos as nossas classes, pois ele serve para definir que uma propriedade não pode ser alterada após a sua inicialização, então ela recebe o seu valor apenas uma vez e depois disso ela não pode mais ser alterada. Isso é bom para quando queremos aplicar a imutabilidade no nosso código, mas queremos expor uma propriedade do nosso objeto. Por exemplo um id de uma entidade:

class User implements Stringable
{
    public function __construct(
        private int $id,
        private string $email,
        private string $password
    ) {
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function __toString(): string
    {
        return "{$this->email}/{$this->password}";
    }
}

Antes do PHP 8.1, teríamos que utilizar getters para isso, pois para evitar que alguém possa manipular essa propriedade, ela deveria ser private e para que ela possa ser acessada precisaríamos de um getter. E agora com readonly properties podemos simplesmente adicionar esse modificador e não precisamos criar um novo método para isso:

class User implements Stringable
{
    public function __construct(
        public readonly int $id,
        private string $email,
        private string $password
    ) {
    }

    public function __toString(): string
    {
        return "{$this->email}/{$this->password}";
    }
}

Sintaxe de callables de primeira classe (doc)

O tipo callable no PHP representa coisas que podem ser chamadas, ou seja, funções, métodos, e objetos que tenham o método __invoke . E até então se quisessemos invocar um callable dinâmicamente ou passar ele como parâmetro e etc, nós teríamos que ou passar uma string com o nome do callable ou teríamos que utilizar uma sintaxe de array com strings. Alguns exemplos:

$words = ["foo", "bar", "bazz"];
$upperWords = array_map('strtoupper', $words);
echo join(", ", $upperWords);
class PostController
{
    public function list()
    {
        # code...
    }

    public function create()
    {
        # code...
    }
}

$postController = new PostController();
$router->get('/posts', [$postController, 'list']);
$router->post('/posts', [$postController, 'create']);

Além de ser um pouco confuso pois não dá para saber de imediato se é um callable ou uma string ou um array, também não casa muito bem com ferramentas de análise estática. Por isso a nova sintaxe de callbables de primeira classe resolve esses dois problemas, ao introduzir uma nova sintaxe que cria uma função anônima (ou um objeto Closure) a partir de um callable. Então vamos ver nossos exemplos anteriores com essa nova sintaxe:

$words = ["foo", "bar", "bazz"];
$upperWords = array_map(strtoupper(...), $words);
echo join(", ", $upperWords);
class PostController
{
    public function list()
    {
        # code...
    }

    public function create()
    {
        # code...
    }
}

$postController = new PostController();
$router->get('/posts', $postController->list(...));
$router->post('/posts', $postController->create(...));

New em inicializadores

Agora podemos utilizar o new para instânciarmos objetos como valores padrão em parâmetros, variáveis estáticas, onstantes globais, e em argumentos de atributos. Antes isso resultaria em um erro de tipo. Essa nova atualização permite que possamos usar os Atributos adicionados no PHP 8.0 aninhados uns nos outros, ex:

class User
{
    #[\\Assert\\All(
        new \\Assert\\NotNull,
        new \\Assert\\Length(min: 6))
    ]
    public string $name = '';
}

Tipos de interseção puros (doc)

Na última atualização tivemos a adição dos union types, assim conseguimos fazer como se fosse um OU na hora de tipar, agora com os pure intersection types nós podemos fazer um E também. Por exemplo:

function count_and_iterate(Iterator&Countable $value) {
    foreach ($value as $val) {
        echo $val;
    }

    count($value);
}

Assim essa função só aceita objetos que implementarem a interface Iterator e que implementarem a interface Countable ao mesmo tempo, se for só uma das duas ou nenhuma das duas, ele lança um erro.

Tipo never (doc)

Seguindo nas novidades relacionadas a tipagem, nós temos um novo tipo: o never.

Ele representa quando o retorno de uma função é void e ao mesmo tempo ele lança uma exceção ou encerra a execução do script. Ou seja, ele serve para indicar que ao mesmo tempo que a função não retorna nada, todo o código após ela também não será executado. É um tipo útil para conseguirmos tipar com mais exatidão funções de redirecionamento por exemplo:

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

Constantes de classes finais (doc)

Agora é possível utilizar o modificador final em constantes de classes para evitar que uma classe filha sobrescreva uma constante da classe mãe.

// Antes
class Foo
{
    public const XX = "foo";
}

class Bar extends Foo
{
    public const XX = "bar"; // No error
}
// Depois
class Foo
{
    final public const XX = "foo";
}

class Bar extends Foo
{
    public const XX = "bar"; // Fatal error
}

Notação explícita de número octal (doc)

Caso você queira utilizar números octais no seu código, agora é possível utiliza-los de forma mais explícita utilizando o prefixo 0o antes do número. Ex:

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

Fibers API (doc)

Esta aqui foi mais uma adição para os programadores que criam bibliotecas do que uma adição para os programadores comuns do PHP utilizarem diretamente em seus códigos. Os Fibers são primitivos para que seja possível implementar concorrência cooperativa leve no PHP. Assim podemos criar blocos de código que podem ser pausados e retomados que nem os generators, porém em qualquer lugar da pilha de execução.

Assim é possível implementar um event loop no php (assim como o Node faz) e termos um jeito nativo melhor de produzir código assíncrono através das bibliotecas que utilizarem a Fibers API por de baixo dos panos. Para notar a mudança veja o exemplo dado pela equipe do PHP de como isso mudaria as coisas:

// Sem Fibers
$httpClient->request('<https://example.com/>')
        ->then(function (Response $response) {
            return $response->getBody()->buffer();
        })
        ->then(function (string $responseBody) {
            print json_decode($responseBody)['code'];
        });
// Com Fibers
$response = $httpClient->request('<https://example.com/>');
print json_decode($response->getBody()->buffer())['code'];

Suporte a unpacking para arrays associativo (doc)

Se você já utiliza JavaScript, essa daqui é o equivalente a utilizar o spread operator em objetos literais, se você não usa, vamos lembrar de uma funcionalidade adicionada no PHP 7.4, o spread operator que permitia juntar dois arrays. Ex:

$numbers = [1, 2, 3, 4, 5];
$otherNumbers = [6, 7, 8, 9, 10];
$allNumbers = [...$numbers, ...$otherNumbers];

Porém essa funcionalidade não funcionava com arrays associativos, então agora no PHP 8.1 esse suporte foi adicionado, assim podemos fazer algo assim:

$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
$result = ['a' => 0, ...$arrayA, ...$arrayB];

Utilizar o operador spread nesse caso tem o mesmo efeito de utilizar a função array_merge.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *