PHP $_GET e $_POST

Na aula anterior, você montou formulários e viu que o navegador envia pares nome=valor. Agora é a vez do PHP: onde esses valores aparecem, como ler cada um, como saber se o formulário foi mesmo enviado e o que fazer quando um campo não veio. São dois arrays, $_GET e $_POST, que você vai usar em praticamente toda página PHP que escrever.

Você vai aprender
  • O que são $_GET e $_POST e de onde vem o conteúdo deles.
  • Como ler um endereço como ?nome=Ana&idade=29.
  • Por que tudo chega como texto.
  • Como saber se o formulário foi enviado: $_SERVER['REQUEST_METHOD'] === 'POST'.
  • O aviso Undefined array key e como evitá-lo com ?? e isset.
  • Como receber várias checkboxes de uma vez com name="frutas[]".
  • O que é $_REQUEST e por que é melhor não usar.
  • Como montar links com dados usando http_build_query.

A ideia: duas caixas de correio

Imagine que a casa do PHP tem duas caixas de correio na porta. Tudo o que chega escrito no envelope (no endereço) vai para a caixa $_GET. Tudo o que chega dentro do envelope (no corpo de um POST) vai para a caixa $_POST. Antes de a primeira linha do seu programa rodar, o PHP já abriu a correspondência, separou cada par nome=valor e arrumou tudo nas caixas.

Essas "caixas" são arrays associativos (vistos em Arrays Associativos): o name do campo é a chave, e o que o visitante digitou é o valor.

ArrayRecebe os dados deExemplo de pedido
$_GETO endereço, depois do ? (a query string, "texto de consulta")GET /busca.php?q=bolo
$_POSTO corpo de um pedido POST (formulário com method="post")POST /cadastro.php + corpo nome=Ana

Elas são superglobais: existem sozinhas, em qualquer lugar do código, inclusive dentro de funções, sem precisar de global (veja Escopo). O nome com $_ e letras maiúsculas é a marca das superglobais; a lista completa está em $_SERVER e Outras Superglobais.


$_GET: os dados do endereço

Você nem precisa de formulário para usar $_GET: basta escrever o endereço. Este programa mostra o conteúdo das duas caixas com var_dump:

<?php
echo "GET: ";  var_dump($_GET);
echo "POST: "; var_dump($_POST);

A Ana pediu dump.php?nome=Ana&idade=29:

$ curl -s "localhost:8000/dump.php?nome=Ana&idade=29"
GET: array(2) {
  ["nome"]=>
  string(3) "Ana"
  ["idade"]=>
  string(2) "29"          « "29" com aspas: é texto, não número
}
POST: array(0) {        « nada veio no corpo
}

Anatomia do endereço

localhost:8000
O servidor (a máquina e a porta).
/dump.php
O programa que vai rodar.
?
Separa o caminho dos dados. Tudo depois dele é a query string.
nome=Ana
Primeiro par: vira $_GET['nome'] com o valor "Ana".
&
Separa um par do outro.
idade=29
Segundo par: $_GET['idade'] vale "29".

Para usar um valor, leia a chave como em qualquer array:

<?php
$nome = $_GET['nome'];
echo "Olá, $nome!\n";
$ curl -s "localhost:8000/ola.php?nome=Ana"
Olá, Ana!
Tudo é texto. Repare no string(2) "29". O protocolo da web só transporta texto, então o PHP entrega strings, mesmo que o campo seja type="number". Se precisar fazer conta, converta ((int) $_GET['idade'], de Conversão de Tipos) e, melhor ainda, confira se é mesmo um número (Validar Dados).

$_POST: os dados do corpo

O mesmo programa, agora recebendo os dados por POST (o -d do curl faz um POST, como um formulário com method="post"):

$ curl -s localhost:8000/dump.php -d "nome=Ana&idade=29"
GET: array(0) {
}
POST: array(2) {
  ["nome"]=>
  string(3) "Ana"
  ["idade"]=>
  string(2) "29"
}

As mesmas chaves, os mesmos valores, só que na outra caixa. As duas caixas podem ter coisas ao mesmo tempo: um formulário POST cujo action tem um ?, como <form method="post" action="editar.php?id=5">:

$ curl -s "localhost:8000/dump.php?id=5" -d "nome=Ana"
GET: array(1) {
  ["id"]=>
  string(1) "5"            « do endereço
}
POST: array(1) {
  ["nome"]=>
  string(3) "Ana"          « do corpo
}

Isso é comum em páginas de edição: o id do que está sendo editado vai no endereço, e os campos alterados, no corpo.

