5 funcionalidades uteis mais recentes do PHP
Tags: php, webdev, programming, braziliandevs
Published: 2024-02-14 23:11:20

O PHP, em suas versões mais recentes, vem crescendo muito como linguagem, cada vez melhorando mais a performance da linguagem, sua segurança de tipos, melhorando a biblioteca padrão da linguagem, e principalmente adicionando novas funcionalidades.

Apesar de, hoje em dia, não ter a melhor fama do mundo, muito graças ao seu design de linguagem que realmente não era dos melhores, e mal uso por parte da comunidade durante muitos anos, a comunidade PHP nunca deixou de melhorar, seja tendo um ecossistema maduro, com frameworks como o Laravel sempre trazendo inovações, seja a própria linguagem que vem a cada atualização consertando esses problemas de design remanescentes de sua origem.

Por isso, para celebrar o recente lançamento da versão 8.3 da linguagem (disponível desde 21 de dezembro de 2023), este artigo irá compartilhar as minhas 5 funcionalidades favoritas desde a versão 7.4 do PHP.

# # 01 - Arrow functions

Arrow functions, funções anônimas, closures, lambdas, seja qual o nome dessa feature na sua linguagem, com a popularização de técnicas de programação funcional nas mais variadas linguagens, as arrow functions tem se mostrado uma feature muito bem sucedida independente da linguagem onde elas são implementadas, seja simplificando a declaração de funções simples, no caso do PHP ainda mais pois por ela ser um closure, ela consegue lembrar dos detalhes do escopo em que ela foi declarada, além de tornar código que usa princípios funcionais como a função array_filter por exemplo muito mais elegante do que anteriormente.

Ela simplifica muito a sintaxe para a criação de uma função anônima no PHP:

```php
// Antes
$fn = function ($value) {
  return $value;
};

// Depois
$arrow = fn ($value) => $value;
```

A capacidade de lembrar do escopo onde ela foi declarada, faz com que nós não precisemos declarar um `use` para que a função anônima consiga usar valores de fora dela, ex:

```php
// Antes
$users = [...]; // um array com objetos representando usuários
$userId = 1;

array_filter($users, function ($user) use ($userId) {
  return $user->id === $userId;
});

// Depois
array_filter($users, fn ($user) => $user->id === $userId);
```

Neste exemplo, na forma tradicional, caso não tivessemos utilizado o `use` o PHP acusaria um erro dizendo que essa variável `$userId` não havia sido declarada, pois ele só consegue enxergar o escopo da função, e não o escopo onde ela está inserida, já com a arrow function como o PHP vai olhar o escopo onde a função foi declarada também durante a resolução de seus valores internos, ele vai ver que o `$userId` não existe no escopo da arrow function, e então vai olhar no escopo onde ela declarada, e vai usar o valor da variável `$userId` declarada lá.

Esse exemplo também mostra como funções que recebem parâmetros callable podem acabar ficando mais fáceis de ler após o uso de uma arrow function.
# # 02 - Named arguments

Um dos argumentos bem comuns leventado pelos críticos do PHP seria a inconsistência nos argumentos usados por várias funções da biblioteca padrão do PHP, o que torna o uso delas confuso, pois você sempre fica em dúvida sobre qual deveria ser a ordem em que os argumentos deveriam ser passados perdendo um pouco de tempo descobrindo isso ou consultando a documentação da linguagem, o que realmente é um sinal de design de linguagem ruim no PHP.

Felizmente, apesar de alterar todas essas funções não ser possível, pois seria um problema maior ainda, o PHP finalmente trouxe uma solução para esse problema por meio dos named arguments, que é uma funcionalidade que permite que você referêncie um parâmetro pelo nome dele, e assim ignore a ordem em que eles deveriam ser passados. O que resolve esse problema de confusão na ordem, uma vez que você pode decidir a ordem em que vai passar os parâmetros da função.

Um exemplo simples de inconsistência seria as funções array_map, array_filter, e array_reduce:

```php
array_map($fn, $array);
array_filter($array, $fn);
array_reduce($array, $fn);
```

Note que o map, apesar de estar no mesmo grupo de funções que o array_filter e o array_reduce, no sentido que são funções de iteração em array que aceitam uma função como parâmetro, mas a ordem no caso do array_map é invertida a ordem dos outros dois. Para evitar a confusão de sempre ter que lembrar quem é quem, podemos usar os named arguments:

```php
array_map(callback: $fn, array: $array);
array_filter(callback: $fn, array: $array);
array_reduce(callback: $fn, array: $array);
```

Isso não é limitado só a funções e métodos do PHP, essa funcionalidade também se aplica a funções criadas pelo usuário:

```php
function doSomething($a, $b, $c): void
{
    var_dump([$a, $b, $c]);
}

doSomething(b: 'b', a: 'a', c: 'c');
```

Ela também torna o código mais legível em alguns casos, princípalmente com o uso de técnicas de orientação a objetos, onde você pode ter métodos onde os parâmetros continuam a se combinar com o nome do método em si, ex:

```php
class Count
{
    private int $value;

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

    public function increment(int $by = 1): void
    {
        $this->value += $by;
    }

    public function show(): void
    {
        echo $this->value . PHP_EOL;
    }
}

$count = new Count(value: 0);
$count->increment();
$count->show(); // 1

$count->increment(by: 5);
$count->show(); // 6
```

Uma coisa legal é que como ela pode ser utilizada nos métodos construtores também, se uma classe tiver muitas propriedades, você pode usar os named arguments para não se perder em qual propriedade deveria ser o que, lembrando até a declaração de objetos literais em linguagens como o JS.

# 03 - Constructor property promotion

Aqui uma funcionalidade que eu gostaria que existisse em todas as linguagens orientadas a objeto que utilizam classes, um dos padrões mais comuns nesse tipo de linguagem, é declarar uma classe com propriedades privadas, e então atribuir um valor para essas propriedades pelo construtor da classe, onde esse método construtor recebe os valores como parâmetro, e depois atribui elas para as propriedades correspondentes, ex:

```php
class Post
{
    private int $id;
    private string $title;
    private string $content;

    public function __construct(int $id, string $title, string $content)
    {
        $this->id = $id;
        $this->title = $title;
        $this->content = $content;
    }
}
```

O que gera um certo grau de boilerplate pois teríamos que repetir esse processo em todas as classes onde fossemos fazer isso, e você pode perceber que a mesma coisa é declarada repetidas vezes o que é algo bem chato de se lidar, por isso a funcionalidade de constructor property promotion facilita tanto a nossa vida, com ela é possível reduzir todo esse template a uma declaração só para tudo, assim:

```php
class Post
{
    public function __construct(
      private int $id,
      private string $title,
      private string $content
    )
    {}
}
```

Dessa forma você já declara a propriedade, incluindo a visibilidade dela, os parâmetros no construtor, e a operação de atribuir o parâmetro na propriedade, com uma única declaração, o que facilita demais o trabalho, enquanto deixa o código bem mais limpo e conciso do que antes.

# # 04 - Match expression

A match expression é mais uma opção disponível no PHP para se lidar com condicionais além do if/elseif/else, switch, e do operador ternário. E ela é interessante pois ela é como se fosse um ternário só que para o switch, só que com uma sintaxe mais elegante que os ternários, e sem os diversos problemas existentes no switch.

O if e o switch tem um problema que é o fato deles serem "statements" e não expressões, o que significa que eles são comandos que indicam como o interpretador do PHP vai ler e executar o código, ao contrário das expressões, eles não terminam virando um valor, e por isso você não pode usar eles em funções ou em variáveis. Por exemplo:

```php
$var = null;

if ($condition) {
  $var = true;
} else {
  $var = false;
}
```

Aqui o if está controlando a execução do código para que uma das linhas atribuindo valor para $var seja executada, mas ele em si não vira valor nenhum, pois ele não é uma expressão, já no caso do ternário, isso é possível:

```php
$var = $condition ? true : false;
```

Aqui o ternário verifica a condição, e retorna o valor true se a condição for verdadeira ou o false se ela for falsa, como se fosse a execução de uma função, o que é uma capacidade que abre bem mais possibilidades do que apenas controlar a execução do código, e inclusive permite um estilo mais declarativo de se programar.

Junte essa limitação do switch ser um "statement" com outras falhas como a comparação ser feita utilizando `==` ao invés de `===`, o que faz o código dentro dos cases ser vulnerável a alguns bugs relacionados a tipagem, ter necessidade de usar um break ou return senão o código continua executando mesmo não sendo a condição correta, o que pode levar a bugs pelo programador esquecer de digitar algum detalhe bobo, e sintaxe trabalhosa de digitar num geral, e existem vários posts que indicam o uso de estruturas de chave-valor como os arrays associativos do PHP emulando um switch, como sendo até preferíveis do que o próprio switch.

