BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Artigos PHP 7 - Novos recursos para os tipos

PHP 7 - Novos recursos para os tipos

Favoritos

Pontos Principais

  • O PHP 7.0 adicionou declarações de tipo escalar para strings, inteiros, floats e booleans;

  • O PHP 7.0 adicionou suporte para declarações de tipo para os retornos;

  • O PHP 7.1 adicionou suporte para os tipos nullable de parâmetro e de retorno;

  • O void é um tipo de retorno válido a partir do PHP 7.1;

  • OPHP 7.1 adicionou um novo tipo composto chamado iterável;

  • O PHP 7.4 adiciona suporte para propriedades tipadas, que são tipos para propriedades de classe.

 

Já exploramos algumas das melhorias introduzidas no PHP 7.x em outros dois artigos: PHP 7 - Introdução e Melhorias no POO e PHP 7 - Melhorias de Classes e Interfaces Classes. Para definir o background para este artigo, temos que lembrar que o PHP é uma linguagem fracamente tipada, que em outras palavras significa que o tipo de dados das variáveis não precisa ser declarado.

Neste artigo, iremos explorar os novos recursos relacionados a tipos disponíveis no PHP 7.x.

Declarações de tipo escalar

As declarações de tipo não são nenhuma novidade no PHP. Elas são usadas para fazer notação dos parâmetros da função e os parâmetros requerem que um argumento seja do tipo especificado. O suporte para declarações de tipo para classe, interface e self foi adicionado no PHP 5.0.0, enquanto o suporte para declarações de tipo para os arrays foi adicionado no PHP 5.1.0, e o suporte para declarações de tipo para os callables foi adicionado no 5.4.0.

O PHP 7.0 adicionou suporte para declarações de tipo escalar para os tipos string (strings), int (inteiros), float (números de ponto flutuante) e bool (booleans).

Vamos demonstrar essas declarações com um exemplo. Crie um script chamado typedeclr.php no diretório raiz do servidor e copie o seguinte código:

<?php
class A {}
class B extends A {}
class C {}
function f(A $a) {
	echo get_class($a)."\n";
}
 
f(new A);
f(new B);
f(new C);
?>

A classe B estende a classe A. A classe C não estende nenhuma classe e define uma função f() com um parâmetro do tipo A. Em seguida, o script invoca instâncias de passagem de função das classes A, B e C sucessivamente como argumentos. Assumindo a mesma configuração do primeiro artigo PHP 7 - Introdução e melhorias POO, execute o script com o url http://localhost:8000/typedeclr.php. Embora chamar f() passando instâncias das classes A e B não gere um erro e produza o valor definido na função, chamar f() com um argumento do tipo C gera um TypeError:

AB
Uncaught TypeError. Argument 1 passed to f() must be an instance of A, instance of C given

Agora, vamos discutir os tipos escalares. Os tipos escalares advém de dois tipos, coercivo (padrão) e estrito. Vamos criar um script chamado sumints.php e vamos definir uma função SumInts tomando parâmetros do tipo int. Agora, invoque a função com alguns dos argumentos fornecidos como floats e outros como string, por exemplo:

echo SumInts(1,'3',0,3.6);

O script sumints.php está escrito abaixo, incluindo uma diretiva declare comentada que discutiremos mais tarde quando cobrirmos os tipos escalares estritos.

<?php
   //declare(strict_types=1);
  function SumInts(int ...$ints) 
  {
  	return array_sum($ints);
  }
  echo SumInts(1,'3',0,3.6);
?>

Se executar o script, verá a soma dos argumentos convertidos em valores inteiros. Em outras palavras, argumentos de tipo errado são convertidos (coagidos) para o tipo esperado. Um valor 9 é gerado para o sumints.php, conforme mostrado na Figura 1.

Figura 1. Resultado do sumints.php

Os floats são convertidos em inteiros removendo o valor fracionário. Como um exemplo, 3.6 torna-se 3 e não é arredondado para 4. O valor da string "3" é convertido para 3.

As declarações de tipo escalar estrito funcionam de uma maneira completamente diferente. Em primeiro lugar, o modo estrito está disponível apenas para declarações de tipo escalar e não para declarações de classe, interface, callables ou arrays. Vamos voltar para o script anterior e descomentar a diretiva declare() para usar declarações de tipo escalar estrito. Observe que a diretiva declare() deve aparecer no início do arquivo onde o tipo escalar deve ser usado.

declare(strict_types=1);

Quando executarmos o script novamente e veremos que um TypeError será gerado. O TypeError indica que os argumentos devem ser do tipo int.