A página foi aberta com ?nome=Ana&idade=29 no endereço. Rode e veja o var_dump na aba Resultado. Depois use os dois formulários: o primeiro envia por GET (o endereço muda), o segundo por POST.

<form method="get">
  GET: <input name="nome" value="Bia"> <button>Enviar por GET</button>
</form>
<form method="post">
  POST: <input name="nome" value="Caio"> <button>Enviar por POST</button>
</form>

<h3>$_GET</h3>
<pre><?php var_dump($_GET); ?></pre>
<h3>$_POST</h3>
<pre><?php var_dump($_POST); ?></pre>

Foi enviado? REQUEST_METHOD

Quando uma página mostra e processa o próprio formulário (como na aula Formulários HTML), o PHP precisa saber em qual das duas situações está:

  1. Primeira visita: a pessoa só abriu a página. Mostre o formulário vazio.
  2. Envio: a pessoa clicou em Enviar. Processe os dados.

Para formulários POST, a forma mais segura de perguntar é olhar o método do pedido. Abrir uma página (digitar o endereço, clicar num link) é sempre GET; só o envio do formulário é POST. O PHP guarda o método em $_SERVER['REQUEST_METHOD']:

<?php
echo "Método: ", $_SERVER['REQUEST_METHOD'], "\n";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    echo "O formulário foi enviado.\n";
} else {
    echo "Primeira visita: mostrar o formulário.\n";
}
$ curl -s localhost:8000/metodo.php
Método: GET
Primeira visita: mostrar o formulário.
$ curl -s -d "x=1" localhost:8000/metodo.php
Método: POST
O formulário foi enviado.
$_SERVER
Outra superglobal: informações sobre o pedido e o servidor (veja $_SERVER e Outras Superglobais).
['REQUEST_METHOD']
"Método do pedido": 'GET' ou 'POST' (sempre em maiúsculas).
=== 'POST'
Comparação estrita (de Comparação): exatamente o texto POST.

Por que não if ($_POST) ou isset($_POST['enviar'])?

Você vai encontrar essas formas na internet. Elas funcionam quase sempre, mas têm furos:

FormaProblema
if ($_POST)Um POST sem nenhum campo preenchível (só checkboxes desmarcadas, por exemplo) chega com $_POST vazio, e o envio passa despercebido.
if (isset($_POST['enviar']))Depende de o botão ter name="enviar". Se alguém renomear o botão, ou se o formulário for enviado por JavaScript (que não inclui botão nenhum), o envio passa despercebido.
if ($_SERVER['REQUEST_METHOD'] === 'POST')Nenhum: responde exatamente "este pedido é um envio POST?". Use esta.

Para formulários GET (uma busca, por exemplo), o método é GET nas duas situações, então a pergunta é sobre o campo: "chegou um q?" (isset($_GET['q'])), como você vê a seguir.


Campo ausente: Undefined array key

O que acontece se a Ana abrir ola.php sem o ?nome=… no endereço? A chave 'nome' não existe no $_GET:

$ curl -s localhost:8000/ola.php
<br />
<b>Warning</b>:  Undefined array key "nome" in <b>/home/ana/site/ola.php</b> on line <b>2</b><br />
Olá, !

No navegador, isso aparece assim: uma linha em negrito Warning: Undefined array key "nome" in /home/ana/site/ola.php on line 2, e depois "Olá, !". O PHP não parou (um Warning é um aviso, não um erro fatal; veja Lendo Erros do PHP): a variável ficou com null, que vira texto vazio no echo.

Rodando o mesmo arquivo direto no terminal (onde $_GET está sempre vazio), o aviso sai sem as tags HTML:

$ php ola.php

Warning: Undefined array key "nome" in /home/ana/site/ola.php on line 2
Olá, !
Parte da mensagemSignifica
WarningAviso: algo está errado, mas o programa continua.
Undefined array key "nome""Chave de array não definida": você leu ['nome'], mas essa chave não existe.
in /home/ana/site/ola.php on line 2Arquivo e linha da leitura.

Num formulário, campo ausente é normal: primeira visita, checkbox desmarcada, alguém abrindo o endereço sem os dados. O seu código precisa esperar por isso. Há três ferramentas:

1. ??: "use isto, ou então aquilo"

O operador ?? (de ?? e ?->) devolve o valor da esquerda se ele existir e não for null; senão, devolve o da direita. Sem aviso nenhum:

<?php
$nome = $_GET['nome'] ?? '';
echo "Olá, $nome!\n";
$ curl -s localhost:8000/ola2.php
Olá, !          « sem o Warning: nome ficou com '' (texto vazio)

