Pressione enter para ver os resultados ou esc para cancelar.

PHP 7.4: novas funcionalidades – Parte 1

Se você é desenvolvedor ou de alguma forma está em contato com linguagens de programação, já deve ter visto artigos ou mesmo discussões apontando o fim das mais variadas linguagens. Os motivos vão desde aspectos técnicos até a pura torcida por conta de desenvolvedores de linguagens “rivais”. Com a linguagem PHP não foi diferente e por diversas vezes tentaram prever o fim dessa linguagem.

Good News PHP 7.4

Sim, ao contrário do que muitos acreditavam, o PHP vem se consolidando ano após ano, inclusive, uma pesquisa da W3Tech mostra que o PHP é a linguagem server-side mais utilizada em websites, estando presente em quase 80% deles. Isso não aconteceu por mero acaso, afinal, a linguagem vem evoluindo constantemente, tanto em termos de performance quanto na adição de novos recursos, e é exatamente sobre mais um passo dessa evolução que iremos falar hoje: o PHP 7.4!

A versão 7.4 entrou em feature freeze no dia 22 de Julho de 2019, em outras palavras, essa foi a data limite para entrada de novas features, a partir de então serão disponibilizadas versões Beta e RC (Release Candidate), até que no dia 28 de Novembro de 2019, será disponibilizada a versão oficial, de acordo com o timetable divulgado no site do próprio PHP.

Nessa versão, diversos recursos que eram esperados há muito tempo finalmente estarão disponíveis, dentre eles, o spread operatorarrow functions e até mesmo a tipagem em classes. Ficou curioso e quer saber como utilizar esses e outros recursos? A seguir vamos por a mão na massa e acima de tudo, entender melhor as novidades dessa versão!


Mãos à obra: Rodando o PHP 7.4!

Em primeiro lugar, para possibilitar a criação do ambiente onde executaremos nossos exemplos, de forma rápida e padronizada, usaremos o Docker. Para quem não conhece, de maneira sucinta, podemos dizer que o Docker é uma plataforma open source que possibilita o empacotamento da aplicação e suas dependências através de containers.

Caso você queira saber mais sobre o docker sugiro uma visita ao site oficial do projeto. Mas se você não entendeu muito bem ou não teve tempo de se aprofundar sobre o Docker, não tem problema, iremos utilizar alguns recursos bem básicos que serão explicados de forma bem didática, só é preciso primeiramente que você tenha instalado o Docker e o Docker Compose em sua máquina.

Posteriormente, basta clonar o projeto através do comando abaixo:

git clone git@github.com:rrmontuan/php74.git

Depois disso, acesse o diretório que foi criado ao clonar o projeto e execute o comando abaixo para executar nosso container:

docker-compose run --service-ports --rm app
(ou apenas make run caso você tenha o make instalado em seu SO)

Não entendeu o que aconteceu no comando acima? Tudo bem, eu explico. Em outras palavras, utilizamos o Docker Compose, que é um orquestrador de containers. Umas das vantagens de usar esse orquestrador é a facilidade que ele oferece quando lidamos com mais de um container.

No nosso caso, temos 2 containers, o container contendo o PHP 7.4 e outro container contendo o Nginx e, através do Docker Compose conseguimos tanto criar dependência entre esses containers (para o container com Nginx funcionar ele depende do funcionamento do container contendo o PHP) quanto garantir a comunicação entre eles, tudo de forma automática. Com relação aos parâmetros e opções utilizadas, segue um breve resumo:

  • run: Executa o container (e cria a imagem, caso ainda não tenha sido criada).
  • –service-ports: O comando é executado ativando e mapeando as portas no host. No nosso caso, estamos utilizando apenas a porta 80.
  • –rm: Remove o container após a sua execução.
  • app: O nome do serviço que será executado (definido no arquivo docker-compose.yml).
  • Após a execução do comando acima, se tudo der certo, você consequentemente deve ver a seguinte mensagem em seu terminal:

Agora que seu ambiente já está funcionando podemos iniciar nossa jornada pelas novas funcionalidades do PHP 7.4. Vamos adiante porque temos muita coisa para ver!

Spread Operator

Desde a versão 5.6 o PHP já possui suporte ao argument unpacking, permitindo, por exemplo, desempacotar um Array ou Traversable na chamada de uma função, por exemplo:

<?php

function exibeDados($nome, $idade, $profissao){
   echo sprintf(' %s tem %s ano(s) e é %s.', $nome, $idade, $profissao);
}

$dados = [
   'João',
   22,
   'Analista de Sistemas'
];

exibeDados(...$dados);
//João tem 22 ano(s) e é Analista de Sistemas.

Entretanto, essa funcionalidade se limita ao desempacotamento de argumentos, então não era possível utilizar o spread operator para criar um array a partir de outro array, por exemplo. Mas isso é passado, pois no PHP 7.4 o spread operator será estendido para a definição de arrays, então vamos ver isso na prática:

Primeiramente, vamos criar o arquivo spread-operator.php dentro do diretório app, com o seguinte conteúdo:

