Variáveis, Tipos de Dados e Operadores

Variáveis, Tipos de Dados e Operadores

Toda variável em PHP guarda um valor de algum tipo, e a linguagem converte entre eles sozinha com mais liberdade do que seria prudente. Os oito tipos, as regras de conversão que decidem quando 5 vira "5", os operadores da linguagem e as armadilhas de comparação que sobrevivem a cada versão nova.
PHP

18 min de leitura

Todo programa de computador, em sua essência, faz três coisas: recebe dados, processa dados e produz resultados. Para isso, precisa de um lugar para guardar informações temporariamente durante a execução — e esse lugar são as variáveis. Neste artigo vamos entender como o PHP lida com variáveis, quais tipos de dados existem, como convertê-los e como operar sobre eles com os operadores da linguagem.

Este é um dos artigos mais densos do Módulo 1, mas também um dos mais importantes. Tudo que vem depois — estruturas de controle, funções, orientação a objetos — depende de você dominar bem este conteúdo.

Variáveis

No PHP, toda variável começa com o símbolo $ seguido do nome. O nome deve começar com uma letra ou underscore, e pode conter letras, números e underscores. O PHP é case-sensitive: $nome e $Nome são variáveis diferentes.

<?php

// Declaração e atribuição são feitas ao mesmo tempo
// Não existe "declarar sem valor" em PHP como em outras linguagens
$nome     = "Ricardo";
$idade    = 35;
$altura   = 1.78;
$ativo    = true;
$endereco = null; // null significa ausência intencional de valor

// Variáveis podem ser reatribuídas a qualquer momento
// e até mudar de tipo — PHP permite isso (tipagem dinâmica)
$valor = 10;      // int
$valor = "dez";   // agora é string — PHP aceita, mas evite isso
$valor = 10.5;    // agora é float

// Exibindo valores
echo $nome;   // Ricardo
echo "\n";
echo $idade;  // 35

A convenção de nomenclatura mais usada no PHP é o camelCase para variáveis e funções: $nomeDoUsuario, $totalDeItens, $dataDeNascimento. Para constantes, usa-se UPPER_SNAKE_CASE. Siga essas convenções — elas tornam o código previsível e legível.

Tipos de dados

O PHP possui oito tipos primitivos divididos em três grupos:

Escalares — armazenam um único valor:

  • string — texto
  • int — número inteiro
  • float — número decimal
  • bool — verdadeiro ou falso

Compostos — armazenam múltiplos valores:

  • array — lista ou mapa de valores
  • object — instância de uma classe

Especiais:

  • null — ausência de valor
  • resource — referência a recursos externos (arquivos, conexões de banco)
<?php

// STRING — sequência de caracteres
// Aspas duplas: interpola variáveis
// Aspas simples: tudo literal, mais performático para strings sem variáveis
$simples  = 'Olá, mundo!';             // aspas simples
$duplas   = "Olá, $simples";           // interpola — resultado: "Olá, Olá, mundo!"
$heredoc  = <<<EOT
    Texto com múltiplas linhas.
    Suporta interpolação de $simples.
    EOT;                               // heredoc — para textos longos

// INT — inteiro, positivo ou negativo, sem limite fixo (depende do sistema)
$positivo  =  42;
$negativo  = -17;
$hexadec   = 0x1A;  // hexadecimal — equivale a 26
$binario   = 0b101; // binário — equivale a 5
$octal     = 0777;  // octal — equivale a 511
$separador = 1_000_000; // PHP 7.4+: underscore como separador visual

// FLOAT — número com ponto decimal ou notação científica
$preco     = 19.99;
$cientifico = 1.5e3;  // 1500.0
$negativo_f = -0.001;

// BOOL — true ou false (case-insensitive: TRUE, True, true são iguais)
$ativo    = true;
$inativo  = false;

// NULL — ausência de valor
$vazio = null;

// Verificando tipos com gettype() e funções is_*
echo gettype($positivo);  // integer
echo gettype($preco);     // double (nome interno do float)
echo gettype($simples);   // string
echo gettype($ativo);     // boolean
echo gettype($vazio);     // NULL

var_dump(is_int($positivo));    // bool(true)
var_dump(is_string($simples));  // bool(true)
var_dump(is_null($vazio));      // bool(true)

Strings em detalhes

Strings merecem atenção especial pois são o tipo mais usado no desenvolvimento web.

<?php

