PHP Superglobais

As superglobais são arrays que o PHP preenche sozinho a cada requisição: o que veio na URL, o que o formulário mandou, os cookies, os arquivos enviados, dados do servidor. "Super" porque estão disponíveis em qualquer lugar, até dentro de funções, sem global. Esta página é a consulta rápida; os valores mostrados são reais, do servidor embutido (php -S) na máquina da Ana. Para aprender com calma, veja $_GET e $_POST e $_SERVER e Outras Superglobais.

Regra de segurança para a página inteira

Tudo o que vem de $_GET, $_POST, $_COOKIE, $_FILES, $_REQUEST e das chaves HTTP_* de $_SERVER foi escrito pelo visitante, que pode mandar qualquer coisa. Sempre: confira se existe (??), valide (Validar Dados), escape na saída com htmlspecialchars (XSS) e use prepared statements no banco (SQL Injection).

Atalhos: Visão geral · $_GET · $_POST · $_SERVER · $_FILES · $_COOKIE · $_SESSION · $_ENV · $_REQUEST · $GLOBALS


Visão geral

SuperglobalO que guardaDe onde vemAula
$_GETOs parâmetros da URL (depois do ?)Links e formulários method="get"$_GET e $_POST
$_POSTOs campos de um formulário enviado por POSTFormulários method="post"$_GET e $_POST
$_SERVERDados da requisição e do servidor (método, URL, cabeçalhos…)O servidor web (ou o terminal, no CLI)$_SERVER
$_FILESOs arquivos enviados num formulário<input type="file"> com enctype="multipart/form-data"Upload de Arquivos
$_COOKIEOs cookies que o navegador mandouCookies criados antes com setcookieCookies
$_SESSIONDados guardados no servidor para este visitanteO seu código, depois de session_start()Sessões
$_ENVVariáveis de ambienteO sistema operacional (se variables_order tiver E)$_SERVER
$_REQUEST$_GET + $_POST (+ $_COOKIE) misturadosOs três acima$_GET e $_POST
$GLOBALSTodas as variáveis globais do scriptO seu códigoEscopo

Todas são arrays associativos (aula): lê-se com colchetes e a chave entre aspas, e uma chave que não existe dá Warning: Undefined array key. Por isso o jeito seguro de ler é com ?? (aula):

<?php
$nome = $_GET["nome"] ?? "visitante";   // se não veio, usa "visitante"
echo "Olá, " . htmlspecialchars($nome);

E elas funcionam dentro de funções sem nenhuma declaração (as variáveis comuns não: veja Escopo):

<?php
function f() { echo $_GET["x"] ?? "sem x", " | ", $_SERVER["PHP_SELF"], "\n"; }
f();
$ php g4.php
sem x | g4.php

$_GET

Cada par nome=valor da URL vira uma chave. Os valores são sempre texto (string), mesmo que pareçam números; colchetes no nome (cores[]) fazem o PHP montar um array.

URL pedidaResultadoObservação
/tudo.php?nome=Bia&pagina=2["nome" => "Bia", "pagina" => "2"]"2" é texto: converta com (int) ou valide com filter_var.
/tudo.php[] (array vazio)$_GET["nome"] daria Undefined array key "nome"; use $_GET["nome"] ?? "".
/busca.php?q=caf%C3%A9+com+leite["q" => "café com leite"]O PHP já decodifica %C3%A9 e + (espaço). Para montar URLs, use urlencode ou http_build_query.
?cores[]=azul&cores[]=verde["cores" => ["azul", "verde"]]Vira array (o mesmo vale para $_POST). Por isso confira com is_string antes de passar a funções de texto.
/ola.php?nome=<b>Ana</b>["nome" => "<b>Ana</b>"]O visitante pode mandar HTML. Com htmlspecialchars, sai &lt;b&gt;Ana&lt;/b&gt; (inofensivo).

Conferido com curl. O servidor roda num terminal; os pedidos, em outro:

