Sessions and Cookies
HTTP is a stateless protocol, meaning each request is independent with no memory of previous ones. Sessions and cookies solve this by allowing you to store data between requests. Cookies are small pieces of data stored in the browser, while sessions store data on the server with only a session ID sent to the browser. Together they enable login systems, shopping carts, preferences, and any feature that requires remembering a user.
What You'll Learn
- How cookies work and how to set them in PHP
- Session lifecycle: starting, reading, writing, and destroying
- Session security best practices
- Building a shopping cart with sessions
- Flash messages for one-time notifications
- Custom session configuration
Working with Cookies
Cookies are set via HTTP headers and stored in the user's browser. PHP makes it easy to create, read, and delete cookies:
<?php
// Setting cookies with setcookie()
// setcookie(name, value, expires, path, domain, secure, httponly)
// Simple cookie that expires in 30 days
$expiry = time() + (30 * 24 * 60 * 60);
setcookie('username', 'Alice', $expiry, '/');
// Cookie with security options (PHP 7.3+ options array)
setcookie('preferences', json_encode(['theme' => 'dark', 'lang' => 'en']), [
'expires' => time() + (365 * 24 * 60 * 60),
'path' => '/',
'domain' => '',
'secure' => true, // Only send over HTTPS
'httponly' => true, // Not accessible via JavaScript
'samesite' => 'Lax', // CSRF protection
]);
echo "Cookies have been set\n";
// Reading cookies (available on the NEXT request via $_COOKIE)
// Simulating cookie values for demonstration
$_COOKIE['username'] = 'Alice';
$_COOKIE['preferences'] = json_encode(['theme' => 'dark', 'lang' => 'en']);
if (isset($_COOKIE['username'])) {
echo "Welcome back, {$_COOKIE['username']}!\n";
}
if (isset($_COOKIE['preferences'])) {
$prefs = json_decode($_COOKIE['preferences'], true);
echo "Theme: {$prefs['theme']}, Language: {$prefs['lang']}\n";
}
// Deleting a cookie: set expiry to the past
setcookie('username', '', time() - 3600, '/');
echo "Username cookie deleted\n";
// Cookie helper class
class CookieJar {
public static function set(string $name, mixed $value, int $days = 30): void {
$encoded = is_string($value) ? $value : json_encode($value);
setcookie($name, $encoded, [
'expires' => time() + ($days * 86400),
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
}
public static function get(string $name, mixed $default = null): mixed {
if (!isset($_COOKIE[$name])) {
return $default;
}
$decoded = json_decode($_COOKIE[$name], true);
return $decoded !== null ? $decoded : $_COOKIE[$name];
}
public static function delete(string $name): void {
setcookie($name, '', time() - 3600, '/');
unset($_COOKIE[$name]);
}
public static function exists(string $name): bool {
return isset($_COOKIE[$name]);
}
}
echo "\nUsing CookieJar:\n";
CookieJar::set('visit_count', 42);
echo "Cookie exists: " . (CookieJar::exists('preferences') ? 'yes' : 'no') . "\n";
echo "Default value: " . CookieJar::get('missing', 'fallback') . "\n";
Important: cookies set with setcookie() are sent as HTTP headers, so they must be called before any output is sent to the browser. The cookie values are not available in $_COOKIE until the next request.
Session Basics
Sessions store data on the server and identify the user by a session ID cookie. This is more secure than cookies because the data itself never leaves the server:
<?php
// Start or resume a session
// session_start(); // In a real application, call this at the top
// Simulating session behavior for demonstration
$_SESSION = [];
// Store data in the session
$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'Alice';
$_SESSION['role'] = 'admin';
$_SESSION['login_time'] = date('Y-m-d H:i:s');
echo "Session data stored:\n";
echo " User ID: {$_SESSION['user_id']}\n";
echo " Username: {$_SESSION['username']}\n";
echo " Role: {$_SESSION['role']}\n";
echo " Login time: {$_SESSION['login_time']}\n";
// Check if session data exists
if (isset($_SESSION['username'])) {
echo "\nWelcome back, {$_SESSION['username']}!\n";
}
// Update session data
$_SESSION['page_views'] = ($_SESSION['page_views'] ?? 0) + 1;
echo "Page views this session: {$_SESSION['page_views']}\n";
// Remove specific session data
unset($_SESSION['login_time']);
echo "Login time removed from session\n";
// Session functions reference:
// session_start() - Start or resume session
// session_id() - Get/set session ID
// session_regenerate_id(true) - New ID (security)
// session_destroy() - Destroy session
// session_unset() - Remove all session data
// Simulated logout function
function logout(): void {
// In a real app:
// session_unset();
// session_destroy();
// setcookie(session_name(), '', time() - 3600, '/');
$_SESSION = [];
echo "User logged out, session cleared\n";
}
logout();
echo "Session after logout: " . (empty($_SESSION) ? 'empty' : 'has data') . "\n";
In a real web application, always call session_start() at the very beginning of your script, before any output. PHP automatically handles creating the session file on the server and sending the session ID cookie to the browser.
Shopping Cart with Sessions
Here is a practical example of using sessions to build a shopping cart:
<?php
class ShoppingCart {
private array $items = [];
public function __construct() {
// In a real app: $this->items = $_SESSION['cart'] ?? [];
$this->items = [];
}
public function addItem(string $id, string $name, float $price, int $quantity = 1): void {
if (isset($this->items[$id])) {
$this->items[$id]['quantity'] += $quantity;
} else {
$this->items[$id] = [
'name' => $name,
'price' => $price,
'quantity' => $quantity,
];
}
$this->save();
echo "Added {$quantity}x {$name} to cart\n";
}
public function removeItem(string $id): void {
if (isset($this->items[$id])) {
$name = $this->items[$id]['name'];
unset($this->items[$id]);
$this->save();
echo "Removed {$name} from cart\n";
}
}
public function updateQuantity(string $id, int $quantity): void {
if ($quantity <= 0) {
$this->removeItem($id);
return;
}
if (isset($this->items[$id])) {
$this->items[$id]['quantity'] = $quantity;
$this->save();
}
}
public function getTotal(): float {
$total = 0.0;
foreach ($this->items as $item) {
$total += $item['price'] * $item['quantity'];
}
return $total;
}
public function getItemCount(): int {
$count = 0;
foreach ($this->items as $item) {
$count += $item['quantity'];
}
return $count;
}
public function display(): void {
if (empty($this->items)) {
echo "Cart is empty\n";
return;
}
echo "\n--- Shopping Cart ---\n";
foreach ($this->items as $id => $item) {
$subtotal = $item['price'] * $item['quantity'];
echo " {$item['name']} x{$item['quantity']} @ \${$item['price']} = \${$subtotal}\n";
}
echo " ---\n";
echo " Items: {$this->getItemCount()}\n";
echo " Total: $" . number_format($this->getTotal(), 2) . "\n";
}
public function clear(): void {
$this->items = [];
$this->save();
}
private function save(): void {
// In a real app: $_SESSION['cart'] = $this->items;
}
}
$cart = new ShoppingCart();
$cart->addItem('laptop', 'Laptop Pro 16"', 1299.99);
$cart->addItem('mouse', 'Wireless Mouse', 49.99, 2);
$cart->addItem('keyboard', 'Mechanical Keyboard', 129.99);
$cart->addItem('mouse', 'Wireless Mouse', 49.99, 1); // Add one more mouse
$cart->display();
echo "\n";
$cart->updateQuantity('mouse', 1);
$cart->removeItem('keyboard');
$cart->display();
In production, the save() method would write to $_SESSION['cart'] so the cart persists across page loads. The cart object provides a clean API while the session handles persistence.
Flash Messages
Flash messages are notifications that appear once and are then automatically cleared. They are commonly used after form submissions (success messages, error messages):
<?php
class FlashMessage {
private static array $messages = [];
public static function set(string $type, string $message): void {
// In a real app: $_SESSION['flash'][] = [...]
self::$messages[] = [
'type' => $type,
'message' => $message,
];
}
public static function success(string $message): void {
self::set('success', $message);
}
public static function error(string $message): void {
self::set('error', $message);
}
public static function warning(string $message): void {
self::set('warning', $message);
}
public static function getAll(): array {
$messages = self::$messages;
// In a real app: unset($_SESSION['flash']);
self::$messages = [];
return $messages;
}
public static function hasMessages(): bool {
return !empty(self::$messages);
}
public static function render(): string {
$html = '';
foreach (self::getAll() as $flash) {
$type = htmlspecialchars($flash['type']);
$message = htmlspecialchars($flash['message']);
$html .= "<div class=\"alert alert-{$type}\">{$message}</div>\n";
}
return $html;
}
}
// Simulating a form submission flow
function processRegistration(string $name, string $email): bool {
if (empty($name)) {
FlashMessage::error("Name is required");
return false;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
FlashMessage::error("Invalid email address");
return false;
}
// Simulate saving to database
FlashMessage::success("Account created for {$name}!");
FlashMessage::warning("Please verify your email address");
return true;
}
// Test with valid data
processRegistration("Alice", "alice@example.com");
// Display flash messages (would happen on the next page load)
echo "Flash messages:\n";
foreach (FlashMessage::getAll() as $flash) {
$icon = match ($flash['type']) {
'success' => '[OK]',
'error' => '[ERR]',
'warning' => '[WARN]',
default => '[INFO]',
};
echo " {$icon} {$flash['message']}\n";
}
// Messages are now cleared
echo "Has messages after display: " . (FlashMessage::hasMessages() ? 'yes' : 'no') . "\n";
// Test with invalid data
echo "\nInvalid registration:\n";
processRegistration("", "not-an-email");
foreach (FlashMessage::getAll() as $flash) {
echo " [{$flash['type']}] {$flash['message']}\n";
}
The key idea behind flash messages is that they are read once and then deleted. In a real application, you would store them in $_SESSION['flash'] during the form handler, then read and unset them on the next page render.
Key Takeaways
- Cookies store small amounts of data in the browser; sessions store data on the server
- Always use
secure,httponly, andsamesiteoptions when setting cookies - Call
session_start()before any output to begin a session - Use
session_regenerate_id(true)after login to prevent session fixation attacks - Sessions are ideal for shopping carts, user authentication, and temporary state
- Flash messages use sessions for one-time notifications across requests
- Never store sensitive data (passwords, credit cards) in cookies
- Configure session lifetime, cookie parameters, and storage path in
php.inior at runtime
Next Steps
You can now set and read cookies, manage server-side sessions, build shopping cart persistence, and implement flash messages. Your applications can maintain state across the stateless HTTP protocol.
At this point you have built up a strong foundation: variables, functions, OOP, databases, files, and sessions. Writing all of that from scratch for every project would be slow and error-prone. That is why PHP frameworks exist. Laravel basics introduces the most popular PHP framework, which ties together routing, controllers, Blade templates, Eloquent ORM, and middleware into an elegant, productive toolkit. Everything you have learned so far maps directly to a Laravel feature, so the concepts will feel familiar even as the syntax gets more expressive.
Continue to Laravel Basics -->
Pro Tip: In production, configure PHP sessions to use a centralized store like Redis or Memcached instead of the default file-based storage. This enables horizontal scaling across multiple servers since any server can access any user's session. Use
session_set_save_handler()to register a custom session handler, or simply setsession.save_handler = redisin your php.ini.