$nome  = "Ana";
$sobrenome = "Silva";

// Concatenação com ponto (.)
$nomeCompleto = $nome . " " . $sobrenome;
echo $nomeCompleto; // Ana Silva

// Interpolação dentro de aspas duplas
echo "Olá, $nome!";          // Olá, Ana!
echo "Olá, {$nome}!";        // Olá, Ana! — chaves deixam o limite da variável claro

// Funções essenciais de string
echo strlen($nome);           // 3 — comprimento
echo strtoupper($nome);       // ANA — maiúsculas
echo strtolower($nome);       // ana — minúsculas
echo str_replace("Ana", "Maria", $nomeCompleto); // Maria Silva
echo substr($nomeCompleto, 0, 3); // Ana — extrai parte da string
echo strpos($nomeCompleto, "Silva"); // 4 — posição da substring
echo trim("  espaços  ");     // "espaços" — remove espaços nas bordas
echo str_pad("5", 3, "0", STR_PAD_LEFT); // "005" — preenchimento

// Verificando conteúdo
var_dump(str_contains($nomeCompleto, "Silva")); // bool(true) — PHP 8+
var_dump(str_starts_with($nomeCompleto, "Ana")); // bool(true) — PHP 8+
var_dump(str_ends_with($nomeCompleto, "Silva")); // bool(true) — PHP 8+

Conversão de tipos (Type Casting)

O PHP converte tipos automaticamente em muitos contextos — isso se chama coerção de tipos. Mas você também pode fazer conversões explícitas, o que é sempre mais seguro e legível.

<?php

// Conversão implícita (automática) — PHP decide o tipo
$resultado = "5" + 3;      // string inteiramente numérica — resultado: 8
$resultado = "5 anos" + 3; // lê os dígitos do início: 8, com Warning
$resultado = "anos" + 3;   // PHP 8: TypeError. Até o PHP 7 isso valia 3, em silêncio// Conversão explícita (casting) — você decide — sempre prefira isso
$texto   = "42";
$inteiro = (int) $texto;    // 42 como int
$decimal = (float) $texto;  // 42.0 como float
$bool    = (bool) $texto;   // true (string não-vazia = true)

// Regras importantes do casting para bool:
// São considerados FALSE: 0, 0.0, "", "0", [], null
// Todo o resto é TRUE
var_dump((bool) 0);      // bool(false)
var_dump((bool) "");     // bool(false)
var_dump((bool) "0");    // bool(false) — armadilha!
var_dump((bool) []);     // bool(false)
var_dump((bool) "false"); // bool(true) — a string "false" não é false!

// Funções de conversão — mais explícitas que casting
$numero = intval("42abc");   // 42 — para inteiro
$decimal = floatval("3.14"); // 3.14 — para float
$texto = strval(100);        // "100" — para string

A linha (bool) "false" retornando true é uma das maiores armadilhas para iniciantes. Lembre-se: o PHP avalia se a string está vazia ou é "0" — não avalia o conteúdo semântico.

Constantes

Constantes são como variáveis, mas seu valor não pode ser alterado após a definição. São definidas com define() ou com a palavra-chave const:

<?php

// define() — pode ser usado em qualquer ponto do código
define('VERSAO_APP', '2.1.0');
define('MAX_TENTATIVAS', 3);
define('PI', 3.14159);

// const — deve ser usada no escopo global ou dentro de classes
const NOME_EMPRESA = 'Minha Empresa Ltda';

echo VERSAO_APP;    // 2.1.0 — sem o $
echo MAX_TENTATIVAS; // 3

// Constantes predefinidas pelo PHP
echo PHP_VERSION;   // versão do PHP instalado
echo PHP_INT_MAX;   // maior inteiro suportado
echo PHP_EOL;       // quebra de linha do sistema operacional
echo PHP_MAJOR_VERSION; // número da versão maior (8, no PHP 8.x)
echo __FILE__;      // caminho completo do arquivo atual
echo __LINE__;      // número da linha atual — útil para debug

Use constantes para valores que não devem mudar ao longo da execução: versões, limites, nomes de configuração, caminhos base. Isso torna o código mais seguro e mais fácil de manter.

Operadores

Operadores são símbolos que realizam operações sobre um ou mais valores. O PHP tem um conjunto rico de operadores.

Operadores aritméticos

<?php

$a = 10;
$b = 3;