O valor da direita pode ser qualquer coisa útil: $_GET['nome'] ?? 'visitante', $_POST['frutas'] ?? [] (um array vazio), $_GET['pagina'] ?? 1. Este é o jeito mais comum de ler um campo: sempre com ?? valor_padrão.

2. isset: "chegou?"

isset($_GET['nome']) responde true se a chave existe (e não é null). Útil quando a pergunta é "veio ou não veio?", como numa checkbox:

if (isset($_POST['entrega'])) {
    echo "Vamos entregar no seu endereço.";
}

3. empty: "chegou vazio?"

empty($_GET['nome']) é true se a chave não existe ou se o valor é "vazio" ('', '0', 0, null, []). Também não gera aviso. Cuidado com o '0': veja a tabela.

<?php
$nome = $_GET['nome'] ?? 'visitante';
echo "Olá, $nome!\n";
var_dump(isset($_GET['nome']), empty($_GET['nome']));
Endereço$_GET['nome'] ?? 'visitante'issetempty
vazio.phpvisitantefalsetrue
vazio.php?nome=(texto vazio)truetrue
vazio.php?nome=00truetrue (!)
vazio.php?nome=AnaAnatruefalse

Saídas reais das quatro chamadas:

$ curl -s localhost:8000/vazio.php
Olá, visitante!
bool(false)
bool(true)
$ curl -s "localhost:8000/vazio.php?nome="
Olá, !
bool(true)
bool(true)
$ curl -s "localhost:8000/vazio.php?nome=0"
Olá, 0!
bool(true)
bool(true)
$ curl -s "localhost:8000/vazio.php?nome=Ana"
Olá, Ana!
bool(true)
bool(false)

Duas lições: o ?? só usa o padrão quando o campo não veio; um campo que veio vazio (?nome=, um <input> deixado em branco) continua vazio. E o empty acha que "0" é vazio, o que é errado para "quantidade: 0" ou "nota: 0". Para conferir campos de verdade, a aula Validar Dados usa trim e comparações explícitas.

Missão: a página foi aberta sem nada no endereço, e o PHP reclama de Undefined array key. Use ?? para que, sem nome, apareça Olá, visitante!, sem aviso nenhum. Depois envie o formulário com o seu nome.

<?php
$nome = $_GET['nome'];
?>
<p>Olá, <?= htmlspecialchars($nome) ?>!</p>
<form>
  <input name="nome"> <button>Enviar</button>
</form>
<?php
$nome = $_GET['nome'] ?? 'visitante';
?>
<p>Olá, <?= htmlspecialchars($nome) ?>!</p>
<form>
  <input name="nome"> <button>Enviar</button>
</form>

Várias checkboxes: name="frutas[]"

A Ana quer um formulário de salada de frutas, onde a pessoa marca quantas frutas quiser. Se todas as checkboxes tiverem name="fruta", só a última marcada sobrevive (cada chave existe uma vez só, como você viu em Formulários HTML). A solução é colocar [] no fim do nome:

<form method="post" action="frutas.php">
  <label><input type="checkbox" name="frutas[]" value="uva"> Uva</label>
  <label><input type="checkbox" name="frutas[]" value="pera"> Pera</label>
  <label><input type="checkbox" name="frutas[]" value="manga"> Manga</label>
  <button>Montar salada</button>
</form>

Os colchetes dizem ao PHP: "junte todos os valores com este nome num array". Marcando uva e pera, o navegador envia frutas[]=uva&frutas[]=pera (com os colchetes codificados como %5B%5D), e o PHP monta uma lista:

<?php
$frutas = $_POST['frutas'] ?? [];
var_dump($frutas);
foreach ($frutas as $fruta) {
    echo "- $fruta\n";
}
$ curl -s localhost:8000/frutas.php -d "frutas%5B%5D=uva&frutas%5B%5D=pera"
array(2) {
  [0]=>
  string(3) "uva"
  [1]=>
  string(4) "pera"
}
- uva
- pera
$ curl -s localhost:8000/frutas.php -d ""
array(0) {          « nenhuma marcada: "frutas" não veio, e o ?? [] deu um array vazio
}
name="frutas[]"
Os colchetes vazios fazem cada valor marcado entrar no fim de uma lista, como $frutas[] = 'uva' (de Arrays Indexados).
?? []
Nenhuma marcada = nada enviado. Com o array vazio como padrão, o foreach simplesmente não repete, sem aviso.