spread-operator.php
<?php

$parts = ['apple''orange''strawberry'];
$fruits = ['mango', ...$parts, 'pear'];

print_r($fruits);

Em segundo lugar, vamos acessar esse arquivo através do endereço http://localhost/spread-operator.php. Viu o resultado?

Array ( [0] => mango [1] => apple [2] => orange [3] => strawberry [4] => pear )

 

Nesse exemplo definimos algumas frutas no array $parts e depois criamos outro array “mesclando” novos valores com os valores “desempacotados” do array $parts entre eles. Para isso só foi necessário usar o spread operator (os três pontinhos) seguido do array $parts entre os novos valores. Resumindo, fácil né?

Até então, acredito que você utilizava a função array_merge para chegar nesse resultado, portanto, aí vai mais uma curiosidade: De acordo com esta RFC, o spread operator tem as seguintes vantagens sobre a função array_merge:

  1. O spread operator deve ter melhor performance que a função array_merge, não apenas porque o spread operator é uma estrutura da linguagem e o array_merge é uma função mas também porque a otimização de tempo de compilação pode ser mais performática para arrays constantes.
  2. A função array_merge só suporta arrays ao passo que o spread operator suporta arrays e objetos que implementam Traversable.

Sendo assim já podemos começar a pensar em aposentar a função array_merge, não é mesmo?

Mas, ainda tem mais coisa a respeito do spread operator. De acordo com a RFC, é possível espalhar o mesmo array múltiplas vezes, além disso o spread operator pode ser utilizado em qualquer lugar do array, ou seja, podemos ter elementos antes ou após ele, dessa forma:

<?php

$arr1 = [123];
$arr2 = [...$arr1]; //[1, 2, 3]
$arr3 = [0, ...$arr1]; //[0, 1, 2, 3]
$arr4 = [...$arr1, ...$arr2111]; //[1, 2, 3, 1, 2, 3, 111]
$arr5 = [...$arr1, ...$arr1]; //[1, 2, 3, 1, 2, 3]

Também é possível desempacotar arrays retornados por uma função:

<?php

function getFruitsArray(){
return ['apple''orange'];
}

$fruits = ['banana''mango', ...getFruitsArray()]; //['banana', 'mango', 'apple', 'orange']

Entretanto, algumas coisas não são permitidas, como tentar desempacotar arrays passados por referência:

<?php

$arr1 = [123];
$arr2 = [...&$arr1]; //sintaxe inválida

A execução do código acima tem como resultado o seguinte erro:

Parse error: syntax error, unexpected '&' in /var/www/default/htdocs/spread-operator.php on line 4

Por outro lado, se passarmos um valor por referência para um primeiro array esse valor também será passado por referência para o segundo array, por exemplo:

<?php

$one = 1;
$arr1 = [&$one, 23];
$arr2 = [0, ...$arr1];
print_r($arr2); //[0, 1, 2, 3]

$one = 99;
print_r($arr2); //[0, 99, 2, 3]

Arrow functions

A inclusão das arrow functions também era algo esperado há tempos. Se você já teve a oportunidade de programar nas versões mais recentes do Javascript (ES6+), provavelmente sabe do que estou falando. Antes do surgimento das arrow functions, funções como array_map, array_filter e array_reduce já possibilitavam o uso de funções anônimas como parâmetro, como no exemplo abaixo onde os itens de um array são elevados ao quadrado:

<?php

$numeros = [12345];

$numerosQuadrado = array_map(function($numero){
   return $numero * $numero;
}, $numeros);

print_r($numerosQuadrado);// [1, 4, 9, 16, 25]

Perceba que o primeiro parâmetro da função array_map se trata de uma função anônima. Parte da comunidade sempre achou essa definição muito verbosa, entretanto, ela pode ficar ainda mais difícil para ler e entender caso precisemos utilizar variáveis externas dentro dessa função anônima através da keyword useVejamos esse exemplo, onde os itens do nosso array são multiplicados por um valor que consta em um variável fora do escopo de nossa função:

<?php

$numeros = [12345];
$multiplicador = 2;

// Utilizamos a keyword use para "importar" a variável $multiplicador
$numerosMultiplicados = array_map(function ($numero) use ($multiplicador) {
   return $numero * $multiplicador;
}, $numeros);

print_r($numerosMultiplicados);// [2, 4, 6, 8, 10]

Percebe o quanto se torna difícil a leitura mesmo em um simples exemplo? Entretanto, a partir do PHP 7.4 poderemos utilizar as arrow functions, com sua sintaxe muito mais simples. Observe abaixo a comparação entre uma função anônima e uma arrow function:

<?php

//Função anônima
function($valor){
   return $valor * $valor;
}

//Arrow function
fn($valor) => $valor * $valor;

Pois bem, agora vamos implementar os exemplos anteriores usando nosso novo recurso: as arrows functions. 

Vamos criar o arquivo arrow-functions.php dentro do diretório app com o seguinte conteúdo:

arrow-functions.php
<?php

$numeros = [12345];
$multiplicador = 2;