echo $a + $b;   // 13 — adição
echo $a - $b;   // 7  — subtração
echo $a * $b;   // 30 — multiplicação
echo $a / $b;   // 3.333... — divisão (resultado sempre float se não divisível)
echo $a % $b;   // 1  — módulo (resto da divisão)
echo $a ** $b;  // 1000 — exponenciação (PHP 5.6+)

// Operadores de incremento e decremento
$contador = 0;
$contador++;    // pós-incremento: usa o valor DEPOIS incrementa
++$contador;    // pré-incremento: incrementa ANTES de usar o valor
$contador--;    // pós-decremento
--$contador;    // pré-decremento

// Operadores de atribuição combinados — atalhos
$total = 100;
$total += 50;   // equivale a $total = $total + 50  → 150
$total -= 30;   // equivale a $total = $total - 30  → 120
$total *= 2;    // equivale a $total = $total * 2   → 240
$total /= 4;    // equivale a $total = $total / 4   → 60
$total %= 7;    // equivale a $total = $total % 7   → 4
$texto  = "Olá";
$texto .= ", mundo!"; // concatenação combinada → "Olá, mundo!"

Operadores de comparação

<?php

$a = 10;
$b = "10";

// == comparação frouxa (loose) — compara valor, ignora tipo
var_dump($a == $b);   // bool(true) — 10 == "10" após coerção

// === comparação estrita — compara valor E tipo
var_dump($a === $b);  // bool(false) — int !== string

// != diferente (frouxa)
var_dump($a != 5);    // bool(true)

// !== diferente estrita
var_dump($a !== $b);  // bool(true) — tipos diferentes

// Comparações numéricas
var_dump($a > 5);     // bool(true)
var_dump($a < 5);     // bool(false)
var_dump($a >= 10);   // bool(true)
var_dump($a <= 10);   // bool(true)

// Spaceship operator <=> — PHP 7+
// Retorna -1, 0 ou 1 dependendo da comparação
// Muito usado em funções de ordenação
echo (1 <=> 2);   // -1 (esquerda menor)
echo (2 <=> 2);   //  0 (iguais)
echo (3 <=> 2);   //  1 (esquerda maior)

Sempre prefira === ao ==. A comparação frouxa tem regras de coerção complexas que causam bugs sutis e difíceis de encontrar.

Operadores lógicos

<?php

$idade    = 25;
$cadastro = true;

// && e || são os operadores lógicos modernos (preferidos)
var_dump($idade >= 18 && $cadastro);  // bool(true) — E lógico
var_dump($idade < 18 || $cadastro);   // bool(true) — OU lógico
var_dump(!$cadastro);                  // bool(false) — NÃO lógico

// and, or, not existem mas têm precedência MENOR que &&, ||
// Isso causa comportamento inesperado em expressões complexas
// Prefira sempre && e || para evitar surpresas
$x = true && false;  // $x = (true && false) = false — correto
$x = true and false; // $x = true, depois "and false" é ignorado — armadilha!

Operadores específicos do PHP

<?php

// Null coalescing (??) — PHP 7+
// Retorna o operando esquerdo se não for null, senão retorna o direito
$nome = null;
$exibir = $nome ?? "Visitante"; // "Visitante"

// Encadeamento: pega o primeiro valor não-null
$a = null;
$b = null;
$c = "Ricardo";
echo $a ?? $b ?? $c; // "Ricardo"

// Null coalescing de atribuição (??=) — PHP 7.4+
$config = [];
$config['tema'] ??= 'claro'; // só atribui se for null
echo $config['tema']; // "claro"

// Operador ternário
$idade = 20;
$status = ($idade >= 18) ? "maior" : "menor";
echo $status; // "maior"

// Ternário curto (Elvis operator) — retorna o próprio valor se truthy
$entrada = "" ;
$valor = $entrada ?: "padrão"; // como $entrada é falsy, usa "padrão"
echo $valor; // "padrão"

// Nullsafe operator (?->) — PHP 8+
// Evita erros ao chamar métodos em objetos possivelmente null
// $resultado = $usuario?->getPerfil()?->getAvatar();
// Se qualquer parte for null, retorna null em vez de erro

A liberdade de tipos do PHP é conveniente até o dia em que deixa de ser: a mesma flexibilidade que permite somar uma string com um inteiro é a que faz um total de pedido sair errado sem ninguém ver. Por isso os dois hábitos que valem mais neste artigo não são sobre sintaxe — são === em vez de ==, e conversão explícita no ponto em que o dado entra no sistema. Adotados cedo, eles tornam desnecessário decorar a tabela de coerções, porque você deixa de depender dela.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Diga o que cada linha produz em PHP 8 — e qual delas se comportava de outro jeito no PHP 7.

