PHP para Iniciantes: Tipos de Dados Primitivos - Declarando Tipagens

Sendo bem sucinto, neste artigo falarei um pouco sobre tipagem declarada e tipagem rígida. Mas antes de começar, eu preciso fazer uma importantíssima pergunta: você está aqui para aprender ou para começar a trabalhar?

Isso é algo que precisa ser discutido, com direito a contar sua história de vida e falar sobre o futuro. Com isso, quero esclarecer que todo(a) desenvolvedor(a) em início de carreira e/ou estudo precisa de um(a) mentor(a). E tem muitos por aí que dão mentoria gratuita! Se você ainda não tem, gostaria de indicar aqui o grupo no Discord feito pelo mestre Rafael Neris: PHP - Career Mentoring.

A ideia de mentorias não é aprender como trabalhar, mas ter bons conselhos sobre o que você deveria focar. Às vezes a gente consegue até arrumar uns freelas pra você! Então só vem que o time é bem unido, hehe. Enfim, vamos voltar ao assunto.

Tipagens declaradas

A partir da versão 7.4 do PHP, recebemos a possibilidade de determinar os tipos de dados que estamos lidando na assinatura dos métodos/funções. Era algo que, antigamente, só poderíamos fazer com comentários, dizendo os tipos esperados. Porém, qualquer um poderia ignorar tudo aquilo e não teria uma tratativa realmente eficaz para isso. Ao declarar o tipo de dado esperado, o interpretador pode tomar a liberdade de converter a tipagem para a esperada ou lançar um erro se o desenvolvedor usar um dado errado.

Infelizmente (ou não), não é possível declarar a tipagem de uma variável. Como sempre reforço: as tipagens nas variáveis tem escopo controlado pelo interpretador. Por tanto, nenhum erro ocorrerá se você fizer isso:

<?php
$var = '1'; // string
$var = $var + 1; // 2, mudando a tipagem de string para int

E já que agora queremos verificar algo além dos valores inseridos nas variáveis, chegou a hora de finalmente sairmos do echo! Vos apresento a função var_dump, que recebe qualquer dado e diz: a informação contida nele (se houver) e sua tipagem. Às vezes vem outras informações, depende do dado em questão. Não vou explicar muito sobre isso, apenas quero te incentivar a usar nos próximos exemplos.

<?php

$var = '1';
var_dump($var);
$var++;
var_dump($var);

Tá, Kiko, mas cadê as declarações?

Calma, chego lá. A questão é que muitas vezes montamos um fluxo pensando num determinado tipo de dados e queremos estar seguros de que ninguém vai colocar um dado diferente lá, certo? Por exemplo, vou inventar um problema em forma de User Story pra gente resolver:

Como pessoa,

Quero ter meu sobrenome separado do nome

Para que as pessoas me chamem apenas pelo nome

Ou seja, se eu tenho armazenado o nome 'Kaique Garcia', eu quero que esqueçam o 'Garcia' e me chamem apenas de 'Kaique'. Esse tipo de dado é notoriamente string, certo? E seu código imperativo seria mais ou menos assim:

<?php // imperativo.php

// dado:
$nomeCompleto = 'Kaique Garcia';

// fragmento em pedaços a partir de espaços em branco até ter dois elementos
// ou seja, se tiver sobrenome, fica [nome, sobrenomeCompleto]. se não, apenas [nome].
$nomes = explode(' ', $nomeCompleto, 2);

// capturo o primeiro índice, que é o primeiro nome
$nome = $nomes[0];

var_dump($nome);

Ok, onde que está o problema nesse código?

Digamos que, por algum motivo, o dado em $nomeCompleto foi adulterado para o número 123123. O que você acha que aconteceria?

. . .

O resultado seria $nome = '123123'!

E isso acontece porque na assinatura função explode diz que tanto o primeiro argumento como o segundo precisam ser string. Como int é um tipo de dado "capaz de se converter" em string, o interpretador faz isso automaticamente.

Uééé! Se a tipagem está declarada, não deveria dar erro?

PHP NÃO É LINGUAGEM DE TIPAGEM FORÇADA

Então mesmo declarando sua tipagem, não vai ser por isso que ele vai forçar todo mundo a respeitar isso. No máximo, sabendo a tipagem esperada, irá barrar se você colocar um dado impossível de se converter (tipo um array).

Então eu preciso verificar o dado primeiro?

Sim e não. Nesse caso em específico, você pode usar a tipagem rígida, que nada mais é que uma declaração ao interpretador de que você deseja respeitar as tipagens declaradas.

Para fazer isso, usaremos a estruturadeclare para alterar uma configuração no interpretador diretamente no código, deixando o nosso exemplo dessa forma:

