PHP para Iniciantes: Funções - Retorno

Continuando os artigos sobre construção de funções, chegamos a um tópico que também merece atenção e carinho: retorno. Eu falei em alguns artigos anteriores que toda função retorna alguma coisa, lembra?

Eis que, logo em breve, isso não será mais verdade. As coisas evoluem rápido, não é mesmo? É que recentemente lançaram nas versões candidatas de release do PHP 8.1 um tipo de retorno chamado never, que indica que a função nunca poderá ter retorno. Eu fiz um teste para validar se não era apenas um termo da boca pra fora, retornando null por baixo dos panos. Mas se você tentar atribuir o resultado de uma função que retorna never, o PHP encerrará a requisição com um FatalError. É sério, pode abrir o link que coloquei aí em cima para conferir.

Então esquece aquela ideia de que funções sempre retornam alguma coisa, pois agora temos essa pequena exceção.

Ainda assim, se o caso da sua função não for esse, é muito importante que você descreva na assinatura da função qual(is) tipo(s) de dados ela retorna para quem as invoca. Isso ajuda tanto ao desenvolvedor que irá consumí-la a entender por intuição o que vai acontecer, como também ajuda ao outro que fará ajustes posteriores nela a não quebrar inserindo um novo tipo de dados como retorno.

Por exemplo, suponha que nós temos uma classe chamada Ponto, que representa um ponto em um plano bidimensional, e outra chamada AreaQuadrilateral, que representa a área conectada por quatro instâncias da classe Ponto. Com essa área, acionaremos a função aspirar, que supostamente irá acionar uma rotina interna do robô aspirador para que limpe aquela região. Isso nos dá a seguinte chamada:

// PHP 8

$area = new AreaQuadrilateral(
    x0y0: new Ponto(x: 0, y: 0),
    x0y1: new Ponto(x: 0, y: 230),
    x1y0: new Ponto(x: 180, y: 0),
    x1y1: new Ponto(x: 180, y: 230),
);

// bastaria informar x0, x1, y0, y1 e deixar a classe se virar, né?
// mas assim fica mais legível e compreensível pra galera, eu acho
// e ainda seriam quatro argumentos

aspirar($area);

Note que não há menção ao robô no código porque, teoricamente, é ele que roda esse script, beleza? Enfim...

Você consegue dizer o que a função aspirar retorna?

. . .

Se você respondeu qualquer coisa, deixa eu te cortar antes que a coisa dure mais tempo: não, não dá pra dizer. Afinal, aqui é apenas uma chamada, não a assinatura dela. Se você tentar desenvolver advinhando as coisas sem checar o que está inserido, de fato, algo de ruim pode acontecer, rs.

Tá, Kiko, mas você nem criou a função... Que sermão é esse, cara?

É só uma dica, não leve pro pessoal. O fato é que, se você abrir a implementação da função e o cabeçalho estiver como abaixo e a função tiver 300 return e 5000 linhas...

function aspirar(AreaQuadrilateral $area) {
    // 5k linhas
    // 300 returns ao longo do código
}

Você vai xingar muito no twitter.

Mr. Bean reagindo incrédulo

Pior ainda se tiver um mixed, tipo de retorno inserido no PHP 8 para indicar que pode retornar qualquer coisa. É o equivalente a não colocar nada, rs. Se tiver 300 returns podendo retornar qualquer coisa, sinceramente, meus pêsames.

Então qual seria o jeito "certo", Kiko?

Logo após fechar o parênteses, aquela seção dos argumentos, você pode indicar o tipo de retorno colocando o caractere de dois pontos (:) e informando o(s) possível(is) tipo(s) retornado(s). No caso da função aspirar, faz sentido retornar bool, concorda? true se conseguiu iniciar o comando de aspirar ou false se deu algum problema na máquina. Se todo o processo for síncrono, pode ser true se conseguiu aspirar toda a área ou false se algo deu errado no meio do caminho - como aquela sua roupa íntima largada no chão enquanto você estava recebendo visita.

Ops, se isso realmente aconteceu com você, não se exponha. Eu inventei, eu juro.

O ponto é: sendo um retorno booleano, a assinatura da função ficaria assim:

function aspirar(AreaQuadrilateral $area): bool
{
    // ...
}

Note que eu coloquei a chave na linha abaixo... Porque acho muito feio escrever o retorno e abrir a chave ao lado, rs. Mas você pode colocar onde achar melhor - DESDE QUE SEJA O PADRÃO DO PROJETO. Sempre busquem manter um padrão impecável, por favor.

Sobre o PHP 8, lembra que no artigo anterior mencionei que você poderia listar várias possibilidades de tipos para um único argumento? Você também pode fazer isso na assinatura de retorno usando a mesmíssima sintaxe: function aspirar(...): bool|int|string|QualquerCoisa. Para engajar geral a colocar realmente todas as possibilidades e jamais deixar a seção em branco.