Uncaught TypeError: Argument 2 passed to SumInts() must be of the type int, string given

O modo estrito não se aplica a chamadas de funções de funções internas, conhecidas como integradas.

Ao usar o modo padrão coercitivo padrão para declarações do tipo escalar, um argumento de valor null não tem seu tipo alterado para o tipo esperado e um TypeError é mostrado como resultado. Para demonstrar isso, podemos criar um script sumints_null.php onde uma função que espera um valor inteiro receba um argumento null:

<?php
  function SumInts(int ...$ints) 
  {
  	return array_sum($ints);
  }
  echo SumInts(1,'3',0,null);
?>

Se executar o script, obterá um TypeError.

Uncaught TypeError: Argument 4 passed to SumInts() must be of the type int, null given

Se quisermos permitir um argumento null, devemos especificar um valor null padrão para o parâmetro correspondente, como no script abaixo:

<?php
  function SumInts(int $a=null) 
  {
  	return array_sum($a);
  }
  echo SumInts(null);
?>

Com o modo estrito, os valores dos argumentos devem ser do tipo definido na função, com uma exceção: Valores inteiros podem ser fornecidos para uma função que espera um float.

Vamos criar um script chamado sum_floats.php onde uma função SumFloats com parâmetros do tipo float é definida. Chame a função passando números inteiros para alguns dos argumentos:

<?php
declare(strict_types=1);
  function SumFloats(float ...$floats) : float
  {
  	return array_sum($floats);
  }
  echo SumFloats(1.1,2.5,3);
?>

Execute o script para ter o resultado, o float 6.6, será impresso. Nesse caso, os valores int são convertidos em float.

O próximo exemplo mostrará como usar declarações de tipo escalar estrito com tipo float. Crie um script sumfloats.php e defina uma função com dois parâmetros float, sendo o retorno int. A declaração do tipo de retorno é outro recurso novo e será discutido com mais detalhes mais a frente, agora, vamos apenas assumir que eles existem e funcionam para este exemplo. A função retorna um valor adicionando dois argumentos. Vamos chamar a função com um argumento int e o outro sendo string:

<?php
declare(strict_types=1);
 
  function SumFloats(float $a, float $b) : int
  {
  	return $a + $b;
  }
 
  echo SumFloats(1,'3');
 
?>

Se executar o script, um TypeError será gerado, indicando que um argumento enviado à função deve ser do tipo float enquanto uma string foi fornecida.

Uncaught TypeError: Argument 2 passed to SumFloats() must be of the type float, string given

Se removermos a diretiva de declaração do modo estrito e executarmos o script novamente, o modo coercivo fará a função retornar 4.

As declarações de tipo escalar podem ser usadas com strings. Vamos crie um script chamado reverse.php e vamos adicionar uma função a ele onde um parâmetro string é passado, depois invertemos e retornarmos a string resultante. Usando um bloco try/catch, vamos chamar a função com um valor de ponto flutuante 3.5:

<?php
  
  function Reverse(string $a) 
  {
  	return strrev($a);
  }
 
try{
  echo Reverse(3.5);
}catch (TypeError $e) {
	echo 'Error: '.$e->getMessage();
}
?>

Se executar o script, o valor de 5.3 será mostrado. Se adicionar a diretiva declare(strict_types=1); para o modo estrito e executar o script novamente, um error será gerado:

Error: Argument 1 passed to Reverse() must be of the type string, float given

A tipagem estrita para declarações de tipo escalar se aplica apenas a chamadas de função de dentro do arquivo onde a tipagem estrita está habilitada e não a chamadas de função de outro arquivo onde ela não está declarada. Da mesma forma, se o arquivo que faz a chamada de função também tiver a tipagem estrita ativada, o modo estrito será utilizado.

Para demonstrar isso, crie um script PHP chamado reverse_2.php com o modo estrito habilitado. Adicione uma função (chamada Reverse) que inverte a string e retorna um valor string:

<?php
declare(strict_types=1);
  function Reverse(string $a) 
  {
  	return strrev($a);
  }?>

Agora crie outro script, chamado reverse_1.php que faz uma requisição do script reverse_2.php e chama a função Reverse passando um valor float:

<?php
require_once('reverse_2.php');
  echo Reverse(3.5);
?>