Funciona com qualquer campo, não só checkbox: <select name="cores[]" multiple> (lista com várias escolhas) ou vários <input name="telefones[]">. Também dá para dar nomes às chaves: name="endereco[rua]" e name="endereco[cidade]" chegam como $_POST['endereco']['rua'] e $_POST['endereco']['cidade'].


$_REQUEST: a caixa misturada (evite)

Existe uma terceira superglobal, $_REQUEST, que junta $_GET, $_POST e, conforme a configuração, os cookies (Cookies). Parece prático: "não importa de onde veio". O problema é justamente esse. Veja o que acontece quando o mesmo nome chega pelos três caminhos:

<?php
var_dump($_REQUEST);
$ curl -s "localhost:8000/req.php?nome=Get" -d "nome=Post" -b "nome=Cookie"
array(1) {
  ["nome"]=>
  string(6) "Cookie"        « o cookie venceu os outros dois
}
$ curl -s "localhost:8000/req.php?nome=Get" -d "nome=Post"
array(1) {
  ["nome"]=>
  string(4) "Post"          « sem cookie, o POST venceu o GET
}

Quem "vence" depende de duas configurações do php.ini (request_order e variables_order), que mudam de servidor para servidor. No laboratório da Ana, o PHP sem php.ini põe até o cookie na mistura; muitos servidores deixam só GET e POST. Ou seja: o mesmo código pode se comportar diferente em outra máquina, e um cookie esquecido pode "trocar" um campo do formulário sem você perceber.

Regra: leia sempre da caixa certa. Formulário POST → $_POST. Endereço e formulário GET → $_GET. Cookie → $_COOKIE. Assim o código diz claramente de onde espera cada dado.

Montar links com dados: http_build_query

O caminho inverso também aparece muito: o PHP precisa escrever um endereço com dados, como o link "Próxima página" de uma busca. Concatenar à mão ("?busca=" . $busca . "&pagina=2") quebra quando o texto tem espaço, acento ou &. A função http_build_query ("construir a consulta HTTP") recebe um array e devolve a query string já codificada:

<?php
$filtros = ['busca' => 'bolo de cenoura', 'pagina' => 2];
echo http_build_query($filtros), "\n";
echo '<a href="?' . http_build_query($filtros) . '">Próxima página</a>', "\n";
echo http_build_query(['frutas' => ['uva', 'pera']]), "\n";
$ curl -s localhost:8000/link.php
busca=bolo+de+cenoura&pagina=2
<a href="?busca=bolo+de+cenoura&pagina=2">Próxima página</a>
frutas%5B0%5D=uva&frutas%5B1%5D=pera

A última linha mostra que até arrays viram o formato com colchetes (frutas[0]=uva, codificado), que o PHP do outro lado transforma de volta em array. Para um valor sozinho, existe a irmã urlencode, vista em Escapar a Saída.


Quando dá errado

Warning: Undefined array key "nome"
$ curl -s localhost:8000/ola.php
<br />
<b>Warning</b>:  Undefined array key "nome" in <b>/home/ana/site/ola.php</b> on line <b>2</b><br />
Olá, !

O que aconteceu: o código leu $_GET['nome'], mas o campo não veio. As causas mais comuns: primeira visita (nada foi enviado ainda); o formulário usa POST e o código lê $_GET (ou o contrário); o name no HTML é diferente da chave no PHP (name="Nome" × $_GET['nome']: maiúsculas contam); checkbox desmarcada.

Como resolver: confira método e nome; e leia sempre com um padrão: $_GET['nome'] ?? ''.

foreach() argument must be of type array|object, string given
$ curl -s localhost:8000/frutas.php -d "frutas=uva"
string(3) "uva"
<br />
<b>Warning</b>:  foreach() argument must be of type array|object, string given in <b>/home/ana/site/frutas.php</b> on line <b>4</b><br />

O que aconteceu: o foreach esperava um array (as frutas marcadas), mas chegou uma string. Ou o HTML está com name="frutas" (sem os colchetes), ou alguém mandou o pedido "na mão", sem o seu formulário. Lembre: qualquer pessoa pode enviar qualquer coisa para o seu PHP.

Como resolver: no HTML, name="frutas[]". No PHP, confira o tipo antes de usar: $frutas = $_POST['frutas'] ?? []; seguido de if (!is_array($frutas)) { $frutas = []; }.

A conta deu errado: Unsupported operand types ou A non-numeric value
<?php
$a = "29" + "1";
var_dump($a);
$b = "vinte" + 1;
var_dump($b);
$ php conta.php
int(30)