E em algum momento no futuro, creio eu, teremos o famoso Generics, que provavelmente será a feature favorita de todo desenvolvedor de libs e extensões em PHP. Por quê? Porque Generics é sobre dar possibilidade de criar cenários onde argumentos e retornos dependem de um dado inserido pelo desenvolvedor. Por exemplo, ao invés de falar que a entrada da função é mixed e, por isso, o retorno também é, você poderá dizer que o tipo de retorno é o mesmo do tipo inserido no argumento, criando ali, em tempo real, um tipo genérico que corresponderá ao tipo retornado.

Por exemplo: function traduzir(<Midia> $midia): Midia (essa sintaxe ainda não existe, tô só exemplificando mesmo). Nesse cenário, eu estou te dizendo que o resultado da tradução sairá no mesmo tipo do argumento $midia. Assim, eu posso traduzir um áudio, retornando outro áudio, ou traduzir um texto, retornando outro texto, etc.

E quando você estiver desenvolvendo, sua IDE poderá dizer qual será o retorno baseado na expressão que você inseriu no argumento, garantindo com mais precisão que você está escrevendo o código certo. Não é legal?

Tá, Kiko, aprendi como assinar o retorno da função, mas como faz o retorno acontecer?

Nesse caso, usamos a estrutura de controle return, lembra dela? Nas funções, ela serve para encerrar a interpretação da função e forçar o interpretador a voltar aonde estava antes de entrar na função com a informação que você desejou retornar.

Há muitas possibilidades do que dá pra fazer com o que uma função retorna. Por exemplo, se a função retorna um array e você sabe, com certeza, a quantidade de elementos que vem nele e a ordem de cada dado, você pode distribuir os elementos em variáveis em uma única linha usando a estrutura list ou a notação curta de construção de array:

<?php

function retornaArray(): array
{
    return ['Kaique', 'Garcia', 'Kiko'];
}

list($nome, $sobrenome, $apelido) = retornaArray();
var_dump($nome, $sobrenome, $apelido);

// equivalente
[$nome, $sobrenome, $apelido] = retornaArray();
var_dump($nome, $sobrenome, $apelido);

Porém, falar sobre isso não tem muito a ver com o retorno das funções em si, é mais sobre o que você pode fazer quando aciona uma. Por isso, não vou me aprofundar muito nesses casos, beleza?

Voltando ao nosso exemplo, só haverá dois retornos possíveis: return true ou return false. Ah, claro que você pode retornar uma expressão que resulta em um booleano também, mas no final das contas, o interpretador só vai enxergar esses cenários que citei. Caso você retorne qualquer outra coisa diferente de bool, ou não retorne nada, o interpretador poderá lançar um erro.

Como assim "poderá", Kiko?

Porque PHP não é linguagem de tipagem forçada. Se você não estiver usando a tipagem rígida (mencionada no artigo sobre declaração de tipagens), o PHP tentará converter o dado inserido para bool. Nesse caso, é bem provável que converta mesmo.

Mas beleza, agora você já sabe onde colocar o(s) tipo(s) e como escrever o retorno em si, mas você já sabe quais tipos pode colocar na assinatura? Você deveria saber, pois eu comentei no artigo sobre declaração de tipagens, onde mencionei um link para uma tabela atualizada com os tipos aceitos na documentação oficial do PHP.

Essa tabela vale tanto para argumentos quanto para retornos. Lá também tem os tipos exclusivos para retornos, que são os casos do void e never. Ignore o static, porque isso é um retorno de métodos, que só vamos ver lá pra frente, beleza?

. . .

É isso, Kiko? Acabou?

Acabou... Pode ir...

Opa, peraí

Na verdade, ainda temos um tópico chato para falar. Usei a mesma tática do artigo anterior: deixei pro final porque não gosto, hahahaha. É sobre o retorno de referências.

Você pode retornar a referência de alguma variável ao escrever o operador de referência (&) antes do nome da função. Isso indica que ela irá retornar isso. Quando você acionar a função, você também precisará inserir esse símbolo (&) depois do operador de atribuição. Por exemplo:

<?php

function &contar(): int // & antes do nome "contar"
{
    static $contador = 0;
    $contador++;
    return $contador;
}

$contador =& contar(); // & depois do operador de atribuição "="

contar();
contar();
contar();

var_dump($contador); // int(4)

Eu não consigo imaginar isso como real necessidade, mas deve ter alguma coisa sim por aí e que não seja loucura... Deve ter...

Deve ter... Deve ter...

E agora sim! Por hoje é só! Curtiu? Comenta e compartilha! Olha, eu tomei um susto danado no BOOM que deram lá no Twitter de ontem pra hoje. Vocês quebraram meu recorde de like e RT de uma vez só, hahaha. Fico muito animado vendo essa reação da galera e o total interesse em conhecer mais as entranhas do PHP. Dito isso, vou continuar dando o meu máximo para entreter vocês com mais informações relevantes, fechado?

E sério, alguém pode me falar o que rolou? Fiquei o tempo todo esperando alguém anunciar uma pegadinha, HAHAHAHA. Vocês são demais! Obrigado pela visita, voltem sempre!

Inté!!