Execute o script reverse_1.php. Como o modo estrito está habilitado no reverse_2.php, esperamos que o retorno seja um TypeError, pois a função que espera um parâmetro string é chamada passando um valor float. Mas o reverse_1.php usa tipagem fraca por isso o valor float 5.3 será retornado. Porém, se o modo estrito estiver habilitado em reverse_1.php, a tipagem estrita é aplicada e um TypeError é gerado:

Uncaught TypeError: Argument 1 passed to Reverse() must be of the type string, float given

Nosso próximo exemplo trata de declarações de tipo escalar para o tipo boolean. Crie um script com nome test.php com o modo estrito habilitado e adicione uma função que aceite um parâmetro do tipo bool e retorne o valor do parâmetro sem nenhuma alteração. Invoque a função com um valor de string 'true':

<?php
declare(strict_types=1);
 function test(bool $param) {return $param;}
  echo test('true');
 ?>

Executar o script no navegador fornecerá o TypeError abaixo.

Uncaught TypeError: Argument 1 passed to test() must be of the type bool, string given

Se a diretiva declare para o modo estrito for comentada ou removida e o script for executado novamente, o modo coercivo será aplicado. Nesse caso, a string 'true' é convertida para o valor boolean true e a saída será 1.

Agora, vamos nos aprofundar no uso dos valores NULL com strings do tipo escalar.

Crie o script string_null.php contendo duas funções, cada uma delas recebendo um argumento string. Uma das funções tem o valor do argumento padrão definido como NULL e a outra função não tem nenhum valor padrão definido. Chame cada uma das funções com um valor de string, nenhum valor e com valor NULL:

<?php
function string_null_1(string $str) {
  var_dump($str);
}
function string_null_2(string $str = NULL) {
  var_dump($str);
}
string_null_1('a');     
string_null_2('b');   
string_null_1();
string_null_2();      
string_null_2(null); 
string_null_1(null);   
?>

Se executar o script, será gerado um erro quando for chamado a função string_null_1() sem nenhum argumento.

Uncaught ArgumentCountError: Too few arguments to function string_null_1(), 0 passed i

Comente a chamada de função que gera a mensagem anterior e execute o script novamente. Verá que as chamadas de função que fornecem argumentos do tipo string geram uma string, conforme o esperado. Ao chamar string_null_2(), que tem um valor padrão definido como NULL, sem argumentos, o resultado será NULL. Chamar string_null_2() com um valor NULL como argumento também produz NULL, conforme o esperado. Chamar string_null_1() passando NULL gera um TypeError, uma vez que o argumento NULL não é convertido em uma string no modo coercivo.

Declarações de tipo no retorno

Como mencionamos brevemente acima, o PHP 7.0 adicionou suporte para declarações de tipo de retorno, que são semelhantes às declarações do tipo de parâmetro. Os mesmos tipos do PHP podem ser usados com declarações de tipo de retorno e de parâmetro. Para demonstrar o uso de declarações de tipo de retorno, vamos criar um script chamado reverse_return.php que declara uma função com um parâmetro e um retorno do tipo string.

<?php
  function Reverse(string $a) : string 
  {
  	return strrev($a);
  }
try{
  echo Reverse('hello');
}catch (TypeError $e) {
	echo 'Error: '.$e->getMessage();
}?>

Chamar a função com o valor de entrada "hello" fará com que a string seja invertida, obtendo assim o resultado olleh. A diretiva de modo estrito que é usada para declarações de tipo escalar também é aplicada para retornar as declarações de tipo. Para demonstrar isso, vamos criar um script chamado reverse_return_strict.php e vamos adicionar a diretiva de modo estrito no início. Adicione então a função que recebe um parâmetro string e retorna um valor do mesmo tipo. Ao invés de retornar um valor de string, faça com que um valor retornado seja umint:

<?php
declare(strict_types=1);
  function Reverse(string $a) : string 
  {
  	return 5;
  }
 
try{
  echo Reverse("hello");
}catch (TypeError $e) {
	echo 'Error: '.$e->getMessage();
}
 
?>

Ao chamarmos a função com um valor de string o seguinte erro irá aparecer:

Error: Return value of Reverse() must be of the type string, int returned

Se remover a declaração de modo estrito, alternando assim para o modo coercivo, e executar o script novamente, o valor '5' será impresso. Nesse caso, o valor int retornado é convertido em uma string devido à tipificação fraca.

No modo coercitivo, o valor de retorno é convertido para o tipo esperado, se necessário. Podemos ver isso modificando o script e declarando o tipo de retorno como int, mas, na verdade, retornando uma string '5'.

<?php
  	function Reverse(string $a) : int
  {
  	return '5';
  }