<?php
$a = "5" + 3;
$b = "5 anos" + 3;
$c = "anos" + 3;
Ver resposta

✓ Resposta: $a vale 8, sem aviso nenhum: "5" é uma string inteiramente numérica, e converter é o comportamento previsto. $b também vale 8, mas acompanhado de Warning: A non-numeric value encountered — o PHP lê os dígitos iniciais e descarta o resto, avisando que fez isso. $c é a linha que mudou: em PHP 8 ela lança TypeError: Unsupported operand types: string + int e interrompe a execução; até o PHP 7.x, a mesma linha devolvia silenciosamente 3, tratando "anos" como zero. A mudança veio da RFC Saner string to number conversions, e é uma boa notícia: o caso mais perigoso — somar com algo que não tem número nenhum — deixou de ser silencioso. Mas ela também explica por que sistemas antigos quebram ao migrar: código que dependia da conversão para zero agora morre com exceção. A lição prática é não depender de nenhum dos três comportamentos. Dado que vem de formulário, de URL ou de arquivo é sempre string, e a conversão deve ser explícita e validada na entrada, com is_numeric() antes do cast, ou com filter_var($v, FILTER_VALIDATE_INT), que devolve false em vez de adivinhar.

Exercício 2

Um carrinho soma três itens de R$ 0,10, R$ 0,20 e R$ 0,70. O código compara o total com 1.00 e diz que é diferente. O que houve, e como se escreve isso direito?

<?php
$total = 0.1 + 0.2 + 0.7;
var_dump($total == 1.0);  // false
Ver resposta

✓ Resposta: Não é bug do PHP: é como o computador guarda número decimal. O tipo float segue o padrão IEEE 754, em base dois, e valores como 0,1 e 0,2 não têm representação exata nessa base — do mesmo jeito que 1/3 não tem representação decimal finita. O resultado é que 0.1 + 0.2 vale 0.30000000000000004, e o total do carrinho fica a alguns bilionésimos de 1,0. Imprimir engana, porque echo arredonda na exibição; a diferença só aparece na comparação, ou depois de milhares de somas acumuladas. Para dinheiro, a solução é não usar float: guarde centavos em int (1990 em vez de 19.90) e divida só na hora de exibir — inteiro em PHP é exato até 9.223.372.036.854.775.807, o que cobre qualquer valor financeiro real. Em cálculo que exija decimal com precisão arbitrária, use a extensão bcmath (bcadd('0.1', '0.2', 2)), que opera sobre strings. E quando for realmente necessário comparar dois float, nunca use ==: compare a diferença com uma tolerância, abs($a - $b) < 0.00001. A mesma regra vale para o banco de dados — coluna monetária é DECIMAL, jamais FLOAT.

Exercício 3

O artigo mostra que (bool) "0" é false. Sem executar, diga o valor de cada expressão — duas delas costumam surpreender.

<?php
var_dump((bool) "0.0");
var_dump((bool) "false");
var_dump((bool) []);
var_dump(0 == "");
var_dump("abc" == 0);
Ver resposta

✓ Resposta: Na ordem: true, true, false, false, false. As duas primeiras mostram que a conversão para booleano não interpreta o conteúdo: a lista de valores falsos é curta e literal — false, 0, 0.0, "", "0", array vazio e null. A string "0.0" não está nela (é "0", exatamente, e nada além), e "false" é apenas um texto de cinco letras. As duas últimas mudaram no PHP 8 e são a pegadinha real: até o PHP 7, 0 == "" e "abc" == 0 davam true, porque a string era convertida para número antes da comparação — o que fazia if ($senha == 0) aceitar qualquer senha de letras. Desde o PHP 8, quando um dos lados é número e o outro é string não numérica, o número é que vira string, e a comparação passa a ser textual. É uma das correções mais importantes da versão, e também um motivo de sistemas antigos mudarem de comportamento na migração — sem erro nenhum, só resultado diferente. A conclusão prática não muda: use ===, que compara valor e tipo e não tem nenhuma dessas regras.

Exercício 4

Você precisa guardar o identificador de uma transação, um número de 19 dígitos. Este código passa, mas o valor sai errado. Por quê, e o que usar no lugar?