Fatal error: Uncaught TypeError: Unsupported operand types: string + int in /home/ana/site/conta.php:4
Stack trace:
#0 {main}
  thrown in /home/ana/site/conta.php on line 4

E com $b = "29 anos" + 1;:

$ php conta2.php

Warning: A non-numeric value encountered in /home/ana/site/conta2.php on line 2
int(30)

O que aconteceu: os valores do formulário chegam como texto. Numa conta, o PHP converte sozinho um texto que é número ("29"). Um texto que não é número para o programa (TypeError, um erro fatal); um texto que só começa com número gera um aviso e usa o começo (veja Conversão de Tipos). Num formulário, basta alguém digitar "vinte" no campo de idade.

Como resolver: confira antes de usar, com filter_var($_POST['idade'], FILTER_VALIDATE_INT), explicado em Validar Dados.

O texto apareceu com tags funcionando (ou a página "quebrou")

O que aconteceu: o valor de $_GET/$_POST foi escrito com echo direto, e alguém digitou HTML no campo. Isso é a porta para um ataque chamado XSS.

Como resolver: todo dado do visitante que vai para a página passa por htmlspecialchars. A aula inteira sobre isso é Escapar a Saída (XSS).


Referência

CódigoO que faz
$_GET['x']Valor de x vindo do endereço (?x=…)
$_POST['x']Valor de x vindo do corpo de um POST
$_GET['x'] ?? 'padrão'O valor, ou 'padrão' se não veio (sem aviso)
isset($_POST['x'])true se x veio
empty($_POST['x'])true se não veio ou veio vazio (inclusive "0")
$_SERVER['REQUEST_METHOD'] === 'POST'O pedido é um envio POST?
name="x[]" no HTMLVários valores viram um array em $_POST['x']
name="x[chave]" no HTMLVira $_POST['x']['chave']
http_build_query($array)Monta a=1&b=2 codificado
$_REQUESTMistura GET, POST (e talvez cookies). Evite.

Exercícios

1. Ler sem aviso

Complete para ler o campo cidade do POST, com 'Recife' como padrão:

$cidade = $_POST['cidade'] ;

2. Foi enviado?

Complete a pergunta "o formulário POST foi enviado?":

if ($_SERVER[''] === 'POST')

3. Várias cores

Complete o name para que várias checkboxes de cor cheguem como array em $_POST['cores']:

<input type="checkbox" name="" value="azul">

4. O endereço é produto.php?id=7&cor=azul. O que var_dump($_GET['id']) mostra?

5. A pessoa deixou o campo <input name="apelido"> em branco e enviou. Quanto vale $_POST['apelido'] ?? 'sem apelido'?

6. Por que evitar $_REQUEST?

Missão: a salada de frutas da Ana. A página já foi aberta com um envio POST (uva e manga marcadas). Complete o PHP: só quando o método for POST, leia $_POST['frutas'] (com ?? []), mostre Você escolheu N frutas (use count) e a lista com foreach. Depois marque outras frutas na aba Resultado e envie.

<form method="post">
  <label><input type="checkbox" name="frutas[]" value="uva"> Uva</label>
  <label><input type="checkbox" name="frutas[]" value="pera"> Pera</label>
  <label><input type="checkbox" name="frutas[]" value="manga"> Manga</label>
  <button>Montar salada</button>
</form>
<?php
// seu código aqui
<form method="post">
  <label><input type="checkbox" name="frutas[]" value="uva"> Uva</label>
  <label><input type="checkbox" name="frutas[]" value="pera"> Pera</label>
  <label><input type="checkbox" name="frutas[]" value="manga"> Manga</label>
  <button>Montar salada</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $frutas = $_POST['frutas'] ?? [];
    echo "<p>Você escolheu " . count($frutas) . " frutas:</p>";
    echo "<ul>";
    foreach ($frutas as $fruta) {
        echo "<li>" . htmlspecialchars($fruta) . "</li>";
    }
    echo "</ul>";
}

Resumo

  • $_GET guarda os dados do endereço (?nome=Ana&idade=29); $_POST, os do corpo de um POST. São arrays associativos superglobais.
  • Tudo chega como string, mesmo números.
  • Para saber se um formulário POST foi enviado: $_SERVER['REQUEST_METHOD'] === 'POST'.
  • Campo que não veio gera Warning: Undefined array key. Leia sempre com ?? padrão; use isset para "veio?".
  • ?? só troca o que não veio; campo vazio continua vazio. empty considera "0" vazio.
  • name="frutas[]" junta vários valores num array.
  • Evite $_REQUEST: leia da caixa certa.
  • http_build_query monta links com dados, já codificados.