$ php -S localhost:8000
[Fri Oct 16 20:51:24 2026] PHP 8.5.8 Development Server (http://localhost:8000) started
<?php
$nome = $_GET["nome"] ?? "visitante";
?>
<p>Olá, <?= htmlspecialchars($nome) ?>!</p>
<p>Método: <?= $_SERVER["REQUEST_METHOD"] ?></p>
$ curl -s "localhost:8000/ola.php?nome=Ana&cidade=Recife"
<p>Olá, Ana!</p>
<p>Método: GET</p>
$ curl -s "localhost:8000/ola.php?nome=<b>Ana</b>"
<p>Olá, &lt;b&gt;Ana&lt;/b&gt;!</p>
<p>Método: GET</p>

Experimente: a página recebe ?nome=Ana&cidade=Recife, mas só usa o nome. Leia também cidade (com ??) e acrescente o parágrafo Ana mora em Recife., escapado com htmlspecialchars.

<?php
$nome = $_GET["nome"] ?? "visitante";
?>
<p>Olá, <?= htmlspecialchars($nome) ?>!</p>
<p>Método: <?= $_SERVER["REQUEST_METHOD"] ?></p>
<?php
$nome = $_GET["nome"] ?? "visitante";
$cidade = $_GET["cidade"] ?? "algum lugar";
?>
<p>Olá, <?= htmlspecialchars($nome) ?>!</p>
<p>Método: <?= $_SERVER["REQUEST_METHOD"] ?></p>
<p><?= htmlspecialchars($nome) ?> mora em <?= htmlspecialchars($cidade) ?>.</p>

$_POST

Os campos de um formulário com method="post". A chave é o name do campo. Os dados vão no corpo da requisição, não na URL (não aparecem no histórico nem no log do servidor).

<?php
header("Content-Type: text/plain; charset=utf-8");
echo "GET: "; var_dump($_GET);
echo "POST: "; var_dump($_POST);
echo "COOKIE: "; var_dump($_COOKIE);
echo "REQUEST: "; var_dump($_REQUEST);

Um pedido com tudo ao mesmo tempo: parâmetros na URL, campos de formulário (-d faz o curl enviar por POST, como um formulário) e um cookie (-b):

$ curl -s -b "tema=escuro" -d "nome=Ana&cores[]=azul&cores[]=verde" "localhost:8000/tudo.php?nome=Bia&pagina=2"
GET: array(2) {
  ["nome"]=>
  string(3) "Bia"
  ["pagina"]=>
  string(1) "2"
}
POST: array(2) {
  ["nome"]=>
  string(3) "Ana"
  ["cores"]=>
  array(2) {
    [0]=>
    string(4) "azul"
    [1]=>
    string(5) "verde"
  }
}
COOKIE: array(1) {
  ["tema"]=>
  string(6) "escuro"
}
REQUEST: array(4) {
  ["nome"]=>
  string(3) "Ana"
  ["pagina"]=>
  string(1) "2"
  ["cores"]=>
  array(2) {
    [0]=>
    string(4) "azul"
    [1]=>
    string(5) "verde"
  }
  ["tema"]=>
  string(6) "escuro"
}

Repare: um mesmo pedido POST pode ter $_GET (o que está na URL) e $_POST (o corpo). E $_REQUEST misturou tudo: o nome do POST (Ana) apagou o do GET (Bia).

SituaçãoO que acontece
Checkbox desmarcadoO campo não vem: use isset($_POST["aceito"]).
<select multiple name="cores[]">$_POST["cores"] é um array (ou não existe, se nada foi escolhido).
Campo vazioVem como "" (texto vazio), não como null.
Corpo em JSON (Content-Type: application/json)$_POST fica vazio. Leia com json_decode(file_get_contents("php://input"), true). Veja Criar uma API JSON e o projeto da API.
Saber se o formulário foi enviadoif ($_SERVER["REQUEST_METHOD"] === "POST")

$_SERVER

Informações sobre a requisição atual e o servidor. Os valores abaixo são os reais que o php -S da Ana entregou para este pedido (o -H do curl manda um cabeçalho, como um navegador faria):

$ curl -s "localhost:8000/server.php?busca=caneta&pagina=2" -H "Referer: http://localhost:8000/" -H "Accept-Language: pt-BR"
REQUEST_METHOD         => 'GET'
REQUEST_URI            => '/server.php?busca=caneta&pagina=2'
QUERY_STRING           => 'busca=caneta&pagina=2'
SCRIPT_NAME            => '/server.php'
SCRIPT_FILENAME        => '/home/ana/sg/server.php'
PHP_SELF               => '/server.php'
PATH_INFO              => NULL
DOCUMENT_ROOT          => '/home/ana/sg'
SERVER_NAME            => 'localhost'
SERVER_PORT            => '8000'
SERVER_PROTOCOL        => 'HTTP/1.1'
SERVER_SOFTWARE        => 'PHP/8.5.8 (Development Server)'
SERVER_ADDR            => NULL
REMOTE_ADDR            => '127.0.0.1'
REMOTE_PORT            => '46668'
HTTP_HOST              => 'localhost:8000'
HTTP_USER_AGENT        => 'curl/8.18.0'
HTTP_ACCEPT            => '*/*'
HTTP_ACCEPT_LANGUAGE   => 'pt-BR'
HTTP_REFERER           => 'http://localhost:8000/'
CONTENT_TYPE           => NULL
CONTENT_LENGTH         => NULL
HTTPS                  => NULL
REQUEST_TIME           => 1792194685
REQUEST_TIME_FLOAT     => 1792194685.127057

O server.php só percorre uma lista de chaves e mostra cada valor com var_export (NULL = a chave não existe neste pedido):

<?php
header("Content-Type: text/plain; charset=utf-8");
$chaves = ["REQUEST_METHOD", "REQUEST_URI", "QUERY_STRING", /* … */ "REQUEST_TIME_FLOAT"];
foreach ($chaves as $c) {
    echo str_pad($c, 22), " => ", var_export($_SERVER[$c] ?? null, true), "\n";
}

As chaves principais

ChaveValor real (php -S)Para que serve
REQUEST_METHOD'GET'; num envio de formulário, 'POST'Saber se o formulário foi enviado; numa API, escolher a ação (GET, POST, PUT, DELETE).
REQUEST_URI'/server.php?busca=caneta&pagina=2'O caminho pedido, com a query string. Base de roteadores: parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH).
QUERY_STRING'busca=caneta&pagina=2'Só a parte depois do ? (já separada em $_GET).
SCRIPT_NAME'/server.php'O caminho do script na URL.
SCRIPT_FILENAME'/home/ana/sg/server.php'O caminho do arquivo no disco.
PHP_SELF'/server.php'O script atual na URL, incluindo o PATH_INFO. Cuidado: veja o cenário abaixo.
PATH_INFOem /server.php/posts/3: '/posts/3'O que vem depois do nome do script; usado em APIs simples.
DOCUMENT_ROOT'/home/ana/sg'A pasta raiz do site (onde o php -S foi iniciado, ou a de -t).
SERVER_NAME / SERVER_PORT'localhost' / '8000'Nome e porta do servidor.
SERVER_PROTOCOL'HTTP/1.1'Versão do HTTP; útil em header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found").
SERVER_SOFTWARE'PHP/8.5.8 (Development Server)'Que servidor está rodando (no Apache: Apache/2.4…).
REMOTE_ADDR / REMOTE_PORT'127.0.0.1' / '46668'O IP (e a porta) de quem pediu. Atrás de um proxy, é o IP do proxy.
HTTP_HOST'localhost:8000'O endereço que o visitante digitou (cabeçalho Host). Vem do visitante: não confie cegamente.
HTTP_USER_AGENT'curl/8.18.0'O programa/navegador do visitante. Qualquer um pode mandar qualquer texto aqui.
HTTP_ACCEPT'*/*'Que formatos o cliente aceita (text/html, application/json…).
HTTP_ACCEPT_LANGUAGE'pt-BR'Idiomas preferidos do visitante.
HTTP_REFERER'http://localhost:8000/'A página de onde o visitante veio (escrito assim mesmo, "referer", com um r). Só existe se o navegador mandar.
HTTP_* (qualquer)—Todo cabeçalho vira uma chave: X-Token: abc → $_SERVER["HTTP_X_TOKEN"].
CONTENT_TYPE / CONTENT_LENGTHno POST: 'application/x-www-form-urlencoded' / '17'O formato e o tamanho do corpo. Não existem em GET.
HTTPSNULL (não existe)Em servidores com HTTPS, costuma valer 'on'.
SERVER_ADDRNULL no php -SO IP do servidor (o Apache e o nginx preenchem).
REQUEST_TIME1792194685Quando o pedido chegou, em segundos desde 1970 (timestamp). date("d/m/Y H:i", $_SERVER["REQUEST_TIME"]); veja Datas.
REQUEST_TIME_FLOAT1792194685.127057O mesmo, com microssegundos (para medir quanto a página demorou).