try{
  echo Reverse("hello");
}catch (TypeError $e) {
	echo 'Error: '.$e->getMessage();
}
?>

Chame a função passando um valor string. Neste caso, o valor do tipo int 5 é retornado após ser convertido do o valor string '5'. Para que a conversão seja feita no modo coercitivo, o valor retornado deve ser possível sofrer a conversão para o tipo de retorno declarado. Por exemplo, se você declarar um tipo int de retorno e retornar a string 'cinco', um erro será gerado.

Return value of Reverse() must be of the type int, string returned

Embora as declarações de tipo escalar sejam novas no PHP 7, as declarações de tipo de classe ainda não são suportadas. Os tipos de classe podem ser usados em declarações de tipo de retorno, porém, do jeito que demonstraremos a seguir.

Vamos criar um script return_obj.php e declarar uma classe Catalog incluindo algumas variáveis presentes em um catálogo de revista. Instancie a classe e defina os valores para as variáveis. Declare então uma função com o tipo de retorno Catalog, tendo um único parâmetro do tipo Catalog. No corpo desta função, basta retornar o próprio argumento de entrada:

function getCatalog(Catalog $catalog): Catalog {
	return  $catalog;
}

Chame a função com um objeto da classe Catalog e, em seguida, dê um var_dump para ver o que o código irá retornar.

var_dump(getCatalog($Catalog1));

O script return_obj.php está abaixo:

<?php
class Catalog
{
	public $title;
	public $edition;
}
$Catalog1 = new Catalog();
$Catalog1->title = 'Oracle Magazine';
$Catalog1->edition = 'January-February2018';
function getCatalog(Catalog $catalog): Catalog {
	return  $catalog;
}
var_dump(getCatalog($Catalog1));
?>

A execução do script produzirá os valores do campo do objeto da classe Catalog, conforme mostrado abaixo.

object(Catalog)#1 (2) { ["title"]=> string(15) "Oracle Magazine" ["edition"]=> string(20) "January-February2018" }

Como discutimos no primeiro artigo desta série, PHP 7 - Introdução e melhorias no POO, as heranças de classe no PHP 7.2 suporta covariância de tipo de retorno de um nível e contravariância para nenhum tipo. Como deve se lembrar, a covariância para o tipo de retorno torna possível fornecer um retorno mais restrito, enquanto a contravariância para os tipos de parâmetro torna possível fornecer um tipo de parâmetro mais genérico. O PHP 7.4 adicionou suporte completo para a covariância e a contravariância.

Para demonstrar isso, vamos criar um script chamado return_type_inheritance.php. Vamos adicionar uma classe Catalog com uma função declarando um parâmetro do tipo string sem nenhum tipo de retorno. Depois, criemos outra classe CatalogMagazine que vai estender a classe Catalog. O tipo do retorno da função é string e o tipo de parâmetro será omitido, contando com o suporte de ampliação do tipo de parâmetro do PHP 7. Por fim, instancie cada classe e chame a função para gerar o valor que ela irá retornar:

<?php
class Catalog
{
	public function printItem(string $string)
	{
    	return 'Catalog: ' . $string . PHP_EOL;
	}
}
class CatalogMagazine extends Catalog
{
	public function printItem($string) : string
	{
        return 'CatalogMagazine: ' . $string . PHP_EOL;
	}
}
$catalog = new Catalog();
$catalogMagazine = new CatalogMagazine();
echo $catalog->printItem('Catalog'); 
echo $catalogMagazine->printItem('CatalogMagazine'); 
?>

A execução do script produzirá o retorno das funções definidas em cada objeto de classe:

Catalog: Catalog CatalogMagazine: CatalogMagazine

Tipos nulos

Não é incomum que valores nulos sejam passados como parâmetros ou que sejam retornados de funções. O tipo null no PHP tem apenas um valor e não faz distinção entre maiúsculas e minúsculas NULL.

O PHP 7.1 adicionou suporte para os tipos de parâmetro e retornos nulos. Prefixar um parâmetro ou tipo de retorno com um ponto de interrogação ? para fazer com que aceite o nulo. Para demonstrar o uso do tipo null, vamos criar um script chamado hello-nullable.php. Defina uma função hello() especificando um tipo de retorno nulo. Os tipos nulos não implicam que a função irá retornar um valor nulo, por isso, vamos retornar um valor diferente:

function hello(): ?string
{
	return 'Hello';
}

Agora vamos definir outra função que retorna um valor nulo:

function hello_return_null(): ?string
{
	return null;
}