Agora nenhuma desses truques seria mais necessário pois agora temos uma estrutura que é uma expressão, faz as comparações usando `===`, e tem uma sintaxe concisa: a match expression, logo o que se fazia antes usando switch:

```js
$direction = 0;
$name = '';

switch ($direction) {
    case 0:
        $name = "top";
        break;
    case 1:
        $name = "left";
        break;
    case 2:
        $name = "bottom";
        break;
    case 3:
        $name = "right";
        break;
    default:
        $name = "invalid";
        break;
}
```

Pode ser muito simplificado por meio da match expression:

```php
$name = match ($direction) {
  0 => "top",
  1 => "left",
  2 => "bottom",
  3 => "right",
  default => "invalid"
};
```

# 05 - First-class callable syntax

E por último mas não menos importante, as first-class callable syntax não são bem algo que não dava para fazer no PHP antes, mas elas são uma alternativa bem mais elegante do que as outras sintaxes para passar callables por ai no PHP.

Um callable no PHP é o tipo que nós damos para algo que pode ser executado, como uma função, uma função anônima, uma arrow function, um método, um objeto que implementa o método `__invoke`, e etc.

Originalmente se uma função requere um callable, por exemplo o `array_map`, existem diversas sintaxes disponíveis para que você consiga passar ele como parâmetro, entretanto todas elas parecem bem confusas se você não entende do contexto daquele código, e princípalmente se você já não estava acostumado com essa funcionalidade do PHP, pois elas podem facilmente ser confundidas com outras coisas no PHP, ex:

```php
class ToUpperCase
{
    public function __invoke(string $str): string
    {
        return strtoupper($str);
    }
}

class Str
{
    public static function upperCase(string $str): string
    {
        return strtoupper($str);
    }
}

$upperCase = new ToUpperCase();
$strs = ['a', 'b', 'c'];

// Você pode passar o nome da função como string
array_map('strtoupper', $strs);

// Pode passar usando um array com a classe, e o método estático na forma de string
array_map([Str, 'upperCase'], $strs);

// Pode passar um objeto que implementa o __invoke
array_map($upperCase, $strs);
```

Note que, tirando o objeto que implementa o `__invoke`, as outras sintaxes são referêntes a outras coisas do PHP (uma string deveria ser uma string por exemplo), mas que ganham um significado especial quando são usadas onde um callable deveria ser usado, e por isso podem ser uma fonte fácil de confusão para quem está lendo o código e não sabe que ele usa callables.

A sintaxe de first-class callables, além de ser mais elegante, evita esses problemas já que ao usar ela fica bem mais óbvio que o que está sendo passado ali realmente é uma função, então nosso exemplo poderia ter sido:

```php
array_map(strtoupper(...), $strs);

// Pode passar usando um array com a classe, e o método estático na forma de string
array_map(Str::upperCase(...), $strs);

// Pode passar um objeto que implementa o __invoke
array_map($upperCase(...), $strs);
```

Ela seria basicamente a mesma sintaxe de executar o dito callable, porém subsituindo os parâmetros por `...`, assim deixando bem mais aparente que o que está sendo passado é o callable e não o resultado da execução dele.

# Conclusão

Eu tenho acompanhado com muito entusiasmo cada nova versão do PHP desde 2021, além de programar significativamente bastante nele, por isso, apesar da versão 8.3 não ter trazido tantas inovações assim, me faz bem feliz saber que o time por trás da linguagem sempre está alinhado com a comunidade e trazendo novos recursos que impactam bastante no dia-a-dia de quem usa o PHP.

Espero que este artigo tenha te animado a dar uma chance a linguagem, e caso tenha gostado compartilhe ele com aquele seu amigo ainda preso no PHP 5.6, até a próxima.

# Links que podem te interessar

- [Arrow Functions (7.4)](https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.arrow-functions);
- [Named Arguments (8.0)](https://www.php.net/releases/8.0/en.php#named-arguments);
- [Constructor Property Promotion (8.0)](https://www.php.net/releases/8.0/en.php#constructor-property-promotion);
- [Match expression (8.0)](https://www.php.net/releases/8.0/en.php#match-expression);
- [First-class callable syntax (8.1)](https://www.php.net/releases/8.1/en.php#first_class_callable_syntax);