Num pedido POST e num pedido com PATH_INFO, as chaves que mudam:

$ curl -s -d "nome=Ana&idade=29" localhost:8000/server.php | grep -E "METHOD|CONTENT|QUERY"
REQUEST_METHOD         => 'POST'
QUERY_STRING           => NULL
CONTENT_TYPE           => 'application/x-www-form-urlencoded'
CONTENT_LENGTH         => '17'
$ curl -s "localhost:8000/server.php/posts/3" | grep -E "PATH_INFO|SCRIPT_NAME|PHP_SELF|REQUEST_URI"
REQUEST_URI            => '/server.php/posts/3'
SCRIPT_NAME            => '/server.php'
PHP_SELF               => '/server.php/posts/3'
PATH_INFO              => '/posts/3'

Todas as chaves que o php -S criou num GET simples (o Apache e o nginx criam mais):

$ curl -s localhost:8000/chaves.php | tr "\n" " "
DOCUMENT_ROOT HTTP_ACCEPT HTTP_HOST HTTP_USER_AGENT PHP_SELF REMOTE_ADDR REMOTE_PORT REQUEST_METHOD REQUEST_TIME REQUEST_TIME_FLOAT REQUEST_URI SCRIPT_FILENAME SCRIPT_NAME SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE 

$_SERVER no terminal (CLI)