Por fim, defina uma função com um tipo de parâmetro nulo:

<?php
function hello(): ?string
{
	return 'Hello';
}
echo hello();
echo "<br/>";
function hello_return_null(): ?string
{
	return null;
}
echo hello_return_null();
echo "<br/>";
function hello_null_arg(?string $name)
{
	return 'Hello';
}
echo hello_null_arg(null);
echo "<br/>";
?>

A execução do script produzirá o seguinte resultado:

Hello
Hello

A segunda chamada da função não produz saída porque o echo converte o argumento em uma string e o retorno nulo não tem nenhum valor de string para mostrar na tela. Se usarmos o var_dump() ao invés do echo, um valor NULL deve ser mostrado da seguinte maneira:

Hello
NULL
Hello

A seguir, demonstraremos com um exemplo que se o tipo de retorno de uma função for um tipo de classe, o nulo não pode ser retornado. Faça um script chamado return_null.php e copie o seguinte código:

<?php
class Catalog
{
	public $title;
	public $edition;
}
$Catalog1 = new Catalog();
$Catalog1->title = 'Oracle Magazine';
$Catalog1->edition = 'January-February2018';
function getCatalog(?Catalog $catalog): ?Catalog {
	return  $catalog;
 
}
function getCatalog_2(?Catalog $catalog): ?Catalog {
	return  null;
}
function getCatalog_3(?Catalog $catalog): Catalog {
	return  $catalog;
}
var_dump(getCatalog(null));
var_dump(getCatalog_2(null));
var_dump(getCatalog_3(null));
?>

O script declara uma classe Catalog com dois campos, também cria uma instância da classe e define os valores dos campo para inicializá-los. Todas as três funções no script declaram um parâmetro nulo do tipo Catalog. Duas das funções têm o tipo de retorno Catalog nulo e uma função possui o tipo de retorno Catalog não nulo. Cada uma das funções é chamada com um argumento nulo e a chamada é incluída no var_dump().

Como o retorno indica, se o tipo do retorno for nulo e um argumento nulo for passado, o valor do retorno será nulo se o argumento nulo ou não for retornado. Mas se o tipo de retorno for um tipo de classe como Catalog e for retornado nulo, um erro será gerado indicando que o valor de retorno deve ser uma instância do tipo class, no exemplo, Catalog:

 NULL NULL
Uncaught TypeError: Return value of getCatalog_3() must be an instance of Catalog, null returned

Usando void como tipo de retorno para funções

Como mencionado, o suporte para declarações do tipo de retorno foi adicionado no PHP 7.0, enquanto o PHP 7.1 introduziu o tipo de retorno void. Em uma função que retorna void, a instrução do retorno deve estar vazia ou ser totalmente omitida. O NULL não deve ser confundido com o tipo void, já que NULL é um valor do tipo nulo, enquanto void implica a ausência de um valor.

Para demonstrar o uso de um tipo de retorno void, vamos criar um script chamado hello-void.php e copiar o seguinte código abaixo:

<?php
function hello(): void
{
	echo 'Hello';
	return;
}
echo hello();
echo "<br/>";
?>

O script declara uma função chamada hello() com o tipo de retorno void. A função produz uma string 'Hello' e faz um retorno vazio. Se o tipo de retorno for void, a função não deve retornar um valor. Execute o script, e receba "Hello" como resultado.

A seguir, vamos demonstrar que uma função de retorno do tipo void não pode retornar um valor, nem mesmo NULL. Crie outro script, o return_void.php e, declare uma classe Catalog contendo uma função com o tipo de retorno sendo void, e retorno um valor NULL. Chame a função passando um parâmetro Catalog como argumento. O script return_void.php está abaixo:

<?php
class Catalog
{
	public $title;
	public $edition;
}
$Catalog1 = new Catalog();
$Catalog1->title = 'Oracle Magazine';
$Catalog1->edition = 'January-February2018';
function getCatalog(Catalog $catalog): void {
	return NULL;
}
var_dump(getCatalog($Catalog1));
?>

A execução do script gerará uma mensagem de erro.

A void function must not return a value (did you mean "return;" instead of "return null;"?)

Modifique ligeiramente o script para que a função tenha uma instrução de return vazia, que é válida para o tipo de retorno void. Chame a função com uma instância de Catalog como sendo um argumento.

function getCatalog(Catalog $catalog): void {
	return;
}
var_dump(getCatalog($Catalog1));

Neste caso, um valor NULL irá retornar, pois usamos uma instrução de retorno vazia, como o var_dump deixa isso bem claro.