<?php
$id = 9223372036854775807 + 1;
echo $id;          // 9.2233720368548E+18
echo gettype($id); // ?
Ver resposta

✓ Resposta: gettype() devolve double. O valor da primeira linha é PHP_INT_MAX, o maior inteiro representável em 64 bits; somar 1 estoura o limite e o PHP, em vez de dar erro, converte o resultado para float. E float tem cerca de 15 a 16 dígitos significativos, então um número de 19 dígitos perde precisão: o identificador vira outro número, parecido e errado, e a notação científica na saída é o único sinal visível. Isso é especialmente traiçoeiro com identificadores vindos de APIs — o snowflake ID do Twitter e vários sistemas de pagamento usam exatamente essa faixa, e um json_decode despreocupado já os corrompe na entrada. A regra é: identificador não é número, é texto. Guarde como string, compare com ===, e no banco use VARCHAR ou BIGINT conforme o caso — nunca faça aritmética com ele, porque não existe conta que faça sentido com um identificador. Ao ler JSON com valores grandes, json_decode($j, true, 512, JSON_BIGINT_AS_STRING) evita a perda logo na porta de entrada. Para saber o limite da máquina em que o código roda, PHP_INT_MAX e PHP_INT_SIZE respondem — em 32 bits o teto é bem menor.

Exercício 5

Desafio: um formulário envia nome, idade, preço e um campo "aceito os termos". Escreva a conversão da entrada, sabendo que tudo chega como string, e diga o que fazer quando o valor não serve.

Ver resposta

✓ Resposta: O princípio é converter na fronteira: nenhuma função do sistema deve receber string e adivinhar. E a diferença entre casting e validação é o centro do exercício — (int) "abc" devolve 0 sem reclamar, o que transforma lixo em dado plausível.

<?php

declare(strict_types=1);

function converterEntrada(array $post): array
{
    $erros = [];

    // Texto: nunca confie no tamanho nem no conteúdo
    $nome = trim($post['nome'] ?? '');
    if (mb_strlen($nome) < 2) {
        $erros[] = 'Informe o nome completo.';
    }

    // filter_var devolve false quando não é inteiro — (int) devolveria 0
    $idade = filter_var($post['idade'] ?? '', FILTER_VALIDATE_INT,
                        ['options' => ['min_range' => 0, 'max_range' => 130]]);
    if ($idade === false) {
        $erros[] = 'Idade inválida.';
    }

    // Formulário em português manda "19,90"; o PHP só entende "19.90"
    $precoBruto = str_replace(',', '.', trim($post['preco'] ?? ''));
    $preco = filter_var($precoBruto, FILTER_VALIDATE_FLOAT);
    if ($preco === false || $preco < 0) {
        $erros[] = 'Preço inválido.';
    }
    // Dinheiro segue em centavos daqui para dentro
    $centavos = $preco === false ? 0 : (int) round($preco * 100);

    // Caixa desmarcada não é enviada: a ausência é a resposta "não"
    $aceitou = isset($post['termos']);
    if (!$aceitou) {
        $erros[] = 'É preciso aceitar os termos.';
    }

    return ['nome' => $nome, 'idade' => $idade,
            'centavos' => $centavos, 'aceitou' => $aceitou, 'erros' => $erros];
}

Quatro decisões merecem atenção. A idade usa filter_var em vez de (int) porque precisa distinguir "zero" de "não é número" — e a comparação é === false, já que a idade 0 é falsa em contexto booleano. O preço troca vírgula por ponto antes de converter, porque o usuário brasileiro digita como aprendeu e o PHP lê como o padrão americano manda. A caixa de seleção não é convertida e sim testada por existência: navegador nenhum envia caixa desmarcada, então $post['termos'] simplesmente não existe. E os erros são acumulados, não lançados no primeiro problema: quem preencheu o formulário merece ver tudo o que falta de uma vez, em vez de descobrir um erro por tentativa.

Comentários

Mais em PHP

Design Patterns: Singleton, Factory e Builder
Design Patterns: Singleton, Factory e Builder

Três padrões de criação e o que cada um custa: o Singleton, que garante…

Funções
Funções

Uma função guarda um trecho de lógica sob um nome e um contrato. Como declarar…

Exceções Avançadas
Exceções Avançadas

Uma hierarquia de exceções bem desenhada diz ao chamador o que ele pode tratar…