Rodando php arquivo.php, não há requisição: as chaves REQUEST_* e HTTP_* não existem, e aparecem outras, como argv (os argumentos) e as variáveis de ambiente (HOME, PATH…). Veja PHP na Linha de Comando.

<?php
echo $_SERVER["argc"], "\n";
print_r($_SERVER["argv"]);
echo $_SERVER["PHP_SELF"], " | ", $_SERVER["SCRIPT_NAME"], " | ", $_SERVER["SCRIPT_FILENAME"], "\n";
echo $_SERVER["REQUEST_METHOD"] ?? "(sem REQUEST_METHOD)", "\n";
echo PHP_SAPI, "\n";
$ php cli.php ana 29
3
Array
(
    [0] => cli.php
    [1] => ana
    [2] => 29
)
cli.php | cli.php | cli.php
(sem REQUEST_METHOD)
cli

PHP_SAPI (uma constante, não uma superglobal) diz onde o PHP está rodando: cli no terminal, cli-server no php -S, apache2handler ou fpm-fcgi em servidores.

Quando dá errado: PHP_SELF no action do formulário

Um atalho antigo e perigoso, comum em tutoriais:

<form action="<?= $_SERVER["PHP_SELF"] ?>" method="post">

Como PHP_SELF inclui o PATH_INFO, que o visitante escreve na URL, alguém pode montar um link que injeta HTML na sua página:

$ curl -s "localhost:8000/self.php/%22%3E%3Cscript%3Ealert(1)%3C/script%3E"
<form action="/self.php/"><script>alert(1)</script>" method="post">