Novo tipo iterável

O iterable é um novo tipo composto no PHP 7.1. Pseudotipos são palavras-chave usadas para tipos ou valores que um argumento pode ter. O tipo iterable pode ser usado como um tipo de parâmetro ou de retorno. Ele aceita uma matriz ou um objeto que implementa a interface Traversable, sendo que ambos podem ser iterados usando simplesmente o foreach.

Primeiro, devemos demonstrar como usar o iterable com um array. Crie um script chamado iter.php onde iremos declarar uma função com um parâmetro iterable. Os parâmetros de tipo iterable podem declarar um valor padrão que é NULL ou uma matriz. Dentro da função, vamos iterar usando foreach() para que então possamos produzir os valores. Vamos criar uma matriz e invocar a função usando a matriz como argumento. O código do script está abaixo.

<?php
$catalog = ['Oracle Magazine','Java Magazine','PHP Magazine'];
function iterator(iterable $iter)
{
	foreach ($iter as $val) {
    	echo $val;
    	echo "<br/>";
	}
}
iterator($catalog);
?>

Ao executar o script iremos escrever os valores da matriz:

Oracle Magazine
Java Magazine
PHP Magazine

Um tipo iterable também aceita um objeto de classe que implementa a interface Traversable. Para demonstrar isso, vamos criar um script iter_traversable.php e declarar uma classe que implemente a interface IteratorAggregate, que estende ainda mais a interface Traversable. Declare três propriedades de classe.

public $journal1 = "Oracle Magazine";
	public $journal2 = "Java Magazine";
	public $journal3 = "PHP Magazine"

Adicione um construtor de classe que implemente a função de interface abstract public Traversable getIterator (void). Vamos chamar essa classe de catalogData. No mesmo script, vamos declarar também uma função com um tipo de parâmetro iterable. Dentro desta função, vamos usar o foreach com o iterable usando o parâmetro iterável para gerar seus valores:

function iterator(iterable $iter)
{
foreach($iter as $key => $value) {
    var_dump($key, $value);
	echo "\n";
}
}

Por fim, chame a função com uma instância da classe catalogData sendo passada como argumento:

$obj = new catalogData;
iterator($obj);
The iter_traversable.php script is listed:
<?php
class catalogData implements IteratorAggregate {
public $journal1 = "Oracle Magazine";
	public $journal2 = "Java Magazine";
	public $journal3 = "PHP Magazine";
	public function __construct() {
     	
	}
 
	public function getIterator() {
    	return new ArrayIterator($this);
	}
}
$obj = new catalogData;
function iterator(iterable $iter)
{
foreach($iter as $key => $value) {
    var_dump($key, $value);
	echo "\n";
}
}
iterator($obj);
?>

Agora, vamos executar o script para gerar os pares de índice/valor de maneira iterável:

string(8) "journal1" string(15) "Oracle Magazine" string(8) "journal2" string(13) "Java Magazine" string(8) "journal3" string(12) "PHP Magazine"

O tipo iterável oferece suporte a contravariância total (para tipo de parâmetro) e covariância (para tipo de retorno). A covariância e contravariância são discutidas com mais detalhes no artigo anterior PHP 7 - Introdução e melhorias no POO. A contravariância total implica que o array de tipos de parâmetro e o Traversable podem ser transformados em iterável. A covariância total implica que o tipo de retorno iterável pode ser reduzido a um array ou a um Traversable. A seguir, demonstraremos a covariância e a contravariância com iterável usando um exemplo a seguir. Crie um script chamado iter_extend_ret_type.php e declare uma classe contendo uma função iteradora com um parâmetro de matriz e o tipo de retorno iterável sendo nulo. Dentro da classe, itere o argumento do array usando foreach e mostre os valores. Declare uma classe que estenda a primeira e substitui a função iterator por uma função que possui um parâmetro iterable e um tipo de retorno array que pode ser nulo. Crie uma instância da classe estendendo e invoque a função iteradora. O código do script está abaixo:

<?php
class ClassA{
function iterator(array $arr)  : ?iterable
{
	foreach ($arr as $val) {
    	echo  $val;
    	echo "<br/>";
    	return null;
	}
}
}
class ClassB extends ClassA{
function iterator(iterable $iter)  : ?array
{
	foreach ($iter as $val) {
    	echo  $val;
    	echo "<br/>";
 
   	
	}
return null;
}
}
$catalog = ['Oracle Magazine','Java Magazine','PHP Magazine'];
$classB=new ClassB();
$classB->iterator($catalog);
?>