$numerosQuadrado = array_map(fn($numero) => $numero * $numero, $numeros);
print_r($numerosQuadrado); // [1, 4, 9, 16, 25]

$numerosMultiplicados = array_map(fn($numero) => $numero * $multiplicador, $numeros);
print_r($numerosMultiplicados); // [2, 4, 6, 8, 10]

Agora vamos acessar através do endereço http://localhost/arrow-functions.php. O resultado é exatamente o mesmo, no entanto você conseguiu perceber as diferenças em nosso código? Vamos lá:

  1. Certamente está mais fácil de ler, primeiro porque as arrow functions são definidas em apenas 1 linha e também porque não há necessidade da utilização de chaves ou return;
  2. As arrow functions tem acesso as variáveis externas sem necessidade de definição explícita, ou seja, podemos acessar outras variáveis sem a keyword use.

Agora que você já conhece esse recurso, certamente isso lhe poupará algumas linhas de código, além disso, irá facilitar a leitura do mesmo! 🙂

Typed Properties 2.0

A partir do PHP 5 foi introduzido o conceito de Type Hints, que sofreu uma série de evoluções (e acabou ficando conhecido por Type declarations) e que hoje permite a definição dos tipos (scalar types, Classes/Interfaces, callable, etc) dos argumentos de funções e métodos. Por exemplo:

<?php

function mostraListaPresentes(array $lista){
   
   $presentes = implode(', ', $lista);
   
   return sprintf('Os presentes desse ano são: %s.', $presentes);    
}

echo mostraListaPresentes(['bola''patins''skate']);

Contudo, até o momento não era possível definir tipos para as propriedades de classes, mas no PHP 7.4 isso mudou com o suporte a declaração de tipos de propriedades. Agora você já pode definir se uma propriedade deve receber somente strings ou inteiros, por exemplo. Vamos criar o arquivo typed-properties.php no diretório app com o seguinte conteúdo:

typed-properties.php
<?php

class Pessoa
{
 public string $nome;
 public int $idade;

 public function sobre(){
   echo sprintf('Meu nome é %s e tenho %d ano(s).', $this->nome, $this->idade);
 }
}

$pessoa = new Pessoa();
$pessoa->nome = 'Pedro';
$pessoa->idade = 15;
$pessoa->sobre(); //Meu nome é Pedro e tenho 15 ano(s).

Perceba que definimos na classe Pessoa os atributos nome e idade, sendo o primeiro do tipo string e o segundo do tipo int (inteiro). Dessa forma, definimos que nossos atributos irão aceitar somente os tipos previamente definidos (e também aqueles que podem ter conversão implícita). Ao executar a página http://localhost/typed-properties.php você deverá ver a apresentação de nosso amigo Pedro sem problema algum.

Aposto que você está curioso para ver o que acontece caso seja atribuído um conteúdo de um tipo diferente ao definido para a propriedade, não é mesmo? Então façamos um teste, vamos atribuir um array para a propriedade idade, assim:

$pessoa->idade = [];

Posteriormente tente executar o script novamente. Bem, acredito que você deve ter tido um erro parecido com esse aqui:

Fatal error: Uncaught TypeError: Typed property Pessoa::$idade must be int, array used in /var/www/default/htdocs/typed-properties.php:15 Stack trace: #0 {main} thrown in /var/www/default/htdocs/typed-properties.php on line 15

Isso é o que acontece quando se tenta atribuir um dado de tipo diferente daquele definido para a propriedade, um Fatal error. No nosso caso o erro diz que a propriedade idade deveria ser um inteiro mas recebeu um array. Interessante, não?

Podemos usar alguns tipos para as nossa propriedades, por exemplo:

  • bool
  • int
  • float
  • string
  • array
  • object
  • iterable
  • self
  • parent
  • classes ou interfaces
  • e os nullable types, usando o símbolo ? antes do tipo. Exemplo: ?int, ?string, etc.

Com relação aos nullables types, caso você queira iniciar uma propriedade com o valor nulo ou mesmo atribuir tal valor ao longo de seu código, é esse tipo que deve ser usado, por exemplo:

<?php

class Pessoa
{
 public string $nome;
 public ?string $sobrenome = null;
 //...
}

Existem outras funcionalidades no PHP 7.4?

Se você está fazendo essa pergunta é porque deve ter gostado do que viu e está querendo mais, acertei? Pra nossa sorte o PHP 7.4 está trazendo diversas outras funcionalidades, mas para não tornar esse artigo ainda mais extenso abordaremos elas em uma segunda parte, que será publicada em breve, onde falaremos de FFI, preloading, Weak References e muitos mais. Enquanto isso, que tal praticar o que foi visto neste artigo?

Quer exemplos de funcionamento do PHP 7.4?

Antes de finalizar o artigo certamente queria deixar uma dica. Caso você queira verificar exemplos em funcionamento de forma rápida basta acessar o endereço: http://localhost/exemplos (Para isso seu container precisa estar em execução, ok?).

Em suma, lá você encontrará exemplos das funcionalidades que vimos durante esse artigo. Bons estudos e até a próxima!