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.

Você vai aprender
  • 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_start e 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_id no 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         │
                                         └──────────────────────┘
A sessão = um número aleatório no cookie 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 PHPSESSID vá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 o setcookie.
$_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-Control e Pragma: 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çãoCódigo
Gravar$_SESSION['chave'] = $valor;
Ler (com padrão)$_SESSION['chave'] ?? 'padrão'
Existe?isset($_SESSION['chave'])
Apagar uma chaveunset($_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:

  1. A pessoa envia usuário e senha.
  2. O PHP confere. Se estiver certo, grava na sessão quem é: $_SESSION['usuario'] = 'ana'.
  3. 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";
Só a ideia. Senha escrita no código e comparada com === 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";
LinhaO 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

CookieSessão
Onde ficam os dadosNo navegador do visitanteNo servidor (o navegador guarda só o número, num cookie)
O visitante pode ler ou mudar?SimNão (só pode perder ou trocar o número)
TamanhoAté ~4 KBSem limite prático (é um arquivo no servidor)
Quanto duraO 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 quandoNo pedido seguinte ao setcookieNo mesmo pedido, logo após gravar em $_SESSION
Como usarsetcookie + $_COOKIEsession_start + $_SESSION
Bom paraPreferê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).

Gravei na sessão, mas na página seguinte ela está 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 login "cai" sozinho depois de um tempo

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 / itemO 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_maxlifetimeOnde 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:

<?php

2. Lembrar o nome

Complete para guardar o nome 'Ana' na sessão, na chave nome:

= 'Ana';

3. No login

Complete a função que troca o número da sessão logo após o login:

(true);

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 PHPSESSID que 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 com isset.
  • 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.