O <script> entrou no HTML: é um ataque XSS (aula). Como consertar: para enviar o formulário à própria página, deixe o action vazio (action="") ou omita-o; se precisar do valor, escape: htmlspecialchars($_SERVER["PHP_SELF"]).


$_FILES

Os arquivos enviados por um formulário com method="post" e enctype="multipart/form-data". Cada campo <input type="file" name="foto"> vira $_FILES["foto"], um array com 6 chaves. O -F do curl envia como esse formulário:

$ curl -s -F "foto=@produtos.csv" -F "titulo=Lista" localhost:8000/upload.php
Array
(
    [foto] => Array
        (
            [name] => produtos.csv
            [full_path] => produtos.csv
            [type] => application/octet-stream
            [tmp_name] => /tmp/php3tfhotbf0m9h7eJjneJ
            [error] => 0
            [size] => 23
        )

)

(O upload.php só faz print_r($_FILES). O campo de texto titulo foi para $_POST, não para $_FILES.)

ChaveExemplo realO que é
nameprodutos.csvO nome do arquivo no computador do visitante. Não use para salvar sem limpar: pode ter ../ ou nomes repetidos.
full_path PHP 8.1+produtos.csvO caminho relativo, quando o visitante envia uma pasta inteira.
typeapplication/octet-stream (para um .txt: text/plain)O tipo que o navegador disse. Não confie: confira o conteúdo com finfo/mime_content_type.
tmp_name/tmp/php3tfhotbf0m9h7eJjneJOnde o PHP guardou o arquivo, temporariamente. Ele é apagado no fim da requisição: mova com move_uploaded_file.
error0O código de erro (tabela abaixo). Confira antes de tudo.
size23Tamanho em bytes.

Códigos de error

ConstanteValorQuando
UPLOAD_ERR_OK0Deu certo.
UPLOAD_ERR_INI_SIZE1Maior que upload_max_filesize do php.ini (padrão: 2M). Conferido: um arquivo de 3 MB chegou com [error] => 1, [size] => 0 e tmp_name vazio.
UPLOAD_ERR_FORM_SIZE2Maior que o campo oculto MAX_FILE_SIZE do formulário.
UPLOAD_ERR_PARTIAL3Chegou só um pedaço (conexão caiu).
UPLOAD_ERR_NO_FILE4O formulário foi enviado sem escolher arquivo (todas as outras chaves vêm vazias).
UPLOAD_ERR_NO_TMP_DIR6Falta a pasta temporária no servidor.
UPLOAD_ERR_CANT_WRITE7Não conseguiu gravar no disco.
UPLOAD_ERR_EXTENSION8Uma extensão do PHP interrompeu o envio.

Outros limites do php.ini (valores padrão, conferidos com ini_get): post_max_size = 8M (se o pedido inteiro passar disso, $_POST e $_FILES chegam vazios, com o aviso Warning: PHP Request Startup: POST Content-Length of 9000312 bytes exceeds the limit of 8388608 bytes in Unknown on line 0, conferido com um envio de 9 MB) e max_file_uploads = 20.

Vários arquivos

Com name="fotos[]" (e multiple), cada chave vira um array, na mesma ordem: $_FILES["fotos"]["name"][0], $_FILES["fotos"]["size"][0]…

<?php
header("Content-Type: text/plain; charset=utf-8");
foreach ($_FILES["fotos"]["name"] as $i => $nome) {
    echo $i, ": ", $nome, " (", $_FILES["fotos"]["size"][$i], " bytes, erro ", $_FILES["fotos"]["error"][$i], ")\n";
}
$ curl -s -F "fotos[]=@produtos.csv" -F "fotos[]=@notas.txt" localhost:8000/upload2.php
0: produtos.csv (23 bytes, erro 0)
1: notas.txt (8 bytes, erro 0)

O passo a passo completo (validar tipo e tamanho, gerar um nome seguro, move_uploaded_file) está na aula Upload de Arquivos.


