Este documento apresenta as novidades do PHP 7 sobre tipos de declaração de parâmetros, conhecidas anteriormente como type hinting. Inclui, ainda, exemplos de utilização e possíveis erros que podem ser encontrados durante sua utilização.
Introdução
Além de melhorias de desempenho, o PHP 7 trouxe novidades com relação à estrutura da linguagem. Agora, pode-se declarar parâmetros de métodos e funções com tipos básicos.
Esta técnica, anteriormente conhecida como type hinting, teve seu nome modificado para type declarations (declarações de tipos, em tradução livre), possivelmente porque agora é possível utilizar os tipos bool
, float
, int
e string
.
Antes do Novo Recurso
O exemplo abaixo apresenta um código-fonte para a classe Foo\Bar
que possui métodos simples que necessitam de parâmetros com tipos básicos da linguagem.
<?php
namespace Foo;
use InvalidArgumentException;
/**
* Classe de Exemplo
*/
class Bar
{
/**
* Nome
* @type string
*/
protected $name = '';
/**
* Modificador
* @type float
*/
protected $modifier = 1.0;
/**
* Ordem Crescente?
* @type bool
*/
protected $ascending = true;
/**
* Limite
* @type int
*/
protected $limit = PHP_INT_MAX;
/**
* Configura o Nome
*
* @param string $name Valor para Configuração
* @throws InvalidArgumentException Valor com Tipo Inválido
* @return self Próprio Objeto para Encadeamento
*/
public function setName($name) : self
{
// Tipagem Correta?
if (! is_string($name)) {
throw new InvalidArgumentException('Invalid "$name" Type');
}
// Configuração
$this->name = $name;
// Encadeamento
return $this;
}
/**
* Configura o Modificador
*
* @param float $modifier Valor para Configuração
* @throws InvalidArgumentException Valor com Tipo Inválido
* @return self Próprio Objeto para Encadeamento
*/
public function setModifier($modifier) : self
{
// Tipagem Correta?
if (! is_float($modifier)) {
throw new InvalidArgumentException('Invalid "$modifier" Type');
}
// Configuração
$this->modifier = $modifier;
// Encadeamento
return $this;
}
/**
* Configura a Ordem Crescente
*
* @param bool $ascending Valor para Configuração
* @throws InvalidArgumentException Valor com Tipo Inválido
* @return self Próprio Objeto para Encadeamento
*/
public function setAscending($ascending) : self
{
// Tipagem Correta?
if (! is_bool($ascending)) {
throw new InvalidArgumentException('Invalid "$ascending" Type');
}
// Configuração
$this->ascending = $ascending;
// Encadeamento
return $this;
}
/**
* Configura o Limite
*
* @param int $limit Valor para Configuração
* @throws InvalidArgumentException Valor com Tipo Inválido
* @return self Próprio Objeto para Encadeamento
*/
public function setLimit($limit) : self
{
// Tipagem Correta?
if (! is_int($limit)) {
throw new InvalidArgumentException('Invalid "$limit" Type');
}
// Configuração
$this->limit = $limit;
// Encadeamento
return $this;
}
}
Nota-se que as verificações de tipos de dados informados como parâmetros ficam a cargo do programador. Para cada método informado, caso exista a necessidade de que certo tipo específico de dado seja apresentado, adiciona-se uma condicional que verifica o tipo correto, e, em caso de erro, atira-se uma exceção do tipo InvalidArgumentException
.
Aplicando Declarações de Tipos
Agora no PHP 7 é possível utilizar declarações para garantir a passagem dos tipos básicos. O próximo código-fonte contém a mesma estrutura do exemplo anterior, porém com as novas declarações de tipo disponíveis.
<?php
// ...
/**
* Configura o Nome
*
* @param string $name Valor para Configuração
* @return self Próprio Objeto para Encadeamento
*/
public function setName(string $name) : self
{
// Configuração
$this->name = $name;
// Encadeamento
return $this;
}
/**
* Configura o Modificador
*
* @param float $modifier Valor para Configuração
* @return self Próprio Objeto para Encadeamento
*/
public function setModifier(float $modifier) : self
{
// Configuração
$this->modifier = $modifier;
// Encadeamento
return $this;
}
/**
* Configura a Ordem Crescente
*
* @param bool $ascending Valor para Configuração
* @return self Próprio Objeto para Encadeamento
*/
public function setAscending(bool $ascending) : self
{
// Configuração
$this->ascending = $ascending;
// Encadeamento
return $this;
}
/**
* Configura o Limite
*
* @param int $limit Valor para Configuração
* @return self Próprio Objeto para Encadeamento
*/
public function setLimit(int $limit) : self
{
// Configuração
$this->limit = $limit;
// Encadeamento
return $this;
}
// ...
Nota-se, agora, que os tipos básicos podem ser adicionados para garantir a tipagem da informação. Quando um parâmetro pertence a outro tipo, este sofre um type coercing ou coerção de tipo, em tradução livre, pela própria linguagem. Ainda, se o PHP não conseguir efetuar a coerção, uma exceção do tipo TypeError
é apresentada pela linguagem em tempo de execução.
<?php
$element = new Foo\Bar();
$element
->setName(Foo\Bar::class)
->setModifier(1)
->setAscending('0')
->setLimit(3.1415);
var_dump($element);
/*
object(Foo\Bar)#1 (4) {
["name":protected]=>
string(7) "Foo\Bar"
["modifier":protected]=>
float(1)
["ascending":protected]=>
bool(false)
["limit":protected]=>
int(3)
}
*/
O exemplo acima demonstra a utilização de um objeto da classe Foo\Bar
, configurando-o com suporte a declarações de tipo disponíveis. Constata-se que os tipos das variáveis foram convertidos automaticamente pelo PHP, conforme declarações. Ainda, alguns casos demonstram perda de informação, como no método Foo\Bar::setLimit
, onde há a conversão do tipo float
com o valor 3.1415
para o tipo int
com o valor 3
, característica da coerção.
Aumentando a Confiabilidade
Para evitar que seja efetuada a coerção automática de tipos, mesmo em casos que esta ação é possível, pode-se adicionar uma declaração, por arquivo, informando ao PHP que todas as declarações de tipo devem ser estritamente verificadas. Quando o valor informado é incondizente com o tipo configurado, uma exceção do tipo TypeError
é atirada em tempo de execução.
<?php
declare(strict_types=1);
namespace Foo;
/**
* Classe de Exemplo
*/
class Bar
{
// ...
}
Erros Comuns
Um erro comum, durante o início da utilização da declaração de tipos do PHP 7 pelo programador, é utilizar o tipo boolean
. Todavia, o tipo boolean
não é um tipo básico do PHP, mas sim o bool
. Ainda, quando adiciona-se o tipo boolean
como declaração de tipo, o PHP irá interpretar esta informação como o nome de uma classe e não como o tipo básico.
<?php
(function (boolean $checked) {
var_dump($checked);
})(true);
/*
Fatal error: Uncaught TypeError:
Argument 1 passed to {closure}() must be an instance of boolean, boolean given,
called in [...][...] on line 5 and defined in [...][...]:3
Stack trace:
#0 [...][...](5): {closure}(true)
#1 {main}
thrown in [...][...] on line 3
*/
Ainda, outro caso comum é a conversão que não é efetuada entre alguns tipos, como de array
vazio para boolean
false
, contrariando uma conversão explícita, possível na linguagem.
<?php
$container = [];
$converted = (bool) $container;
var_dump($converted);
(function (bool $converted) {
var_dump($converted);
})($container);
/*
bool(false)
Fatal error: Uncaught TypeError:
Argument 1 passed to {closure}() must be of the type boolean, array given,
called in [...][...] on line 10 and defined in [...][...]:8
Stack trace:
#0 [...][...](10): {closure}(Array)
#1 {main}
thrown in [...][...] on line 8
*/
Conclusão
A inclusão dos tipos básicos como declarações de parâmetros em métodos e funções do PHP melhora a legibilidade do código-fonte, uma vez que o programa necessita de menos verificações explícitas em seu conteúdo para garantir a tipagem dos valores. Ainda, este novo recurso torna a execução mais robusta, fazendo com que a própria linguagem efetue as análises necessárias em tempo de execução.
Por outro lado, existem ainda alguns erros comuns que podem causar problemas durante o desenvolvimento, principalmente por programadores iniciantes na linguagem e que desconhecem alguns estranhos comportamentos internos.
Assim, reitero, que o conhecimento profundo de qualquer linguagem é extremamente necessário para programadores que buscam desenvolver códigos mais saudáveis e com menos bugs.