PHP $_SERVER e Outras Superglobais

Você já usou três variáveis "mágicas" que existem sozinhas em qualquer programa PHP: $_GET, $_POST e $_FILES. Elas fazem parte de uma família de nove, as superglobais. Esta aula apresenta a mais curiosa delas, $_SERVER, que conta tudo sobre o pedido que chegou (quem pediu, qual endereço, com qual navegador), e fecha com o mapa da família inteira.

Você vai aprender
  • O que é uma superglobal e por que ela funciona dentro de funções.
  • O que tem dentro de $_SERVER, com a saída real do php -S.
  • As chaves mais úteis: REQUEST_METHOD, REQUEST_URI, QUERY_STRING, SCRIPT_NAME, HTTP_HOST, HTTP_USER_AGENT, REMOTE_ADDR.
  • Quais delas o visitante pode falsificar (e o caso do PHP_SELF).
  • Como $_SERVER muda no terminal (php arquivo.php).
  • $_ENV, getenv e $GLOBALS.
  • A tabela com todas as superglobais, e onde cada uma é estudada.

A ideia: o carimbo do correio

Quando uma carta chega, além do conteúdo, o envelope traz informações: o carimbo com a data, a cidade de origem, o endereço de destino, às vezes o remetente. Ninguém escreveu isso na carta; vem "de brinde" com a entrega.

$_GET e $_POST são o conteúdo da carta. $_SERVER é o envelope: o PHP anota nele como o pedido chegou e onde ele está rodando. E, como num envelope, parte dessas anotações foi escrita pelo correio (o servidor, confiável) e parte pelo remetente (o navegador, que pode escrever o que quiser).

O que é uma superglobal

Normalmente, uma variável criada fora de uma função não existe dentro dela (veja Escopo). As superglobais são a exceção: o PHP as cria antes do seu código rodar e elas são visíveis em todo lugar, sem global:

function foiEnviado(): bool
{
    return $_SERVER['REQUEST_METHOD'] === 'POST';   // funciona: superglobal
}

Todas começam com $_ e são escritas em maiúsculas (a única exceção é $GLOBALS, sem o sublinhado). Você não pode criar novas superglobais: a família é fixa.


$_SERVER por dentro

A forma mais rápida de conhecer $_SERVER é imprimir tudo. A Ana criou um server.php e abriu no servidor embutido (O Servidor Embutido):

