PHP Tipos e Retorno
Uma função é um combinado: "me dê isto e eu devolvo aquilo". Até agora esse combinado estava só na cabeça de quem escreveu a função. Nesta aula você aprende a escrevê-lo no código: declarar de que tipo é cada parâmetro (um número inteiro? um texto? um array?) e o que a função devolve com o return. Com os tipos declarados, o PHP passa a conferir o combinado e a avisar na hora em que alguém o quebra, em vez de deixar o erro aparecer lá na frente, com um resultado estranho.
- Como o
returndevolve um valor (e encerra a função na hora). - A diferença entre mostrar (
echo) e devolver (return). - Como declarar o tipo dos parâmetros:
function f(int $x, string $nome, array $lista). - Como declarar o tipo do retorno:
: int,: string,: ?string,: void,: mixed. - O que o PHP faz quando o tipo não bate: converter em silêncio (modo normal) ou dar
TypeError(declare(strict_types=1)). - O que é o tipo
never(PHP 8.1).
Antes, é bom ter feito Funções, Parâmetros e Tipos de Dados.
O return: devolver um resultado
Uma função pode trabalhar e entregar um resultado a quem a chamou, como a calculadora que mostra o total. A palavra return ("retornar", "devolver") faz isso.
<?php
function situacao($nota)
{
if ($nota >= 7) {
return "Aprovado";
}
if ($nota >= 5) {
return "Recuperação";
}
return "Reprovado";
echo "Esta linha nunca roda\n";
}
echo situacao(9.5), "\n";
echo situacao(6.0), "\n";
echo situacao(3.0), "\n";
$ php t-early.php Aprovado Recuperação Reprovado « e o "Esta linha nunca roda" de fato não apareceu
Duas coisas acontecem num return:
- O valor à direita vira o resultado da chamada: no lugar de
situacao(9.5), fica"Aprovado". - A função termina ali mesmo. Nada depois do
returnroda. Por isso o primeiroifnão precisa deelse: se a nota for 9.5, a função já acabou.
Esse estilo, com vários return que saem cedo, é muito usado e se chama early return ("retorno antecipado"). Ele evita ifs dentro de ifs.
Sem return, a função devolve null
<?php
function dobro($n)
{
$resultado = $n * 2; // calculou… e não devolveu
}
var_dump(dobro(4));
$ php t-null.php NULL
Este é um erro clássico: a conta foi feita, mas ficou presa dentro da função. Faltou return $resultado;. Mais abaixo, você vai ver que declarar o tipo do retorno faz o PHP denunciar esse erro.
Mostrar (echo) não é devolver (return)
Para quem está começando, as duas coisas parecem iguais, porque nos dois casos "aparece o número". Não são:
<?php
function dobroEcho(int $n): void
{
echo $n * 2;
}
function dobroReturn(int $n): int
{
return $n * 2;
}
dobroEcho(5);
echo "\n";
$x = dobroReturn(5);
echo $x + 1, "\n";
$y = dobroEcho(5);
echo "\n";
var_dump($y);
$ php t-echoreturn.php 10 « dobroEcho mostrou o 10 na tela 11 « dobroReturn devolveu 10; deu para somar 1 10 « dobroEcho mostrou de novo… NULL « …mas não devolveu nada: $y ficou null
echo dentro da função | return | |
|---|---|---|
| Para onde vai o valor | Direto para a tela (ou a página) | Para quem chamou |
| Dá para guardar numa variável? | Não | Sim |
Dá para usar numa conta, num if? | Não | Sim |
Regra prática: funções que calculam devolvem com return; quem chama decide se mostra, guarda ou usa em outra conta. Isso deixa a função reaproveitável: formatarPreco serve para a tela, para um e-mail, para um PDF…
Tipos nos parâmetros
Sem tipos, uma função aceita qualquer coisa, e o problema só aparece lá dentro, às vezes com uma mensagem que não ajuda:
<?php
function precoFinal($preco, $quantidade)
{
return $preco * $quantidade;
}
echo precoFinal(18.9, 2), "\n";
echo precoFinal("18.9", "2"), "\n";
echo precoFinal("dezoito", 2), "\n";
$ php t-sem.php
37.8
37.8
Fatal error: Uncaught TypeError: Unsupported operand types: string * int in /home/ana/site/t-sem.php:4
Stack trace:
#0 /home/ana/site/t-sem.php(8): precoFinal('dezoito', 2)
#1 {main}
thrown in /home/ana/site/t-sem.php on line 4
O erro aponta a linha 4 (a conta, dentro da função), e a mensagem fala de "operandos". A culpa, na verdade, é de quem chamou na linha 8. Agora com tipos:
<?php
function precoFinal(float $preco, int $quantidade)
{
return $preco * $quantidade;
}
echo precoFinal(18.9, 2), "\n";
echo precoFinal("18.9", "2"), "\n";
echo precoFinal(18, 2), "\n";
echo precoFinal("dezoito", 2), "\n";
- float $preco
- O tipo vem antes do nome do parâmetro: "
$precodeve ser um número com decimais". - int $quantidade
- "
$quantidadedeve ser um número inteiro".
$ php t-param.php
37.8
37.8 « "18.9" e "2" são textos numéricos: o PHP converteu
36 « o int 18 foi aceito como float
Fatal error: Uncaught TypeError: precoFinal(): Argument #1 ($preco) must be of type float, string given, called in /home/ana/site/t-param.php on line 9 and defined in /home/ana/site/t-param.php:2
Stack trace:
#0 /home/ana/site/t-param.php(9): precoFinal('dezoito', 2)
#1 {main}
thrown in /home/ana/site/t-param.php on line 2
A mensagem agora diz exatamente o que houve: "o argumento nº 1 ($preco) deve ser do tipo float, foi passada uma string, chamada na linha 9". O erro aparece na porta de entrada da função, e aponta o culpado.
Os tipos que você vai usar
| Tipo | Aceita | Exemplo |
|---|---|---|
int | número inteiro | int $quantidade |
float | número com decimais (aceita int também) | float $preco |
string | texto | string $nome |
bool | true ou false | bool $ativo |
array | qualquer array | array $notas |
?string | string ou null (o ? vale para qualquer tipo: ?int, ?array…) | ?string $email |
int|float | um ou outro (tipos união, PHP 8.0+) | int|float $valor |
mixed | qualquer coisa (é o mesmo que não pôr tipo, mas deixa claro que foi de propósito) | mixed $valor |
Também dá para usar nomes de classes como tipo (Produto $p), assunto do Nível 3. Os tipos mais elaborados estão em Tipos Avançados.
Modo normal × declare(strict_types=1)
Você deve ter reparado: com float $preco, a string "18.9" foi aceita. Isso é o modo normal do PHP, chamado de modo coercivo ("que força"): se o valor puder ser convertido para o tipo pedido sem perder informação importante, o PHP converte em silêncio. Veja até onde ele vai:
<?php
function dobro(int $n)
{
return $n * 2;
}
var_dump(dobro(5));
var_dump(dobro("5"));
var_dump(dobro(true));
var_dump(dobro(5.0));
var_dump(dobro(5.5));
var_dump(dobro("5 reais"));
$ php t-coerc.php
int(10)
int(10) « "5" virou 5
int(2) « true virou 1!
int(10) « 5.0 virou 5
Deprecated: Implicit conversion from float 5.5 to int loses precision in /home/ana/site/t-coerc.php on line 2
int(10) « 5.5 virou 5, com um aviso: o ,5 se perdeu
Fatal error: Uncaught TypeError: dobro(): Argument #1 ($n) must be of type int, string given, called in /home/ana/site/t-coerc.php on line 11 and defined in /home/ana/site/t-coerc.php:2
Stack trace:
#0 /home/ana/site/t-coerc.php(11): dobro('5 reais')
#1 {main}
thrown in /home/ana/site/t-coerc.php on line 2
Algumas dessas conversões são práticas (dados de formulário chegam como texto, aula $_GET e $_POST); outras escondem erros (true virando 1). Se você prefere que o PHP não converta nada, ligue o modo estrito com uma linha no topo do arquivo:
<?php
declare(strict_types=1);
function dobro(int $n)
{
return $n * 2;
}
var_dump(dobro(5));
var_dump(dobro("5"));
- declare(…)
- "Declarar": uma instrução para o próprio PHP sobre como tratar o arquivo.
- strict_types=1
- "Tipos estritos: ligado". Sem espaços em volta do
=por costume; o valor é sempre1.
$ php t-strict.php
int(10)
Fatal error: Uncaught TypeError: dobro(): Argument #1 ($n) must be of type int, string given, called in /home/ana/site/t-strict.php on line 9 and defined in /home/ana/site/t-strict.php:4
Stack trace:
#0 /home/ana/site/t-strict.php(9): dobro('5')
#1 {main}
thrown in /home/ana/site/t-strict.php on line 4
Com o modo estrito, "5" não é 5: TypeError. A única conversão que continua permitida é de int para float (um inteiro "cabe" num float sem perder nada). E o modo vale também para as funções nativas chamadas no arquivo:
<?php
declare(strict_types=1);
function metade(float $n)
{
return $n / 2;
}
var_dump(metade(5));
var_dump(strlen("Ana"));
var_dump(strlen(123));
$ php t-strict-float.php
float(2.5) « int → float continua aceito
int(3)
Fatal error: Uncaught TypeError: strlen(): Argument #1 ($string) must be of type string, int given in /home/ana/site/t-strict-float.php:10
Stack trace:
#0 {main}
thrown in /home/ana/site/t-strict-float.php on line 10
Chamada para f(int $n) | Modo normal | Com strict_types=1 |
|---|---|---|
f(5) | 5 | 5 |
f("5") | convertido para 5 | TypeError |
f(5.0) | convertido para 5 | TypeError |
f(5.5) | 5, com Deprecated | TypeError |
f(true) | convertido para 1 | TypeError |
f("5 reais") | TypeError | TypeError |
para g(float $x): g(5) | 5.0 | 5.0 (a exceção permitida) |
As regras do declare
- Tem de ser a primeira instrução do arquivo, logo depois do
<?php. Senão:
<?php echo "Começando\n"; declare(strict_types=1);
$ php t-strict-lugar.php
Fatal error: strict_types declaration must be the very first statement in the script in /home/ana/site/t-strict-lugar.php on line 3
Stack trace:
#0 {main}
- Vale só para o arquivo onde está escrito, e conta o arquivo de onde a função é chamada, não onde ela foi definida. Num projeto com vários arquivos (aula include e require), coloque-o em todos.
Projetos modernos usam declare(strict_types=1); em todos os arquivos: erros de tipo aparecem cedo, na linha certa, em vez de virar um cálculo errado. Mas atenção ao que vem de fora: $_GET, $_POST, arquivos e explode sempre dão strings. No modo estrito, converta você mesmo ((int) $_GET["quantidade"]) ou valide antes (aulas Conversão de Tipos e Validar Dados).
Rode como está e leia o TypeError. Depois apague a linha do declare e rode de novo: o que muda? Experimente também dobro(5.5) e dobro(true) nos dois modos.
<?php
declare(strict_types=1);
function dobro(int $n): int
{
return $n * 2;
}
var_dump(dobro(5));
var_dump(dobro("5"));
Tipo do retorno
Depois dos parênteses, dois-pontos e o tipo do que a função devolve:
<?php
function dobro(int $n): int
{
return $n * 2;
}
function media(array $notas): float
{
return array_sum($notas) / count($notas);
}
function saudacao(string $nome): string
{
return "Olá, $nome!";
}
function eMaiorDeIdade(int $idade): bool
{
return $idade >= 18;
}
var_dump(dobro(21));
var_dump(media([8, 9, 10]));
var_dump(saudacao("Ana"));
var_dump(eMaiorDeIdade(17));
- function media(array $notas): float
- Lê-se: "
mediarecebe um array e devolve um float". Só de ler a primeira linha (a assinatura da função), você sabe como usá-la.
$ php t-retorno.php int(42) float(9) « 27 / 3 dá o int 9; o tipo de retorno float o converteu para 9.0 string(10) "Olá, Ana!" bool(false)
O PHP confere o valor do return do mesmo jeito que confere os parâmetros: no modo normal converte quando pode; no estrito, só aceita int → float. Quando não dá:
<?php
function codigo(string $produto): int
{
return "sem código";
}
var_dump(codigo("café"));
$ php t-retorno-errado2.php
Fatal error: Uncaught TypeError: codigo(): Return value must be of type int, string returned in /home/ana/site/t-retorno-errado2.php:4
Stack trace:
#0 /home/ana/site/t-retorno-errado2.php(6): codigo('caf\xC3\xA9')
#1 {main}
thrown in /home/ana/site/t-retorno-errado2.php on line 4
(No stack trace, o é de "café" aparece como os seus dois bytes, \xC3\xA9: o PHP mostra os bytes acentuados assim nos rastros de erro.)
E aquele erro clássico de esquecer o return? Com o tipo declarado, ele não passa mais em silêncio:
<?php
function dobro(int $n): int
{
$resultado = $n * 2;
}
var_dump(dobro(4));
$ php t-semreturn.php
Fatal error: Uncaught TypeError: dobro(): Return value must be of type int, none returned in /home/ana/site/t-semreturn.php:5
Stack trace:
#0 /home/ana/site/t-semreturn.php(6): dobro(4)
#1 {main}
thrown in /home/ana/site/t-semreturn.php on line 5
"Deve devolver int, nada foi devolvido" (none returned). Compare com o NULL silencioso do começo da aula.
O modo estrito também vale no retorno
<?php
declare(strict_types=1);
function media(array $notas): int
{
return array_sum($notas) / count($notas);
}
var_dump(media([8, 9, 10]));
var_dump(media([7, 8]));
$ php t-strict2.php
int(9) « 27 / 3 = 9, um int: passou
Fatal error: Uncaught TypeError: media(): Return value must be of type int, float returned in /home/ana/site/t-strict2.php:6
Stack trace:
#0 /home/ana/site/t-strict2.php(9): media(Array)
#1 {main}
thrown in /home/ana/site/t-strict2.php on line 6
15 / 2 = 7.5, um float. Sem o modo estrito, o mesmo código devolveria 7 com um Deprecated: Implicit conversion from float 7.5 to int loses precision, e a média estaria errada. O problema real aqui é o tipo: média deveria ser float.
?string: pode devolver "nada"
Quando uma função procura algo que pode não existir, é comum devolver null nesse caso. O ? antes do tipo diz "este tipo ou null":
<?php
$emails = ["Ana" => "ana@exemplo.com", "Bruno" => "bruno@exemplo.com"];
function buscarEmail(array $emails, string $nome): ?string
{
return $emails[$nome] ?? null;
}
var_dump(buscarEmail($emails, "Ana"));
var_dump(buscarEmail($emails, "Zeca"));
echo buscarEmail($emails, "Zeca") ?? "sem e-mail", "\n";
$ php t-nullable.php string(15) "ana@exemplo.com" NULL sem e-mail
Repare que o array $emails foi passado como parâmetro: uma função não enxerga as variáveis de fora dela (aula Escopo). Nos parâmetros, o ? funciona igual, e combina bem com o padrão null da aula Parâmetros:
<?php
function saudacao(?string $nome = null): string
{
return "Olá, " . ($nome ?? "visitante") . "!";
}
echo saudacao("Ana"), "\n";
echo saudacao(), "\n";
echo saudacao(null), "\n";
$ php t-nullparam.php Olá, Ana! Olá, visitante! Olá, visitante!
Sem o ?, um parâmetro string recusa null (must be of type string, null given). E escrever string $nome = null, sem o ?, ainda funciona mas é desaprovado desde o PHP 8.4 (veja Quando dá errado).
void: não devolve nada
Para funções que só fazem algo (mostrar, gravar), sem resultado, o tipo de retorno é void ("vazio"):
<?php
function linha(int $tamanho): void
{
echo str_repeat("-", $tamanho), "\n";
}
linha(10);
var_dump(linha(3));
$ php t-void.php ---------- --- NULL « quem tenta usar o "resultado" de uma função void recebe null
Numa função void, return; sozinho (para sair mais cedo) é permitido; return com valor, não:
<?php
function linha(int $tamanho): void
{
return str_repeat("-", $tamanho);
}
$ php t-void-erro.php
Fatal error: A void function must not return a value in /home/ana/site/t-void-erro.php on line 4
Stack trace:
#0 {main}
Esse erro aparece antes de qualquer coisa rodar, só de o PHP ler o arquivo.
mixed e tipos união
<?php
function descrever(mixed $valor): string
{
return get_debug_type($valor);
}
echo descrever(10), " ", descrever("Ana"), " ", descrever([1, 2]), " ", descrever(null), " ", descrever(2.5), "\n";
function dobro(int|float $n): int|float
{
return $n * 2;
}
var_dump(dobro(4), dobro(1.5));
$ php t-mixed.php int string array null float « get_debug_type diz o tipo de qualquer valor int(8) float(3)
mixed aceita tudo (inclusive null); use quando a função realmente lida com qualquer tipo. int|float aceita um ou outro. Prefira sempre o tipo mais preciso que fizer sentido.
array para devolver vários valores
Uma função devolve um valor. Para devolver dois ou mais, devolva um array, e desmonte-o com a desestruturação:
<?php
function minMax(array $numeros): array
{
return [min($numeros), max($numeros)];
}
[$menor, $maior] = minMax([7, 3, 9, 5]);
echo "Menor: $menor, maior: $maior\n";
$ php t-array.php Menor: 3, maior: 9
never: a função nunca volta PHP 8.1+
Existe ainda um tipo de retorno para funções que nunca terminam normalmente: elas sempre encerram o programa (exit) ou lançam uma exceção. É o caso de uma função redirecionar(), que manda o navegador para outra página e para tudo. Você vai encontrar never nas aulas header e Redirecionamento e Exceções; por ora, basta saber que ele existe.
Quando dá errado
TypeError: …(): Argument #1 ($n) must be of type int, string given
O que aconteceu: um argumento não é do tipo declarado (e não pôde ser convertido, ou o modo estrito está ligado). A mensagem traz o número e o nome do parâmetro, o tipo esperado, o tipo recebido e a linha da chamada (called in … on line …).
Como resolver: vá à linha da chamada. Se o valor veio de um formulário ou de um explode, converta: dobro((int) $texto). Se o tipo declarado é que está errado (uma média declarada como int), corrija a declaração.
TypeError: …(): Return value must be of type int, none returned
O que aconteceu: a função chegou ao fim sem passar por nenhum return. Às vezes é só um caminho esquecido: um if que tem return e um caso que não tem.
Como resolver: confira que todo caminho termina num return com valor do tipo certo. Se a função não deve devolver nada, o tipo é void.
Deprecated: Implicitly marking parameter $nome as nullable is deprecated
<?php
function saudacao(string $nome = null): string
{
return "Olá, " . ($nome ?? "visitante") . "!";
}
echo saudacao(), "\n";
$ php t-implicit.php Deprecated: saudacao(): Implicitly marking parameter $nome as nullable is deprecated, the explicit nullable type must be used instead in /home/ana/site/t-implicit.php on line 2 Olá, visitante!
O que aconteceu: o tipo é string, mas o padrão é null. Antigamente, isso tornava o parâmetro "string ou null" sem avisar. Desde o PHP 8.4, o PHP pede que você diga isso explicitamente.
Como resolver: ponha o ?: ?string $nome = null.
strict_types declaration must be the very first statement in the script
O que aconteceu: havia algo antes do declare (um echo, um require, até HTML ou uma linha em branco antes do <?php). Veja t-strict-lugar.php, acima.
Como resolver: o arquivo deve começar exatamente com <?php e, logo depois, declare(strict_types=1);. Comentários antes do declare são permitidos.
A void function must not return a value
O que aconteceu: uma função declarada : void tem um return com valor.
Como resolver: se a função deve devolver algo, troque void pelo tipo certo; se não deve, use só return; (ou nada).
Referência rápida
| Você quer… | Código |
|---|---|
| Devolver um valor | return $x; |
| Sair da função mais cedo | if (…) { return …; } |
| Tipo no parâmetro | function f(int $n, string $s, array $a, bool $b, float $x) |
| Parâmetro que aceita null | function f(?string $s = null) |
| Tipo do retorno | function f(): int, : string, : array, : bool, : float |
| Retorno que pode ser null | function f(): ?string |
| Não devolve nada | function f(): void |
| Qualquer tipo / um ou outro | mixed / int|float |
| Nunca volta (sempre sai ou lança erro) | function f(): never (8.1) |
| Desligar conversões automáticas | declare(strict_types=1); na 1ª linha |
| Saber o tipo de um valor | get_debug_type($x) |
Missão final
Missão: deixe a função "à prova de erros". (1) Ligue o modo estrito na primeira linha depois do <?php. (2) Declare o parâmetro como float $nota e o retorno como string. (3) Rode: uma das chamadas vai dar TypeError. Leia a mensagem e corrija a chamada. No fim, as três situações devem aparecer, sem nenhum erro.
<?php
function situacao($nota)
{
if ($nota >= 7) {
return "Aprovado";
}
if ($nota >= 5) {
return "Recuperação";
}
return "Reprovado";
}
echo situacao(9.5), "\n";
echo situacao("6.0"), "\n";
echo situacao(3), "\n";
<?php
declare(strict_types=1);
function situacao(float $nota): string
{
if ($nota >= 7) {
return "Aprovado";
}
if ($nota >= 5) {
return "Recuperação";
}
return "Reprovado";
}
echo situacao(9.5), "\n";
echo situacao(6.0), "\n";
echo situacao(3), "\n";
No passo 3, o PHP do laboratório mostra:
$ php situacao.php
Aprovado
Fatal error: Uncaught TypeError: situacao(): Argument #1 ($nota) must be of type float, string given, called in /home/ana/site/situacao.php on line 16 and defined in /home/ana/site/situacao.php:4
Stack trace:
#0 /home/ana/site/situacao.php(16): situacao('6.0')
#1 {main}
thrown in /home/ana/site/situacao.php on line 4
A chamada situacao(3) não dá erro: no modo estrito, int → float continua permitido.
Exercícios
1. O tipo do retorno
Complete para dizer que a função devolve um texto:
2. Pode ser nulo
Complete o tipo do retorno: a função devolve um int ou null:
3. Modo estrito
Complete a primeira instrução do arquivo:
4. Sem strict_types, o que acontece com dobro("8"), sendo function dobro(int $n): int?
5. Com declare(strict_types=1);, qual chamada a function metade(float $x) é aceita?
6. Uma função calcula um valor, mas var_dump(f()) mostra NULL. A causa mais provável?
Resumo
returndevolve um valor e encerra a função. Semreturn, o resultado énull. Devolver não é mostrar:echovai para a tela,returnvai para quem chamou.- Tipos nos parâmetros (
int $n) e no retorno (): int) documentam o combinado e fazem o PHP conferir. ?tipoaceitanull;voidnão devolve nada;mixedaceita tudo;int|floataceita um ou outro;nevernunca volta.- Modo normal: o PHP converte o que der (
"5"→ 5,true→ 1). Comdeclare(strict_types=1);(1ª instrução do arquivo), só int → float; o resto viraTypeError. - As mensagens de
TypeErrordizem o parâmetro, o tipo esperado, o recebido e a linha da chamada.