Sessions
HTTP is stateless: every request is independent, and the server doesn't remember the previous one. Sessions fix that. PHP gives each visitor a random session ID, stored in a cookie, and keeps data for that visitor on the server in the $_SESSION array — perfect for logins, shopping carts and one-time messages.
Starting a session
Call session_start() at the top of every page that uses the session, before any output. Then read and write $_SESSION like a normal array; PHP saves it automatically at the end of the request:
<?php session_start(); $_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1; $_SESSION['last_page'] = 'counter'; echo "You have viewed this page {$_SESSION['visits']} time(s).";
GET /counter.php GET /counter.php GET /counter.php
> GET /counter.php < Set-Cookie: PHPSESSID=…; path=/ You have viewed this page 1 time(s). > GET /counter.php You have viewed this page 2 time(s). > GET /counter.php You have viewed this page 3 time(s).
On the first visit, PHP creates a session and sends a PHPSESSID cookie containing a long random ID. The browser sends that cookie back with every later request, and PHP loads the matching data. The data itself never leaves the server — the visitor only holds the ID.
A shopping cart
Session data can be any serialisable value — arrays work well:
<?php session_start(); header('Content-Type: text/plain'); $_SESSION['cart'] ??= []; $prices = ['pen' => 1.20, 'notebook' => 3.50, 'stapler' => 8.90]; $action = $_POST['action'] ?? ''; $item = $_POST['item'] ?? ''; if ($action === 'add' && isset($prices[$item])) { $_SESSION['cart'][$item] = ($_SESSION['cart'][$item] ?? 0) + 1; } elseif ($action === 'remove') { unset($_SESSION['cart'][$item]); } elseif ($action === 'clear') { $_SESSION['cart'] = []; } $total = 0; foreach ($_SESSION['cart'] as $name => $qty) { printf("%-9s x%d %6.2f\n", $name, $qty, $prices[$name] * $qty); $total += $prices[$name] * $qty; } printf("Total: %.2f\n", $total);
POST /cart.php action=add&item=pen POST /cart.php action=add&item=notebook POST /cart.php action=add&item=pen POST /cart.php action=remove&item=notebook
> POST /cart.php pen x1 1.20 Total: 1.20 > POST /cart.php pen x1 1.20 notebook x1 3.50 Total: 4.70 > POST /cart.php pen x2 2.40 notebook x1 3.50 Total: 5.90 > POST /cart.php pen x2 2.40 Total: 2.40
Logging in and out
A login stores the user's ID in the session after checking their password. Two security details matter:
- Call
session_regenerate_id(true)right after logging in. It gives the session a new ID, so an attacker who planted or guessed the old one (session fixation) gains nothing. - On logout, clear the data and destroy the session.
<?php session_start(); header('Content-Type: text/plain'); // In a real app these come from a database; the hash was made with password_hash('secret123', PASSWORD_DEFAULT) $users = ['ana' => password_hash('secret123', PASSWORD_DEFAULT)]; $action = $_GET['action'] ?? 'status'; if ($action === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') { $user = $_POST['user'] ?? ''; $hash = $users[$user] ?? null; if ($hash !== null && password_verify($_POST['password'] ?? '', $hash)) { session_regenerate_id(true); $_SESSION['user'] = $user; echo "Logged in as $user\n"; } else { echo "Invalid username or password\n"; } } elseif ($action === 'logout') { $_SESSION = []; session_destroy(); echo "Logged out\n"; } else { echo isset($_SESSION['user']) ? "Signed in: {$_SESSION['user']}\n" : "Not signed in\n"; }
GET /login.php POST /login.php?action=login user=ana&password=wrong POST /login.php?action=login user=ana&password=secret123 GET /login.php GET /login.php?action=logout GET /login.php
> GET /login.php Not signed in > POST /login.php?action=login Invalid username or password > POST /login.php?action=login Logged in as ana > GET /login.php Signed in: ana > GET /login.php?action=logout Logged out > GET /login.php Not signed in
Passwords are never stored or compared as plain text — password_hash and password_verify are explained in lesson 59. Note the error message doesn't reveal whether the username or the password was wrong.
Flash messages
A "flash" message is stored in the session, shown on the next page, and then deleted. It's the standard way to say "Saved!" after a Post/Redirect/Get:
<?php session_start(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { // ...save the profile... $_SESSION['flash'] = 'Your profile was updated.'; header('Location: /profile.php'); exit; } $flash = $_SESSION['flash'] ?? null; unset($_SESSION['flash']); ?> <?php if ($flash): ?><div class="flash"><?= htmlspecialchars($flash) ?></div><?php endif; ?> <h1>Your profile</h1>
POST /profile.php name=Ana GET /profile.php GET /profile.php
> POST /profile.php < 302 Found < Location: /profile.php > GET /profile.php <div class="flash">Your profile was updated.</div><h1>Your profile</h1> > GET /profile.php <h1>Your profile</h1>
The message appears exactly once.
Session configuration and security
- Sessions are only as secure as their cookie. Serve your site over HTTPS and set the cookie flags
secure,httponlyandsamesite— pass them tosession_start(['cookie_secure' => true, 'cookie_httponly' => true, 'cookie_samesite' => 'Lax'])or set them inphp.ini. - Keep sessions small: store IDs, not whole database records.
session_start()locks the session until the request ends, so two simultaneous requests from the same user wait for each other. Callsession_write_close()early in slow scripts that no longer need to write.- By default, session data is stored in files on the server. Applications running on several servers store sessions in a shared place such as Redis or a database.
Try it yourself
Build a "recently viewed products" list that remembers the last three products a visitor opened.