Os cookies que o navegador mandou neste pedido (veja o exemplo com -b "tema=escuro" em $_POST). Pontos que confundem:

SituaçãoO que acontece
setcookie("tema", "escuro") e logo depois echo $_COOKIE["tema"]Não aparece ainda: setcookie só pede ao navegador para guardar; ele manda de volta no próximo pedido.
setcookie depois de um echoWarning: Cannot modify header information - headers already sent (mensagem). Chame antes de qualquer saída.
Apagar um cookiesetcookie("tema", "", time() - 3600) (data no passado).
Confiar no valorNunca: o visitante pode editar seus cookies. Para dados importantes (quem está logado), use $_SESSION.

Aula: Cookies.

$_SESSION

Dados que ficam no servidor, ligados ao visitante por um cookie (PHPSESSID) com um código aleatório. Só existe depois de session_start(), que precisa vir antes de qualquer saída.

<?php
session_start();
$_SESSION["visitas"] = ($_SESSION["visitas"] ?? 0) + 1;
header("Content-Type: text/plain; charset=utf-8");
echo "Visitas: ", $_SESSION["visitas"], "\n";
print_r($_SESSION);

No primeiro pedido, o PHP cria a sessão e manda o cookie (-i mostra os cabeçalhos):

$ curl -s -i localhost:8000/sessao.php
HTTP/1.1 200 OK
Host: localhost:8000
Date: Fri, 16 Oct 2026 23:51:41 GMT
Connection: close
X-Powered-By: PHP/8.5.8
Set-Cookie: PHPSESSID=4eb6a76472acd99cab32775c3082d5e2; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/plain; charset=utf-8

Visitas: 1
Array
(
    [visitas] => 1
)

Quem devolve o cookie, como um navegador (-c guarda os cookies num arquivo, -b os manda de volta), continua a mesma sessão:

$ curl -s -c /tmp/c.txt -b /tmp/c.txt localhost:8000/sessao.php
Visitas: 1
Array
(
    [visitas] => 1
)
$ curl -s -c /tmp/c.txt -b /tmp/c.txt localhost:8000/sessao.php
Visitas: 2
Array
(
    [visitas] => 2
)
OperaçãoCódigo
Iniciar (no topo de toda página que usa sessão)session_start();
Guardar / ler$_SESSION["usuario"] = "ana"; / $_SESSION["usuario"] ?? null
Apagar um itemunset($_SESSION["carrinho"]);
Trocar o código depois do login (contra roubo de sessão)session_regenerate_id(true);
Sair (logout)$_SESSION = []; session_destroy();

Aulas: Sessões e o projeto Login com Sessão.


$_ENV

As variáveis de ambiente do sistema (como HOME). Se ela vem preenchida depende da opção variables_order do php.ini: só com a letra E. Sem php.ini o padrão é EGPCS (preenchida); os php.ini recomendados (php.ini-development e -production, usados pelas distribuições Linux) trazem GPCS (vazia):

<?php var_dump(count($_ENV)); var_dump(getenv("HOME"));
$ php env2.php
int(14)
string(9) "/home/ana"
$ php -d variables_order=GPCS env2.php
int(0)
string(9) "/home/ana"

Use getenv("NOME"): funciona nos dois casos. Variáveis de ambiente são o lugar certo para senhas de banco e chaves de API (fora do código).

$_REQUEST

Uma mistura de $_GET, $_POST e, conforme o php.ini, $_COOKIE. Quando a mesma chave existe em mais de um, vence o que vem depois na ordem (request_order, ou variables_order se ela estiver vazia): no exemplo de $_POST, o nome do POST apagou o do GET, e o cookie tema entrou junto. Nos php.ini recomendados, request_order = "GP" (sem cookies).

Evite. Você não sabe de onde o valor veio, e um link (GET) pode se passar por um formulário (POST). Use $_GET ou $_POST, explicitamente.

$GLOBALS

Um array com todas as variáveis globais do script (as criadas fora de funções), acessível de dentro das funções:

<?php
$total = 10;
function somar5() {
    $GLOBALS["total"] += 5;
}
somar5();
echo $total, "\n";
$ php g3.php
15

Desde o PHP 8.1 não dá para substituir o array inteiro:

$ cat g2.php
<?php
$GLOBALS = [];
$ php g2.php

Fatal error: $GLOBALS can only be modified using the $GLOBALS[$name] = $value syntax in /home/ana/sg/g2.php on line 2
Stack trace:
#0 {main}

Funções que mexem em variáveis globais são difíceis de entender e testar. Prefira passar valores por parâmetro e devolver com return (Escopo, Funções).


Missão: um pedido pelo formulário

Missão: a caixa já começa enviando o formulário (POST) com produto=Caneta e quantidade=3. Dentro do if, leia os dois campos de $_POST (com ??), converta a quantidade com (int), confira se o produto existe em $precos e mostre Total: R$ 7,50 (use number_format($total, 2, ",", ".")). Produto desconhecido: Pedido inválido.

<?php
$precos = ["Caneta" => 2.50, "Caderno" => 15.90];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    // leia "produto" e "quantidade" de $_POST,
    // calcule o total e mostre "Total: R$ 7,50"
}
?>
<form method="post">
  <select name="produto"><option>Caneta</option><option>Caderno</option></select>
  <input name="quantidade" type="number" value="1">
  <button>Calcular</button>
</form>
<?php
$precos = ["Caneta" => 2.50, "Caderno" => 15.90];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $produto = $_POST["produto"] ?? "";
    $qtd = (int) ($_POST["quantidade"] ?? 0);
    if (isset($precos[$produto]) && $qtd > 0) {
        $total = $precos[$produto] * $qtd;
        echo "<p>" . htmlspecialchars($produto) . " x $qtd</p>";
        echo "<p>Total: R$ " . number_format($total, 2, ",", ".") . "</p>";
    } else {
        echo "<p>Pedido inválido.</p>";
    }
}
?>
<form method="post">
  <select name="produto"><option>Caneta</option><option>Caderno</option></select>
  <input name="quantidade" type="number" value="1">
  <button>Calcular</button>
</form>

A solução, conferida no laboratório:

$ curl -s -d "produto=Caneta&quantidade=3" localhost:8000/pedido-sol.php
<p>Caneta x 3</p><p>Total: R$ 7,50</p><form method="post">
  <select name="produto"><option>Caneta</option><option>Caderno</option></select>
  <input name="quantidade" type="number" value="1">
  <button>Calcular</button>
</form>
$ curl -s -d "produto=Lápis&quantidade=3" localhost:8000/pedido-sol.php | head -1
<p>Pedido inválido.</p><form method="post">

Exercícios

1. Na URL /loja.php?id=7, qual é o tipo de $_GET["id"]?

2. Um cliente envia JSON no corpo de um POST. Onde estão os dados?

3. $_FILES["foto"]["error"] vale 4. O que houve?

4. Por que action="<?= $_SERVER["PHP_SELF"] ?>" é perigoso?

Complete para saber se o formulário foi enviado:

if ($_SERVER[""] === "POST") { … }

Complete para ler uma variável de ambiente de um jeito que funciona com qualquer variables_order:

$senha = ("DB_SENHA");

Resumo

  • $_GET (URL), $_POST (corpo do formulário), $_COOKIE, $_FILES: vêm do visitante. Leia com ??, valide, escape com htmlspecialchars.
  • Valores de $_GET/$_POST são sempre texto (ou array, com [] no nome).
  • $_SERVER["REQUEST_METHOD"], ["REQUEST_URI"], ["HTTP_…"] são as chaves mais usadas; no terminal, ["argv"].
  • $_FILES["campo"]: name, full_path, type, tmp_name, error, size. Confira error === UPLOAD_ERR_OK.
  • $_SESSION fica no servidor (depois de session_start()); $_COOKIE fica no navegador.
  • Prefira getenv a $_ENV; evite $_REQUEST e $GLOBALS.