Execute o script, que resultará em:

Oracle Magazine
Java Magazine
PHP Magazine

As funções com tipo de retorno iterable também podem ser usadas como geradores. Crie um script com nome de iterable_gen.php e chame uma função geradora com tipo de retorno iterable. Declare alguns valores yeld na função do gerador e retorne um array. Em seguida, escreva os valores incluídos na função geradora. Produza também o valor de retorno da função do gerador usando a função getReturn() para obter o valor de retorno:

<?php
function generator(): iterable {
	yield 'a';
	yield 'b';
	yield 'c';
	return ['a','b','c'];
}
$gen = generator();
foreach ($gen as $value) {
	echo "$value\n";
}
var_dump($gen->getReturn());
?>

Execute o script para mostrar os valores gerados pela função do gerador.

a b c array(3) { [0]=> string(1) "a" [1]=> string(1) "b" [2]=> string(1) "c" }

Propriedades tipadas

A versão 7 fez várias melhorias no sistema de tipos do PHP. A versão 7.4 adiciona suporte para propriedades tipadas, onde podemos usar quando quisermos declarar tipos para propriedades das classes. As propriedades de classe não exigem que declaremos explicitamente os métodos getter/setter para elas. Algumas das características mais importantes são:

  • Os tipos também podem ser declarados em propriedades static;
  • Referências também possuem suporte a propriedades tipadas;
  • As propriedades tipadas são afetadas pela diretiva strict_types, assim como os tipos de parâmetro e de retorno;
  • Os tipos podem ser usados com notação var;
  • Os valores padrão para propriedades tipadas podem ser declarados;
  • O tipo de propriedades múltiplas pode ser declarados em uma única declaração;
  • int e um float são fundidos implicitamente;
  • O tipo de uma propriedade anulável pode ser declarado;
  • Uma propriedade de classe do tipo callable ou void não pode ser declarada.

Como exemplo, considere a seguinte classe chamada Catalog, que ilustra várias das características anteriores:

<?php
declare(strict_types=1);
class Catalog {
    public int $catalogid,$journalid;
    public ?string $journal=null;
    public static string $edition="January-February 2020";
    var bool $flag;
    public float $f=1;
public function __construct(int $catalogid,int $journalid,string $journal,bool $flag)
    {
        $this->catalogid = $catalogid;
        $this->journalid = $journalid;
        $this->journal = $journal;
        $this->flag = $flag;
    }
}
$c = new Catalog(123,345,"PHP Magazine",true);
echo "Catalogid: ".$c->catalogid."\n";
echo "Journalid: ".$c->journalid."\n";
echo "Journal: ".$c->journal."\n";
echo "Flag: ".$c->flag."\n";
echo "Edition: ".Catalog::$edition."\n";
?>

Executando o script acima, reproduziremos o resultado da Figura 2.

Figura 2. Ilustrando o resultado do uso de propriedades tipadas

Para demonstrar o uso de propriedades junto com o modo de tipo estrito, considere o seguinte script que define strict_types como 1 e declara uma propriedade de classe chamada $catalogid do tipo int. Em seguida, tente atribuir à propriedade $catalogid um valor string:

<?php
declare(strict_types=1);
class Catalog {
    public int $catalogid;
}
$c = new Catalog();
$c->catalogid = "123"; 
?>

Quando o script é executado, o seguinte TypeError é criado:

Uncaught TypeError: Typed property Catalog::$catalogid must be int, string used

Se definir, nenhum TypeError será retornado. A distinção importante a ser observada aqui é que o valor atribuído deve satisfazer o modo strict_types no local de gravação da propriedade. Em outras palavras, o modo strict_types no local da declaração da propriedade não é relevante. Se o modo strict_types for definido como 1 no local de gravação, o valor atribuído deverá ser do tipo declarado. Para demonstrar o uso de modos diferentes, devemos usar dois arquivos diferentes a.php e o b.php com o modo strict_types definido como 1 e 0 respectivamente. Os scripts também demonstram as seguintes características de referências quando usados com propriedades tipadas:

  • Uma propriedade não inicializada como nula pode ser acessada por referência;
  • Uma propriedade não inicializada como sendo não nula não pode ser acessada por referência;
  • Uma matriz pode ser acessada por referência.

No a.php, defina o modo strict_types como 1 e declare uma classe A contendo uma propriedade de classe tipada chamada $a do tipo int. Certifique-se de que o script a.php também declare uma matriz, uma propriedade podendo ser nula e uma propriedade não nula:

<?php
declare(strict_types=1);
class A {
    public int $a;
    public array $array = [3, 1, 2];
    public ?int $x;
    public int $y; 
}

No segundo arquivo, b.php, faça um include de a.php e defina o modo strict_types como 0. Instancie a classe A e, em seguida, use o var_dump para gerar o valor da propriedade $a dessa instância, que é mostrado na lista a seguir após o comentário //. Em seguida, acesse a propriedade do tipo de array por meio da referência e, por fim, classifique e mostre o resultado.

Agora acesse a variável de tipo int $a por meio da sua referência e atribua a ela o valor string "1". Escreva a variável $a value usando var_dump e veremos que nenhum TypeError é mostrado. Isso ocorre porque o valor armazenado em $a é convertido no tipo int.

Observe também que podemos acessar a propriedade anulável não inicializada $x por meio de sua referência. Nesse caso, será inicializado de forma transparente para o NULL. Porém, se tentarmos acessar a propriedade não inicializada não nula $y, obteremos um TypeError.

<?php 
declare(strict_types=0);
include 'a.php';
$a = new A;
$a->a = "1";
var_dump($a->a); // int(1)
sort($a->array);
var_dump($a->array);// array(3) { [0]=> int(1) [1]=> //int(2) [2]=> int(3) }
$x =& $a->x; // Inicializado como nula
$y =& $a->y; //TypeError Uncaught Error: Cannot access //uninitialized non-nullable property A::$y by reference

A seguir, iremos discutir outra característica importante das propriedades tipadas que tem a ver com a não variância de tipo.

  • O tipo de uma propriedade não privada não pode ser alterada em uma classe derivada. Um tipo de propriedade não privada também não pode ser adicionado ou removido;
  • O tipo de uma propriedade privada pode ser alterado em uma classe derivada. Tipos de propriedade privada podem ser adicionados ou removidos.

Para demonstrar a não variância de tipo, declare uma classe chamada A que contém uma variável privada $a do tipo int, uma variável pública $b do tipo string, uma variável pública anulável $c do tipo int e uma variável pública $d do tipo string. Declare também uma propriedade de tipo void chamada $v para verificar se o tipo void não pode ser usado com propriedades.

<?php 
    class A {
    private int $a;
    public string $b;
    public ?int $c;
    public string $d;
    public void $v;//Property A::$v cannot have type void
}

Declare uma classe B que estende a classe A e altera os tipos das propriedades de classe. Altere o tipo da propriedade privada $a de int para string e torne-a pública, o que é possível. Se tentarmos alterar o tipo da propriedade pública $b de string para int, o tipo da propriedade pública $c anulável int para int, ou para omitir o tipo de propriedade pública $d, erros serão retornados como mostrado abaixo nos comentários.

class B extends A {
    public string $a;  
public  int $b;   // Type of B::$b must be string (as in class A)
public int $c;    // Type of B::$c must be ?int (as in class A)
public  $d;    // Type of B::$d must be string (as in class A)
}

Conclusão

Neste terceiro artigo da nossa série sobre PHP 7.x, discutimos os novos recursos no sistema de tipos do PHP.

O PHP 7.0 adicionou suporte para declarações de tipo escalar para os tipos string, int, float e bool. O PHP também 7.0 adicionou suporte para declarações de tipo de retorno a serem usadas na instrução return.

O PHP 7.1 adicionou suporte para o tipo null, que tem apenas um valor, NULL, e para um tipo de retorno chamado void, representando a ausência de qualquer valor. O PHP 7.1 também adicionou suporte para um novo tipo chamado iterable. Enquanto o tipo void pode ser usado apenas como tipo de retorno, o iterable pode ser usado como um parâmetro ou um retorno.

As propriedades tipadas foram adicionadas no PHP 7.4, permitindo declarar explicitamente os tipos para as propriedades da classe.

No próximo artigo da série, exploraremos novos recursos para as funções do PHP.

Sobre o autor

Deepak Vohra é um programador Java com as certificações Sun Certified Java Programmer and Sun Certified Web Component Developer. Deepak publicou artigos técnicos relacionados a Java e Java EE no WebLogic Developer's Journal, XML Journal, ONJava, java.net, IBM developerWorks, Java Developer's Journal, Oracle Magazine e devx. Além disso, publicou cinco livros no Docker e é um dos mentores desta tecnologia. Deepak também publicou vários artigos sobre PHP e um livro sobre Ruby on Rails para desenvolvedores de PHP e Java.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

BT