<?php
print_r($_SERVER);
$ curl -s localhost:8000/server.php
Array
(
    [DOCUMENT_ROOT] => /home/ana/site
    [REMOTE_ADDR] => 127.0.0.1
    [REMOTE_PORT] => 37966
    [SERVER_SOFTWARE] => PHP/8.5.8 (Development Server)
    [SERVER_PROTOCOL] => HTTP/1.1
    [SERVER_NAME] => localhost
    [SERVER_PORT] => 8000
    [REQUEST_URI] => /server.php
    [REQUEST_METHOD] => GET
    [SCRIPT_NAME] => /server.php
    [SCRIPT_FILENAME] => /home/ana/site/server.php
    [PHP_SELF] => /server.php
    [HTTP_HOST] => localhost:8000
    [HTTP_USER_AGENT] => curl/8.18.0
    [HTTP_ACCEPT] => */*
    [REQUEST_TIME_FLOAT] => 1792195356.5285
    [REQUEST_TIME] => 1792195356
)

As chaves se dividem em três grupos:

  • Sobre o pedido: REQUEST_METHOD, REQUEST_URI, QUERY_STRING, REQUEST_TIME.
  • Cabeçalhos que o navegador mandou: todas as que começam com HTTP_. Cada cabeçalho HTTP vira uma chave: User-Agent → HTTP_USER_AGENT, Accept-Language → HTTP_ACCEPT_LANGUAGE (maiúsculas, hífen vira sublinhado, HTTP_ na frente).
  • Sobre o servidor e o arquivo: SERVER_NAME, SERVER_PORT, DOCUMENT_ROOT, SCRIPT_FILENAME.

Num Apache ou Nginx, a lista é bem maior (e algumas chaves mudam). O que vale para todos: as chaves abaixo.

As chaves mais úteis

Para ver cada uma com calma, a Ana pediu a página loja/produto.php?id=7&cor=azul como se fosse um Firefox, vindo de um link na página /loja/. O -A do curl define o User-Agent, e o -H acrescenta outros cabeçalhos:

<?php
$chaves = ['REQUEST_METHOD', 'REQUEST_URI', 'QUERY_STRING', 'SCRIPT_NAME', 'PHP_SELF',
           'HTTP_HOST', 'HTTP_USER_AGENT', 'HTTP_ACCEPT_LANGUAGE', 'HTTP_REFERER',
           'REMOTE_ADDR', 'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL', 'DOCUMENT_ROOT', 'SCRIPT_FILENAME', 'HTTPS'];
foreach ($chaves as $c) {
    echo str_pad($c, 22), '= ', $_SERVER[$c] ?? '(não existe)', "\n";
}
$ curl -s "localhost:8000/loja/produto.php?id=7&cor=azul" -H "Accept-Language: pt-BR,pt;q=0.9" -H "Referer: http://localhost:8000/loja/" -A "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
REQUEST_METHOD        = GET
REQUEST_URI           = /loja/produto.php?id=7&cor=azul
QUERY_STRING          = id=7&cor=azul
SCRIPT_NAME           = /loja/produto.php
PHP_SELF              = /loja/produto.php
HTTP_HOST             = localhost:8000
HTTP_USER_AGENT       = Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
HTTP_ACCEPT_LANGUAGE  = pt-BR,pt;q=0.9
HTTP_REFERER          = http://localhost:8000/loja/
REMOTE_ADDR           = 127.0.0.1
SERVER_NAME           = localhost
SERVER_PORT           = 8000
SERVER_PROTOCOL       = HTTP/1.1
DOCUMENT_ROOT         = /home/ana/site
SCRIPT_FILENAME       = /home/ana/site/loja/produto.php
HTTPS                 = (não existe)

E a mesma página pedida pelo curl "puro", sem endereço com dados nem cabeçalhos extras. Repare nas chaves que desaparecem:

$ curl -s localhost:8000/loja/produto.php
REQUEST_METHOD        = GET
REQUEST_URI           = /loja/produto.php
QUERY_STRING          = (não existe)
…
HTTP_USER_AGENT       = curl/8.18.0
HTTP_ACCEPT_LANGUAGE  = (não existe)
HTTP_REFERER          = (não existe)
…
ChaveExemploO que éQuem escreve
REQUEST_METHODGETO método do pedido (veja $_GET e $_POST)Servidor
REQUEST_URI/loja/produto.php?id=7&cor=azulO endereço pedido, a partir da /, com a query stringVisitante (o endereço que ele digitou)
QUERY_STRINGid=7&cor=azulSó a parte depois do ?. Não existe quando não há ?Visitante
SCRIPT_NAME/loja/produto.phpO caminho do arquivo PHP no siteServidor
PHP_SELF/loja/produto.phpParecido com SCRIPT_NAME, mas pode incluir texto extra do endereço (veja o cenário de erro)Visitante, em parte
HTTP_HOSTlocalhost:8000O nome do site que o navegador pediuVisitante
HTTP_USER_AGENTMozilla/5.0 … Firefox/140.0"Agente do usuário": o programa que fez o pedido (navegador, robô, curl)Visitante
HTTP_ACCEPT_LANGUAGEpt-BR,pt;q=0.9Os idiomas preferidos do navegadorVisitante
HTTP_REFERERhttp://localhost:8000/loja/A página de onde a pessoa veio (pelo link). Muitas vezes não vemVisitante
REMOTE_ADDR127.0.0.1O endereço IP de quem fez o pedido (127.0.0.1 = a própria máquina)Servidor (a conexão)
SERVER_NAME, SERVER_PORTlocalhost, 8000Nome e porta do servidorServidor
DOCUMENT_ROOT/home/ana/siteA pasta raiz do site no discoServidor
SCRIPT_FILENAME/home/ana/site/loja/produto.phpO caminho completo do arquivo no discoServidor
HTTPS(não existe)Existe (com um valor diferente de off) quando a conexão é segura (https://). O php -S não tem HTTPSServidor
REQUEST_TIME1792195356A hora do pedido, em segundos desde 1970 (veja Datas e Horas)Servidor
Tudo o que começa com HTTP_ foi escrito pelo navegador e pode ser qualquer coisa: o curl acima "virou" um Firefox com uma opção. Use esses valores para estatísticas e conveniências (escolher o idioma da página), nunca para segurança ("só deixa entrar se vier do Chrome"). E, ao mostrar qualquer um deles na página, htmlspecialchars (Escapar a Saída). Até o REMOTE_ADDR, que é confiável, pode ser o endereço de um proxy no meio do caminho, e não da pessoa.

O $_SERVER do editor do guia. Aqui o "servidor" é o PHP rodando dentro do seu navegador, então alguns valores são diferentes dos do php -S, mas o HTTP_USER_AGENT é o do seu navegador. Rode, envie o formulário (GET e depois POST) e veja REQUEST_METHOD, REQUEST_URI e QUERY_STRING mudarem. Troque a lista para print_r($_SERVER) e veja tudo.

<form method="get"><input name="busca" value="torta"> <button>GET</button></form>
<form method="post"><input name="busca" value="pudim"> <button>POST</button></form>
<pre>
<?php
foreach (['REQUEST_METHOD', 'REQUEST_URI', 'QUERY_STRING', 'SCRIPT_NAME',
          'HTTP_HOST', 'HTTP_USER_AGENT', 'REMOTE_ADDR'] as $c) {
    echo str_pad($c, 16), '= ', htmlspecialchars($_SERVER[$c] ?? '(não existe)'), "\n";
}
?>
</pre>

Usos comuns

Para…Use
Saber se um formulário POST foi enviado$_SERVER['REQUEST_METHOD'] === 'POST'
Montar o link da página atual$_SERVER['REQUEST_URI'] (escapado com htmlspecialchars)
Destacar o item atual de um menu$_SERVER['SCRIPT_NAME'] === '/contato.php'
Registrar quem acessou (um log)REMOTE_ADDR, HTTP_USER_AGENT, REQUEST_TIME
Saber se está em HTTPS($_SERVER['HTTPS'] ?? 'off') !== 'off'
Caminhos de arquivos no discoPrefira __DIR__ (a pasta do arquivo atual) a DOCUMENT_ROOT

$_SERVER no terminal

Quando o PHP roda no terminal (php arquivo.php, veja PHP na Linha de Comando), não existe pedido HTTP. Então não há REQUEST_METHOD nem cabeçalhos, mas aparecem os argumentos (argv, argc) e as variáveis de ambiente do sistema:

<?php
echo $_SERVER['argv'][1] ?? '-', ' ', $_SERVER['argc'], ' ', $_SERVER['SCRIPT_NAME'], ' ', $_SERVER['REQUEST_METHOD'] ?? '(sem REQUEST_METHOD)', "\n";
$ php cli.php Ana
Ana 2 cli.php (sem REQUEST_METHOD)

Por isso, num código que pode rodar dos dois jeitos, leia sempre com ?? ….


$_ENV e getenv: variáveis de ambiente

O sistema operacional guarda variáveis de ambiente: configurações como HOME (a pasta pessoal), LANG (o idioma) e, em servidores, coisas como a senha do banco de dados, para não ficarem escritas no código. O PHP pode lê-las de dois jeitos:

<?php
var_dump(count($_ENV), getenv('HOME'));
$ php env.php
int(15)
string(9) "/home/ana"

No laboratório da Ana, $_ENV veio cheio porque o PHP está sem php.ini. Nos php.ini que acompanham o PHP (e na maioria dos servidores), a configuração variables_order é "GPCS", sem o E de Environment, e $_ENV fica vazio. Por isso a forma mais segura é getenv('NOME'), que funciona nos dois casos e devolve false se a variável não existir.


$GLOBALS: todas as variáveis globais

$GLOBALS é um array com todas as variáveis criadas fora de funções. Dentro de uma função, ele dá acesso a elas pelo nome:

<?php
$loja = 'Padaria da Ana';
function nomeDaLoja(): string
{
    return $GLOBALS['loja'];
}
echo nomeDaLoja(), "\n";
$ php globals.php
Padaria da Ana

Funciona, mas cria a mesma bagunça do global (veja Escopo): a função depende de uma variável que pode ser mudada em qualquer lugar. Prefira passar o valor como parâmetro. Desde o PHP 8.1, $GLOBALS não pode mais ser substituído inteiro:

<?php
$GLOBALS = [];
$ php globals2.php

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

O mapa das superglobais

SuperglobalO que guardaDe onde vemAula
$_GETDados do endereço (?nome=Ana)Visitante$_GET e $_POST
$_POSTDados do corpo de um POSTVisitante$_GET e $_POST
$_FILESArquivos enviadosVisitanteUpload de Arquivos
$_COOKIECookies que o navegador devolveuVisitanteCookies
$_SESSIONDados da sessão, guardados no servidor (existe depois de session_start())Seu programaSessões
$_REQUESTMistura de GET, POST e (talvez) cookiesVisitante$_GET e $_POST (evite)
$_SERVERInformações do pedido e do servidorServidor + cabeçalhos do visitanteesta
$_ENVVariáveis de ambiente (muitas vezes vazio; use getenv)Sistemaesta
$GLOBALSTodas as variáveis globais do programaSeu programaesta e Escopo

Repare na coluna "De onde vem": tudo o que vem do visitante passa por validação na entrada e escape na saída (Validar Dados, Escapar a Saída).


Quando dá errado

XSS pelo PHP_SELF
<form action="<?= $_SERVER['PHP_SELF'] ?>" method="post">
$ curl -s "localhost:8000/self.php/%22%3E%3Cb%20style=%22color:red%22%3Ehackeado%3C/b%3E"
<form action="/self.php/"><b style="color:red">hackeado</b>" method="post">

O que aconteceu: o servidor aceita texto extra depois do nome do arquivo (/self.php/qualquer-coisa ainda roda o self.php), e esse texto entra no PHP_SELF. A aspa fechou o action e o resto virou HTML. Esse código aparece em muitos tutoriais antigos.

Como resolver: para enviar à própria página, deixe o action de fora (Formulários HTML). Se precisar do caminho, use htmlspecialchars($_SERVER['SCRIPT_NAME']).

Warning: Undefined array key "HTTP_REFERER"
$ curl -s localhost:8000/ref.php
<br />
<b>Warning</b>:  Undefined array key "HTTP_REFERER" in <b>/home/ana/site/ref.php</b> on line <b>2</b><br />

O que aconteceu: chaves HTTP_* só existem se o navegador mandou aquele cabeçalho. O Referer não vem quando a pessoa digita o endereço, abre um favorito ou quando o navegador o esconde por privacidade. O mesmo vale para QUERY_STRING (sem ?) e REQUEST_METHOD (no terminal).

Como resolver: $_SERVER['HTTP_REFERER'] ?? ''.

Warning: Undefined global variable $_SESSION
<?php
echo $_SESSION["usuario"];
$ curl -s localhost:8000/sess.php
<br />
<b>Warning</b>:  Undefined global variable $_SESSION in <b>/home/ana/site/sess.php</b> on line <b>2</b><br />
<br />
<b>Warning</b>:  Trying to access array offset on null in <b>/home/ana/site/sess.php</b> on line <b>2</b><br />

O que aconteceu: $_SESSION só passa a existir depois de session_start(). Sem ela, a variável não existe (primeiro aviso) e ler ["usuario"] de "nada" (null) gera o segundo.

Como resolver: chame session_start() no começo do arquivo, como em Sessões.

O código funciona no laboratório, mas $_ENV vem vazio no servidor

O que aconteceu: o variables_order do servidor não inclui o E.

Como resolver: troque $_ENV['X'] por getenv('X').


Exercícios

1. O navegador

Complete a chave que guarda o programa que fez o pedido:

$_SERVER['']

2. O endereço pedido

Para o pedido /blog.php?post=3, qual chave vale /blog.php?post=3?

$_SERVER['']

3. Variável de ambiente

Complete com a função que lê a variável de ambiente HOME mesmo quando $_ENV está vazio:

('HOME')

4. Qual destas chaves não pode ser escolhida pelo visitante?

5. Qual superglobal só existe depois de chamar uma função?

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

Missão: a página foi aberta com ?pagina=2. Complete o rodapé para mostrar Método: seguido do REQUEST_METHOD e Você pediu: seguido do REQUEST_URI, os dois com htmlspecialchars.

<h1>Receitas</h1>
<p>Página de receitas da Ana.</p>
<footer>
<?php
// mostre aqui o método e o endereço pedido
?>
</footer>
<h1>Receitas</h1>
<p>Página de receitas da Ana.</p>
<footer>
<?php
echo "<p>Método: " . htmlspecialchars($_SERVER['REQUEST_METHOD']) . "</p>";
echo "<p>Você pediu: " . htmlspecialchars($_SERVER['REQUEST_URI']) . "</p>";
?>
</footer>

Missão: mostre Seu navegador: seguido do HTTP_USER_AGENT. Lembre: esse cabeçalho pode não vir (use ?? 'desconhecido') e foi escrito pelo visitante (use htmlspecialchars).

<?php
// seu código aqui
<?php
$agente = $_SERVER['HTTP_USER_AGENT'] ?? 'desconhecido';
echo "<p>Seu navegador: " . htmlspecialchars($agente) . "</p>";

Resumo

  • Superglobais existem em todo lugar, inclusive em funções. São nove: $_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, $_REQUEST, $_SERVER, $_ENV, $GLOBALS.
  • $_SERVER descreve o pedido e o servidor: REQUEST_METHOD, REQUEST_URI, QUERY_STRING, SCRIPT_NAME, REMOTE_ADDR…
  • Cada cabeçalho HTTP vira HTTP_NOME; todos podem ser falsificados e podem não existir: ?? '' e htmlspecialchars.
  • Não use PHP_SELF sem escape; para enviar à própria página, omita o action.
  • No terminal, $_SERVER não tem dados de pedido, mas tem argv/argc.
  • Para variáveis de ambiente, getenv. $GLOBALS existe, mas prefira parâmetros.