<?php
declare(strict_types=1);
// a declaração precisa ser a primeira linha do arquivo

// dado:
$nomeCompleto = 'Kaique Garcia';

// fragmento em pedaços a partir de espaços em branco até ter dois elementos
// ou seja, se tiver sobrenome, fica [nome, sobrenomeCompleto]. se não, apenas [nome].
$nomes = explode(' ', $nomeCompleto, 2);

// capturo o primeiro índice, que é o primeiro nome
$nome = $nomes[0];

var_dump($nome);

Se com essa diretriz você alterar o $nomeCompleto para 123123, o PHP dará um erro na hora de chamar a função explode. Isso é muito útil pra quem gosta da estratégia de ser educado pela tipagem forçada, enquanto ao mesmo tempo desfruta das variáveis com tipagens flexíveis.

Vale ressaltar: com a tipagem rígida, ao declarar o tipo de retorno de uma função ou método, você precisará retornar aquele(s) mesmíssimos tipos ou dará o mesmo erro.

Enfim

Tá, Kiko, o que a gente pode colocar como declaração em métodos/funções?

Excelente pergunta. Tem uma tabela bem legal na documentação oficial que lista todas as possibilidades, porém ainda não tem tradução em pt-br no momento em que escrevo (que tal traduzir? ajuda a comunidade aí!). Nesse caso vou mencionar somente as palavras que não representam nenhum tipo de dado primitivo.

OBS.: você vai precisar saber um pouco de Orientação a Objeto para entender com clareza!

Você pode declarar:

  • o nome de uma classe ou interface: um método/função pode receber/retornar uma instância de uma classe ou implementação de uma interface. Quando você declara esse tipo, significa que o objeto inserido/retornado precisa respeitar essa condição;

      <?php
      class Exemplo { }
    
      function exemploRetorno(): Exemplo
      {
          return new Exemplo();
      }
    
      function exemploArgumento(Exemplo $exemplo): void
      {
          var_dump($exemplo);
      }
    
      $exemplo = exemploRetorno();
      var_dump($exemplo);
      exemploArgumento($exemplo);
    
  • self: é similar ao caso acima, exceto que se trata de uma referência à própria classe do objeto instanciado;

      <?php
    
      class Exemplo {
          // self = Exemplo
          public function exemploArgumento(self $exemplo): void
          {
              var_dump($exemplo);
          }
      }
    
      $exemplo = new Exemplo();
      $exemplo->exemploArgumento(new Exemplo());
    
  • parent: também é similar, porém a classe referenciada é a que gerou a classe que está chamando, também conhecida como "classe pai";

      <?php
    
      class Pai { }
      class Filho extends Pai {
          public function exemploArgumento(parent $pai): void
          {
              var_dump($pai);
          }
      }
    
      $filho = new Filho();
      $filho->exemploArgumento(new Pai());
    
  • mixed: quando você quer declarar que aceita todos os tipos de dados (você pode simplesmente não declarar, mas tem isso agora no PHP 8).

Além dessas, tem os tipos que já estudamos (array, callable, bool, float, int, string, iterable e object). Outra coisa legal é que, do PHP 8 pra cima, você pode declarar mais de um tipo de dado ao mesmo tempo, separando pelo caractere |.

Por exemplo:

<?php

function exemploArgumento(string|int $numeral): void
{
    var_dump($numeral);
}

exemploArgumento("blablabla");
exemploArgumento(123);

E os nulos, Kiko?

Se você precisa ressaltar que um dado pode ser nulo, basta acrescentar uma interrogação na frente do tipo declarado. Por exemplo:

<?php

function exemploArgumento(?int $numeralNulo): void
{
    var_dump($numeral);
}

exemploArgumento(null);

E além de tudo isso

Tem tipos específicos que só funcionam em retornos. Um deles você já me viu usar, o void, que representa "vazio" ou "nada". Também tem o static, que é parecido com o self mas é uma referência à classe que a invoca. Essa frase é bem complexa de entender... Se você manja de POO, vale a pena tentar ver o exemplo:

<?php

function exemploVoid(): void
{
    // nada acontece feijoada
}

class Pai {
    public function exemploStatic(): static
    {
         return new static();
    }
}

class Filho extends Pai { }

$filho = new Filho();
var_dump($filho->exemploStatic());

No caso do static, eu acionei o método pela classe Filho, então o retorno esperado era uma instância de Filho, não de Pai. Se você trocar por self, então vai retornar pai em todas as chamadas da hierarquia.

E aí, curtiu? Comenta se tinha alguma coisa que você ainda não sabia sobre o que foi apresentado nesse artigo e marca um amigo pra ler mais tarde!

Inté!