PHP Sessões
Na aula de Cookies, você viu que dá para o site "lembrar" de um visitante, mas que o cookie fica no computador dele, onde pode ser lido e mudado. E se você precisar lembrar de algo que o visitante não pode mexer, como "esta pessoa fez login como Ana"? A resposta é a sessão: os dados ficam guardados no servidor, e o navegador leva só um número de identificação. É assim que funcionam logins, carrinhos de compra e mensagens do tipo "cadastro salvo!" na página seguinte.
- O que é uma sessão e como ela usa um cookie (
PHPSESSID) só para o número. - Como começar uma sessão com
session_starte guardar dados em$_SESSION. - Onde os dados ficam de verdade (o arquivo da sessão no servidor).
- Um contador de visitas e um carrinho de compras.
- A ideia de um login com sessão (o login completo está em Login com Sessão).
- Como sair (logout) com
session_destroy. - Por que usar
session_regenerate_idno login. - Quando usar sessão e quando usar cookie.
A ideia: o guarda-volumes
No guarda-volumes de um museu, você entrega a mochila e recebe uma ficha numerada. A mochila fica guardada lá dentro; você leva só a ficha. Na saída, mostra a ficha e recebe a mochila de volta. Se perder a ficha, perdeu o acesso. E ninguém consegue mexer na sua mochila mudando o número escrito na ficha: no máximo, pega uma ficha que não existe.
NAVEGADOR SERVIDOR
(leva a ficha) (guarda os dados)
cookie PHPSESSID=aede68e6… ─────▶ arquivo sess_aede68e6…
┌──────────────────────┐
│ usuario = "ana" │
│ carrinho = [bolo,…] │
│ visitas = 3 │
└──────────────────────┘
PHPSESSID (a ficha) + um arquivo no servidor com os dados (a mochila). O PHP liga um ao outro sozinho.Você não precisa criar o cookie nem o arquivo: o PHP faz tudo quando você chama session_start(). Para o seu código, a sessão é só um array, $_SESSION, que "sobrevive" de um pedido para o outro.
Começando: session_start e $_SESSION
<?php session_start(); $_SESSION['visitas'] = ($_SESSION['visitas'] ?? 0) + 1; echo "Visitas nesta sessão: ", $_SESSION['visitas'], "\n"; echo "ID da sessão: ", session_id(), "\n";
- session_start();
- "Começar sessão". Se o navegador mandou um cookie
PHPSESSIDválido, o PHP abre o arquivo dessa sessão e carrega os dados em$_SESSION. Se não mandou, cria um número novo e uma sessão vazia. Vem antes de qualquer saída, como osetcookie. - $_SESSION['visitas']
- Um array comum: leia e grave chaves como em qualquer array. Ao final da página, o PHP salva o conteúdo no arquivo da sessão sozinho.
- ?? 0
- Na primeira visita, a chave não existe (veja Undefined array key).
- session_id()
- O número da ficha desta sessão. Aqui só para você ver; normalmente não precisa dele.
Primeiro, a resposta completa, com os cabeçalhos (-i):
$ curl -si localhost:8000/contador.php HTTP/1.1 200 OK Host: localhost:8000 Date: Sat, 17 Oct 2026 00:08:20 GMT Connection: close X-Powered-By: PHP/8.5.8 Set-Cookie: PHPSESSID=eb569c35e6f72d1c2ed1815e8328aa2e; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Content-type: text/html; charset=UTF-8 Visitas nesta sessão: 1 ID da sessão: eb569c35e6f72d1c2ed1815e8328aa2e
O session_start mandou quatro cabeçalhos:
Set-Cookie: PHPSESSID=…; path=/: a ficha.PHPSESSIDé o nome padrão do cookie de sessão; o valor é um número aleatório de 32 caracteres. Sem data de expiração: some ao fechar o navegador.Expires(numa data de 1981, no passado),Cache-ControlePragma: pedem ao navegador para não guardar a página em cache, porque ela é pessoal e muda a cada visita.
Agora três visitas seguidas, com o "pote de cookies" do curl (-c grava, -b envia, como em Cookies):
$ curl -s -c pote -b pote localhost:8000/contador.php Visitas nesta sessão: 1 ID da sessão: aede68e6cef190d7d5c62fa0d67ebfb4 $ curl -s -c pote -b pote localhost:8000/contador.php Visitas nesta sessão: 2 ID da sessão: aede68e6cef190d7d5c62fa0d67ebfb4 $ curl -s -c pote -b pote localhost:8000/contador.php Visitas nesta sessão: 3 ID da sessão: aede68e6cef190d7d5c62fa0d67ebfb4 $ tail -1 pote localhost FALSE / FALSE 0 PHPSESSID aede68e6cef190d7d5c62fa0d67ebfb4
O mesmo número nas três visitas: o navegador (o curl) devolveu a ficha, e o PHP achou a sessão certa. Repare que o cookie guarda só o número. O 3 não está em lugar nenhum do lado da Ana. Onde está, então?
Onde os dados ficam
Por padrão, o PHP grava cada sessão num arquivo na pasta temporária do servidor, chamado sess_ + o número:
$ ls -l /tmp/sess_* -rw------- 1 ana ana 12 out 16 21:08 /tmp/sess_aede68e6cef190d7d5c62fa0d67ebfb4 -rw------- 1 ana ana 12 out 16 21:08 /tmp/sess_eb569c35e6f72d1c2ed1815e8328aa2e $ cat /tmp/sess_aede68e6cef190d7d5c62fa0d67ebfb4 visitas|i:3;
Dois arquivos: um para cada "visitante" (o primeiro curl -si, sem pote, ganhou uma sessão só para ele). O conteúdo visitas|i:3; é o $_SESSION num formato do PHP: a chave visitas, um inteiro (i) de valor 3. Você nunca precisa ler esse arquivo; é só para ver que os dados estão no servidor. (A pasta é definida por session.save_path no php.ini; vazia, usa a pasta temporária do sistema. Em sites grandes, as sessões costumam ficar num banco de dados ou no Redis.)
O contador de visitas com sessão. Rode e clique em Visitar de novo algumas vezes: o editor guarda o cookie PHPSESSID entre os pedidos desta caixa, como um navegador. Veja o $_SESSION e o $_COOKIE embaixo: o número de visitas só aparece no primeiro; no segundo, só a ficha.
<?php session_start(); $_SESSION['visitas'] = ($_SESSION['visitas'] ?? 0) + 1; ?> <p>Visitas nesta sessão: <?= $_SESSION['visitas'] ?></p> <form><button>Visitar de novo</button></form> <h3>$_SESSION (no servidor)</h3> <pre><?php print_r($_SESSION); ?></pre> <h3>$_COOKIE (o que o navegador mandou)</h3> <pre><?php print_r($_COOKIE); ?></pre>
Guardar de tudo: arrays na sessão
$_SESSION aceita qualquer valor: textos, números, booleanos e arrays. Isso permite, por exemplo, um carrinho de compras:
<?php
session_start();
$_SESSION['carrinho'] ??= []; // cria o carrinho vazio na primeira vez
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['carrinho'][] = $_POST['produto'] ?? '';
}
?>
<p>Itens no carrinho: <?= count($_SESSION['carrinho']) ?></p>
<ul>
<?php foreach ($_SESSION['carrinho'] as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
- $_SESSION['carrinho'] ??= [];
- O operador
??=(de Operadores de Atribuição) só atribui se a chave ainda não existir: na primeira visita, o carrinho vira um array vazio; nas outras, fica como está. - $_SESSION['carrinho'][] = …
- Acrescenta um item no fim da lista, que fica salva na sessão.
| Operação | Código |
|---|---|
| Gravar | $_SESSION['chave'] = $valor; |
| Ler (com padrão) | $_SESSION['chave'] ?? 'padrão' |
| Existe? | isset($_SESSION['chave']) |
| Apagar uma chave | unset($_SESSION['chave']); |
| Esvaziar tudo | $_SESSION = []; |
Um uso muito comum: a mensagem de uma vez só (chamada de flash). A página que salva um cadastro grava $_SESSION['aviso'] = 'Cadastro salvo!' e redireciona; a página seguinte mostra o aviso e faz unset($_SESSION['aviso']), para ele não aparecer de novo. Você monta isso em header e Redirecionamento.
A ideia de um login
Agora o motivo principal das sessões. Um login é, no fundo, isto:
- A pessoa envia usuário e senha.
- O PHP confere. Se estiver certo, grava na sessão quem é:
$_SESSION['usuario'] = 'ana'. - Toda página restrita pergunta: "
$_SESSION['usuario']existe?". Se não, nega o acesso.
Como a sessão está no servidor, a pessoa não consegue "se promover" mudando um cookie: o máximo que ela muda é o número da ficha, e um número inventado não corresponde a nenhuma sessão.
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$usuario = $_POST['usuario'] ?? '';
$senha = $_POST['senha'] ?? '';
if ($usuario === 'ana' && $senha === 'bolo123') { // só a ideia! veja projeto-login.html
session_regenerate_id(true);
$_SESSION['usuario'] = $usuario;
echo "Login feito.\n";
} else {
echo "Usuário ou senha errados.\n";
}
}
<?php
session_start();
if (!isset($_SESSION['usuario'])) {
echo "Acesso negado: faça login.\n";
exit;
}
echo "Olá, ", htmlspecialchars($_SESSION['usuario']), "! Este é o seu painel.\n";
=== serve para entender o mecanismo, nunca para um site de verdade. Senhas são guardadas no banco de dados com password_hash e conferidas com password_verify (Senhas), e o formulário precisa de outras proteções. O login completo, do jeito certo, é o projeto Login com Sessão.Testando o caminho completo (o grep mostra só o cabeçalho Set-Cookie e a mensagem):
$ curl -s -b pote2 -c pote2 localhost:8000/painel.php Acesso negado: faça login. $ curl -s -b pote2 -c pote2 localhost:8000/contador.php | tail -1 ID da sessão: 74a8036513814e08fe4c54f2286fb699 $ curl -si -b pote2 -c pote2 localhost:8000/login.php -d "usuario=ana&senha=errada" | grep -i "set-cookie\|errad" Usuário ou senha errados. $ curl -si -b pote2 -c pote2 localhost:8000/login.php -d "usuario=ana&senha=bolo123" | grep -i "set-cookie\|login" Set-Cookie: PHPSESSID=c1218ccc8c8fac82e8b2daf847938b0b; path=/ Login feito. $ curl -s -b pote2 -c pote2 localhost:8000/painel.php Olá, ana! Este é o seu painel.
Repare: antes do login, a sessão era 74a80365…. No login certo, o PHP mandou um Set-Cookie com um número novo, c1218ccc…. Quem fez isso foi o session_regenerate_id(true).
Por que trocar o número no login: session_regenerate_id
Imagine que um atacante consiga fazer a vítima usar um número de sessão que ele conhece (por exemplo, mandando um link num computador compartilhado). Se o número continuar o mesmo depois do login, o atacante, com o mesmo número, também estará "logado como a vítima". Esse ataque se chama fixação de sessão.
A defesa: no momento em que a pessoa faz login (ou muda de nível de acesso), troque a ficha. session_regenerate_id(true) cria um número novo, mantém os dados e, com o true, apaga o arquivo da sessão antiga. O número que o atacante conhecia deixa de valer.
Sair: logout com session_destroy
Para encerrar a sessão de verdade, são três passos: esvaziar o array, apagar o cookie no navegador e destruir o arquivo no servidor:
<?php
session_start();
$_SESSION = [];
$p = session_get_cookie_params();
setcookie(session_name(), '', [
'expires' => time() - 3600,
'path' => $p['path'],
'domain' => $p['domain'],
'secure' => $p['secure'],
'httponly' => $p['httponly'],
]);
session_destroy();
echo "Você saiu.\n";
| Linha | O que faz |
|---|---|
session_start(); | Abre a sessão atual (não dá para destruir o que não foi aberto). |
$_SESSION = []; | Esvazia os dados desta página em diante. |
session_get_cookie_params() | As opções com que o cookie de sessão foi criado (path, domain…), para apagar com as mesmas (veja apagar um cookie). |
setcookie(session_name(), '', …) | Apaga o cookie PHPSESSID (session_name() devolve esse nome). |
session_destroy(); | Apaga o arquivo da sessão no servidor. |
$ curl -si -b pote2 -c pote2 localhost:8000/sair.php | grep -i "set-cookie\|saiu" Set-Cookie: PHPSESSID=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/ Você saiu. $ curl -s -b pote2 -c pote2 localhost:8000/painel.php Acesso negado: faça login.
Só session_destroy(), sem os outros passos, apaga o arquivo, mas o $_SESSION da página atual continua cheio até ela terminar, e o navegador continua com o cookie antigo. Faça os três.
Sessão × cookie
| Cookie | Sessão | |
|---|---|---|
| Onde ficam os dados | No navegador do visitante | No servidor (o navegador guarda só o número, num cookie) |
| O visitante pode ler ou mudar? | Sim | Não (só pode perder ou trocar o número) |
| Tamanho | Até ~4 KB | Sem limite prático (é um arquivo no servidor) |
| Quanto dura | O que você definir em expires (dias, meses) | Até fechar o navegador e/ou alguns minutos sem uso (session.gc_maxlifetime, 1440 segundos = 24 minutos por padrão) |
| Disponível quando | No pedido seguinte ao setcookie | No mesmo pedido, logo após gravar em $_SESSION |
| Como usar | setcookie + $_COOKIE | session_start + $_SESSION |
| Bom para | Preferências que podem durar muito: tema, idioma, "lembrar meu usuário" | Login, carrinho, mensagens de uma vez, qualquer coisa que o visitante não pode alterar |
Configurações úteis
O cookie de sessão sai, por padrão, sem httponly, secure nem samesite. Para protegê-lo como você aprendeu em Cookies, passe as opções ao session_start (ou configure no php.ini):
session_start([
'cookie_httponly' => true,
'cookie_secure' => true, // com o site em HTTPS
'cookie_samesite' => 'Lax',
'use_strict_mode' => true, // recusa números de sessão inventados
]);
Quando dá errado
session_start(): Session cannot be started after headers have already been sent
<h1>Painel</h1> <?php session_start();
$ curl -s localhost:8000/erro.php <h1>Painel</h1> <br /> <b>Warning</b>: session_start(): Session cannot be started after headers have already been sent (sent from /home/ana/site/s/erro.php on line 1) in <b>/home/ana/site/s/erro.php</b> on line <b>3</b><br />
O que aconteceu: o mesmo problema do headers already sent: o HTML da linha 1 já foi enviado, e o session_start precisa mandar o cabeçalho Set-Cookie. A sessão não começa, e $_SESSION não é carregado.
Como resolver: session_start() na primeira linha, logo depois do <?php, sem nada antes (nem espaço, nem linha vazia).
<?php $_SESSION['usuario'] = 'ana'; echo "Gravei? ";
<?php session_start(); var_dump($_SESSION);
$ curl -s -c pote3 -b pote3 localhost:8000/semstart.php
Gravei?
$ curl -s -c pote3 -b pote3 localhost:8000/lesemstart.php
array(0) {
}
O que aconteceu: o semstart.php não chamou session_start(). Sem ele, $_SESSION é só um array comum, que some no fim da página: nada foi salvo, e nenhum aviso apareceu. Outras causas: o navegador não está devolvendo o cookie (cookies bloqueados, ou páginas em domínios diferentes, como localhost e 127.0.0.1), ou a sessão expirou.
Como resolver: session_start() no topo de toda página que lê ou grava a sessão. Um arquivo comum incluído em todas (include e require) ajuda a não esquecer.
Notice: session_start(): Ignoring session_start() because a session is already active
$ curl -s localhost:8000/duas.php <br /> <b>Notice</b>: session_start(): Ignoring session_start() because a session is already active (started from /home/ana/site/s/duas.php on line 2) in <b>/home/ana/site/s/duas.php</b> on line <b>3</b><br /> ok
O que aconteceu: session_start() foi chamado duas vezes no mesmo pedido (muitas vezes, uma no arquivo principal e outra num arquivo incluído). A segunda é ignorada; a mensagem só avisa.
Como resolver: deixe um só session_start(), ou proteja com if (session_status() === PHP_SESSION_NONE) { session_start(); }.
Warning: Undefined global variable $_SESSION
O que aconteceu: leitura de $_SESSION sem session_start() antes (veja o exemplo em Superglobais).
Como resolver: session_start() no topo da página.
O que aconteceu: sessões sem uso por mais de session.gc_maxlifetime segundos (24 minutos por padrão) podem ser apagadas pela limpeza automática do PHP. Também somem quando o navegador é fechado, porque o cookie PHPSESSID não tem data de expiração.
Como resolver: é o comportamento esperado (e mais seguro). Para "lembrar de mim" por semanas, o login completo usa um cookie próprio, com cuidados extras.
Referência
| Função / item | O que faz |
|---|---|
session_start() | Abre (ou cria) a sessão; antes de qualquer saída |
$_SESSION['x'] | Lê/grava dados da sessão |
session_id() | O número da sessão atual |
session_name() | O nome do cookie (PHPSESSID) |
session_regenerate_id(true) | Troca o número, mantendo os dados (use no login) |
session_get_cookie_params() | As opções do cookie de sessão |
session_destroy() | Apaga a sessão no servidor |
session_status() | PHP_SESSION_NONE (não iniciada) ou PHP_SESSION_ACTIVE |
session.save_path, session.gc_maxlifetime | Onde ficam os arquivos; quanto tempo sem uso até poderem ser apagados |
Exercícios
1. A primeira linha
Complete a função que precisa vir no topo de toda página que usa sessão:
2. Lembrar o nome
Complete para guardar o nome 'Ana' na sessão, na chave nome:
3. No login
Complete a função que troca o número da sessão logo após o login:
4. O que o cookie PHPSESSID guarda?
5. Para guardar "esta pessoa está logada como ana", o mais seguro é:
6. Qual combinação faz um logout completo?
Missão: o carrinho da padaria não lembra de nada: adicione dois produtos na aba Resultado e o contador nunca passa de 1, sem nenhum aviso (sem a sessão, $_SESSION é só um array comum, que some no fim de cada pedido). Descubra o que falta no começo do código, rode de novo e veja o carrinho crescer.
<?php
$_SESSION['carrinho'] ??= [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['carrinho'][] = $_POST['produto'] ?? '';
}
?>
<form method="post">
<select name="produto">
<option>Bolo de cenoura</option><option>Pão de queijo</option><option>Torta de limão</option>
</select>
<button>Adicionar</button>
</form>
<p>Itens no carrinho: <?= count($_SESSION['carrinho']) ?></p>
<ul>
<?php foreach ($_SESSION['carrinho'] as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
<?php
session_start();
$_SESSION['carrinho'] ??= [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['carrinho'][] = $_POST['produto'] ?? '';
}
?>
<form method="post">
<select name="produto">
<option>Bolo de cenoura</option><option>Pão de queijo</option><option>Torta de limão</option>
</select>
<button>Adicionar</button>
</form>
<p>Itens no carrinho: <?= count($_SESSION['carrinho']) ?></p>
<ul>
<?php foreach ($_SESSION['carrinho'] as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
Missão: o "painel secreto" aparece para qualquer um. Antes do segredo, confira se $_SESSION['usuario'] existe; se não existir, mostre Acesso negado e o link de entrar, e pare com exit. Depois teste na aba Resultado: Entrar, ver o painel, Sair.
<?php
session_start();
if (isset($_GET['entrar'])) { // finge um login certo (só para testar)
session_regenerate_id(true);
$_SESSION['usuario'] = 'ana';
}
if (isset($_GET['sair'])) {
$_SESSION = [];
session_destroy();
}
// proteja o painel aqui
echo "<p>Olá, " . htmlspecialchars($_SESSION['usuario']) . "! Saldo do caixa: R$ 1.234,00</p>";
echo '<p><a href="?sair=1">Sair</a></p>';
<?php
session_start();
if (isset($_GET['entrar'])) { // finge um login certo (só para testar)
session_regenerate_id(true);
$_SESSION['usuario'] = 'ana';
}
if (isset($_GET['sair'])) {
$_SESSION = [];
session_destroy();
}
if (!isset($_SESSION['usuario'])) {
echo '<p>Acesso negado. <a href="?entrar=1">Entrar</a></p>';
exit;
}
echo "<p>Olá, " . htmlspecialchars($_SESSION['usuario']) . "! Saldo do caixa: R$ 1.234,00</p>";
echo '<p><a href="?sair=1">Sair</a></p>';
Resumo
- Sessão = dados guardados no servidor + um número aleatório no cookie
PHPSESSIDque liga o navegador a eles. session_start()no topo de toda página que usa a sessão, antes de qualquer saída.$_SESSIONé um array comum que sobrevive entre pedidos: grave, leia (?? padrão),unset.- Login = conferir a senha e gravar
$_SESSION['usuario']; páginas restritas conferem comisset. session_regenerate_id(true)no login, contra fixação de sessão.- Logout:
$_SESSION = [], apagar o cookie,session_destroy(). - Cookie para preferências que o visitante pode ver; sessão para tudo que ele não pode mexer.