PHP Web Interaction: Error Handling, File I/O, and State Management
A web application is only as good as its ability to handle data and errors. This guide covers the essential runtime operations of PHP: catching exceptions before they crash production, manipulating the filesystem via Streams and SPL, securing form data against vulnerabilities like CSRF, and maintaining user state across stateless HTTP requests.
Error Handling
Error Types (Notice, Warning, Fatal, Parse)
PHP has four main error severity levels: Notice (minor issues like undefined variables), Warning (non-fatal problems that don't stop execution), Fatal (critical errors that halt script execution), and Parse (syntax errors caught before execution).
┌─────────────────────────────────────────────────────────┐ │ PHP ERROR SEVERITY │ ├──────────┬──────────┬─────────────────┬────────────────┤ │ Parse │ Fatal │ Warning │ Notice │ │ (Syntax) │ (Stops) │ (Continues) │ (Minor) │ ├──────────┴──────────┴─────────────────┴────────────────┤ │ SEVERITY: HIGH ◄──────────────────────────────► LOW │ └─────────────────────────────────────────────────────────┘
echo $undefined; // Notice: Undefined variable include 'missing.php'; // Warning: file not found require 'missing.php'; // Fatal: script stops echo "missing quote; // Parse: syntax error
error_reporting Levels
The error_reporting() function controls which errors PHP reports; you can use predefined constants or bitmask
combinations to filter specific error types, and E_ALL is recommended during development.
error_reporting(E_ALL); // Report all errors error_reporting(E_ERROR | E_WARNING); // Only fatal and warnings error_reporting(E_ALL & ~E_NOTICE); // All except notices error_reporting(0); // Disable all reporting ini_set('display_errors', 1); // Show errors on screen ini_set('log_errors', 1); // Log errors to file
Custom Error Handlers
set_error_handler() allows you to define a custom function to handle PHP errors, giving you control over logging,
formatting, and response behavior instead of using PHP's default error handling.
function customErrorHandler($errno, $errstr, $errfile, $errline) { $log = "[" . date('Y-m-d H:i:s') . "] Error $errno: $errstr in $errfile:$errline"; error_log($log, 3, '/var/log/php_errors.log'); if ($errno === E_USER_ERROR) { die("Fatal error occurred. Check logs."); } return true; // Don't execute PHP's internal handler } set_error_handler('customErrorHandler'); trigger_error("Custom warning!", E_USER_WARNING);
Exceptions
Exceptions are objects representing errors that can be thrown and caught, providing a structured way to handle exceptional conditions with stack traces and error propagation through the call stack.
function divide($a, $b) { if ($b === 0) { throw new Exception("Division by zero!", 1001); } return $a / $b; } $exception = new Exception("Something failed"); echo $exception->getMessage(); // "Something failed" echo $exception->getCode(); // 0 echo $exception->getFile(); // Current file path echo $exception->getLine(); // Line number echo $exception->getTraceAsString(); // Stack trace
try-catch-finally Blocks
The try block contains code that might throw exceptions, catch handles specific exception types, and finallyalways
executes regardless of whether an exception occurred—useful for cleanup operations.
try { $db = new PDO($dsn, $user, $pass); $result = riskyOperation(); } catch (PDOException $e) { echo "Database error: " . $e->getMessage(); } catch (InvalidArgumentException $e) { echo "Invalid input: " . $e->getMessage(); } catch (Exception $e) { echo "General error: " . $e->getMessage(); } finally { // Always runs - cleanup code $db = null; echo "Cleanup completed"; }
┌─────────────────────────────────────────┐ │ TRY ──► Code that might throw │ │ │ │ │ ▼ (exception thrown) │ │ CATCH ──► Handle specific exception │ │ │ │ │ ▼ (always executes) │ │ FINALLY ──► Cleanup code │ └─────────────────────────────────────────┘
Throwing Exceptions
Use the throw keyword to raise an exception manually when your code encounters an error condition; this transfers
control to the nearest matching catch block in the call stack.
function validateAge($age) { if (!is_numeric($age)) { throw new InvalidArgumentException("Age must be numeric"); } if ($age < 0 || $age > 150) { throw new RangeException("Age must be between 0 and 150"); } return true; } // Re-throwing exceptions try { validateAge(-5); } catch (Exception $e) { error_log($e->getMessage()); throw $e; // Re-throw to caller }
Custom Exception Classes
Creating custom exception classes by extending Exception allows you to add domain-specific properties, methods, and
behavior for different error categories in your application.
class ValidationException extends Exception { private array $errors; public function __construct(array $errors, $message = "Validation failed") { parent::__construct($message, 422); $this->errors = $errors; } public function getErrors(): array { return $this->errors; } } // Usage throw new ValidationException([ 'email' => 'Invalid format', 'age' => 'Must be positive' ]);
Exception Hierarchy
PHP's exception classes form an inheritance hierarchy under Throwable, with Exception for application errors and
Error for engine-level errors (PHP 7+), allowing granular catching.
Throwable (interface) │ ┌────────────┴────────────┐ │ │ Exception Error │ │ ┌───────┼───────┐ ┌───────┼───────┐ │ │ │ │ │ │ Runtime Logic Custom TypeError Parse Argument Exception Exception Error Error Error
try { // code } catch (Error $e) { // Catches PHP engine errors } catch (Exception $e) { // Catches application exceptions } catch (Throwable $t) { // Catches everything }
SPL Exceptions
The Standard PHP Library provides a set of predefined exception classes for common error scenarios, offering semantic meaning and standardization across codebases without creating custom exceptions.
// Logic exceptions (programmer errors - shouldn't be caught in production) throw new LogicException("Logic error"); throw new BadFunctionCallException("Function doesn't exist"); throw new BadMethodCallException("Method doesn't exist"); throw new DomainException("Value not in valid domain"); throw new InvalidArgumentException("Invalid argument passed"); throw new LengthException("Invalid length"); throw new OutOfRangeException("Index out of range"); // Runtime exceptions (can occur during normal operation) throw new RuntimeException("Runtime error"); throw new OutOfBoundsException("Invalid key"); throw new OverflowException("Container overflow"); throw new UnderflowException("Container underflow"); throw new RangeException("Value not in range"); throw new UnexpectedValueException("Unexpected value");
Error vs Exception
Errors are typically unrecoverable issues generated by PHP's engine (syntax, memory), while Exceptions are
application-level conditions designed to be caught and handled gracefully; PHP 7+ unified both under Throwable.
// Error (PHP 7+) - engine level try { $result = undefinedFunction(); } catch (Error $e) { echo "Error caught: " . $e->getMessage(); } // Exception - application level try { throw new Exception("App error"); } catch (Exception $e) { echo "Exception caught: " . $e->getMessage(); } // Converting errors to exceptions set_error_handler(function($severity, $message, $file, $line) { throw new ErrorException($message, 0, $severity, $file, $line); });
File Handling
File Opening and Closing
Use fopen() to open files with specific modes (read, write, append) returning a resource handle, and always close with
fclose() to free system resources and ensure data is flushed.
$handle = fopen('file.txt', 'r'); // Open for reading // ... do operations fclose($handle); // Close the handle
// File modes ┌──────┬─────────────────────────────────────────────┐ │ Mode │ Description │ ├──────┼─────────────────────────────────────────────┤ │ r │ Read only, pointer at start │ │ r+ │ Read/write, pointer at start │ │ w │ Write only, truncate or create │ │ w+ │ Read/write, truncate or create │ │ a │ Write only, append, create if needed │ │ a+ │ Read/write, append, create if needed │ │ x │ Create and write only, fail if exists │ │ c │ Write only, create, don't truncate │ └──────┴─────────────────────────────────────────────┘
Reading Files (fread, fgets, file_get_contents)
PHP offers multiple file reading methods: fread() for binary/chunk reading, fgets() for line-by-line reading, and
file_get_contents() for slurping entire files into a string.
// Read entire file into string (simplest) $content = file_get_contents('file.txt'); // Read entire file into array (line by line) $lines = file('file.txt', FILE_IGNORE_NEW_LINES); // Read with handle - specific bytes $handle = fopen('file.txt', 'r'); $chunk = fread($handle, 1024); // Read 1024 bytes fclose($handle); // Read line by line (memory efficient for large files) $handle = fopen('large.txt', 'r'); while (($line = fgets($handle)) !== false) { echo $line; } fclose($handle); // Read single character $char = fgetc($handle);
Writing Files (fwrite, file_put_contents)
Use fwrite() for granular write control with file handles, or file_put_contents() for simple one-liner writes; both
support flags for appending and locking.
// Simple write (overwrites) file_put_contents('file.txt', "Hello World"); // Append to file file_put_contents('file.txt', "New line\n", FILE_APPEND); // With locking (prevents race conditions) file_put_contents('file.txt', $data, LOCK_EX); // Using handle for multiple writes $handle = fopen('file.txt', 'w'); fwrite($handle, "Line 1\n"); fwrite($handle, "Line 2\n"); fflush($handle); // Force write to disk fclose($handle);
File Pointers
File pointers track the current read/write position within an open file; use ftell() to get position, fseek() to
move it, and rewind() to reset to the beginning.
$handle = fopen('file.txt', 'r+'); echo ftell($handle); // 0 (start position) fseek($handle, 10); // Move to byte 10 echo ftell($handle); // 10 fseek($handle, -5, SEEK_CUR); // Move 5 bytes back from current fseek($handle, -10, SEEK_END); // Move 10 bytes before end rewind($handle); // Back to start (position 0) fclose($handle); // File: [H][e][l][l][o][ ][W][o][r][l][d] // Pos: 0 1 2 3 4 5 6 7 8 9 10 // │ // └── ftell() returns current position
File Locking
File locking with flock() prevents concurrent access issues when multiple processes read/write the same file; use
shared locks for reading and exclusive locks for writing.
$handle = fopen('data.txt', 'r+'); // Exclusive lock for writing if (flock($handle, LOCK_EX)) { fwrite($handle, "Safe write"); fflush($handle); flock($handle, LOCK_UN); // Release lock } // Shared lock for reading (multiple readers allowed) if (flock($handle, LOCK_SH)) { $content = fread($handle, filesize('data.txt')); flock($handle, LOCK_UN); } // Non-blocking lock attempt if (flock($handle, LOCK_EX | LOCK_NB)) { // Got lock immediately } else { // Lock not available, do something else } fclose($handle);
Directory Operations
PHP provides functions for creating, reading, and manipulating directories: mkdir(), rmdir(), scandir(),
opendir()/readdir(), and glob() for pattern matching.
// Create directory mkdir('new_folder', 0755); mkdir('path/to/nested', 0755, true); // Recursive // Remove directory (must be empty) rmdir('empty_folder'); // List directory contents $files = scandir('/path/to/dir'); // Returns array $files = glob('*.php'); // Pattern matching $files = glob('**/*.php', GLOB_BRACE); // Recursive patterns // Iterate directory $dir = opendir('/path'); while (($file = readdir($dir)) !== false) { if ($file !== '.' && $file !== '..') { echo $file . "\n"; } } closedir($dir); // Check and get info is_dir('folder'); is_file('file.txt'); file_exists('path');
File Upload Handling
Handle file uploads via the $_FILES superglobal, validating type, size, and errors before moving from the temp
location with move_uploaded_file() for security.
// HTML: <input type="file" name="document"> if ($_SERVER['REQUEST_METHOD'] === 'POST') { $file = $_FILES['document']; // Check for errors if ($file['error'] === UPLOAD_ERR_OK) { $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; $maxSize = 5 * 1024 * 1024; // 5MB // Validate if (!in_array($file['type'], $allowedTypes)) { die('Invalid file type'); } if ($file['size'] > $maxSize) { die('File too large'); } // Move to permanent location $destination = 'uploads/' . basename($file['name']); move_uploaded_file($file['tmp_name'], $destination); } }
File Permissions
PHP can read and modify Unix file permissions using chmod(), chown(), chgrp(), and inspect them with
fileperms(); permissions follow the standard octal notation.
chmod('file.txt', 0644); // rw-r--r-- chmod('script.sh', 0755); // rwxr-xr-x chown('file.txt', 'www-data'); // Change owner chgrp('file.txt', 'www-data'); // Change group // Get permissions $perms = fileperms('file.txt'); echo substr(sprintf('%o', $perms), -4); // "0644" // Check permissions is_readable('file.txt'); is_writable('file.txt'); is_executable('script.sh');
┌─────────────────────────────────────┐ │ 0 7 5 5 │ │ │ │ │ │ │ │ │ Owner Group Other │ │ │ rwx r-x r-x │ │ └── Octal prefix │ └─────────────────────────────────────┘
CSV File Handling
Use fgetcsv() and fputcsv() for robust CSV parsing and writing that handles quoted fields, delimiters, and
enclosures correctly without manual string splitting.
// Reading CSV $handle = fopen('data.csv', 'r'); $headers = fgetcsv($handle); // First row as headers while (($row = fgetcsv($handle, 0, ',')) !== false) { $record = array_combine($headers, $row); print_r($record); } fclose($handle); // Writing CSV $handle = fopen('output.csv', 'w'); fputcsv($handle, ['Name', 'Email', 'Age']); // Headers $data = [ ['John', 'john@example.com', 30], ['Jane', 'jane@example.com', 25], ]; foreach ($data as $row) { fputcsv($handle, $row); } fclose($handle);
Working with JSON Files
Use json_encode() and json_decode() combined with file functions to serialize PHP data structures to JSON files and
parse them back, with proper error handling.
// Writing JSON $data = [ 'users' => [ ['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Jane'], ] ]; file_put_contents('data.json', json_encode($data, JSON_PRETTY_PRINT)); // Reading JSON $json = file_get_contents('data.json'); $data = json_decode($json, true); // true for associative array // Error handling if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException('JSON error: ' . json_last_error_msg()); } // JSON flags json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Working with XML Files
PHP offers SimpleXML for easy XML manipulation and DOMDocument for complex operations; both support reading, modifying, and creating XML with XPath queries.
// SimpleXML - Reading $xml = simplexml_load_file('data.xml'); echo $xml->book[0]->title; foreach ($xml->book as $book) { echo $book['id'] . ': ' . $book->title . "\n"; } // SimpleXML - Creating $xml = new SimpleXMLElement('<library/>'); $book = $xml->addChild('book'); $book->addAttribute('id', '1'); $book->addChild('title', 'PHP Guide'); $xml->asXML('output.xml'); // DOMDocument for complex operations $dom = new DOMDocument(); $dom->load('data.xml'); $xpath = new DOMXPath($dom); $nodes = $xpath->query('//book[@category="php"]');
Streams and Stream Wrappers
Streams provide a unified way to access files, network resources, and compressed data through a common interface;
wrappers like php://, http://, and custom ones extend this functionality.
// Built-in stream wrappers $content = file_get_contents('http://example.com'); // HTTP $content = file_get_contents('ftp://user:pass@host/file'); $input = file_get_contents('php://input'); // Raw POST data $temp = fopen('php://temp', 'r+'); // Temporary stream $memory = fopen('php://memory', 'r+'); // Memory stream // Stream context $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode(['key' => 'value']), ] ]); $response = file_get_contents('http://api.example.com', false, $context); // List available wrappers print_r(stream_get_wrappers());
SPL File Classes (SplFileObject, SplFileInfo)
SPL provides object-oriented file handling: SplFileInfo for file metadata and SplFileObject for read/write
operations with iterator support for memory-efficient line processing.
// SplFileInfo - metadata only $info = new SplFileInfo('document.pdf'); echo $info->getSize(); echo $info->getExtension(); echo $info->getMTime(); echo $info->isReadable(); // SplFileObject - full file handling $file = new SplFileObject('data.csv'); $file->setFlags(SplFileObject::READ_CSV); foreach ($file as $row) { print_r($row); // Each line as array } // Writing with SplFileObject $file = new SplFileObject('output.txt', 'w'); $file->fwrite("Hello World\n"); // Seeking $file->seek(10); // Go to line 10 echo $file->current();
Forms and User Input
GET and POST Methods
GET appends data to the URL (visible, cacheable, limited length) for retrieving data, while POST sends data in the request body (hidden, unlimited size) for submitting/modifying data.
┌─────────────────────────────────────────────────────────┐ │ GET Request │ │ URL: /search?query=php&page=1 │ │ ► Data visible in URL │ │ ► Cacheable, bookmarkable │ │ ► ~2KB limit (browser dependent) │ ├─────────────────────────────────────────────────────────┤ │ POST Request │ │ URL: /login │ │ Body: username=john&password=secret │ │ ► Data hidden from URL │ │ ► Not cacheable │ │ ► No size limit │ └─────────────────────────────────────────────────────────┘
<form method="GET" action="search.php"> <form method="POST" action="login.php">
$_GET, $_POST, $_REQUEST
These superglobal arrays contain form data: $_GET for URL parameters, $_POST for POST body data, and $_REQUEST
combines both (plus cookies)—avoid $_REQUEST for clarity.
// URL: /page.php?id=123&name=john $id = $_GET['id']; // "123" $name = $_GET['name']; // "john" // POST form data $email = $_POST['email']; $password = $_POST['password']; // Safe access (avoid undefined index) $id = $_GET['id'] ?? null; $id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT); // Check if exists if (isset($_POST['submit'])) { // Form was submitted } // $_REQUEST (contains GET + POST + COOKIES - avoid using) $value = $_REQUEST['field'];
Form Validation
Validate all user input on the server-side by checking required fields, data types, formats, lengths, and business rules; never trust client-side validation alone.
$errors = []; // Required field if (empty($_POST['email'])) { $errors['email'] = 'Email is required'; } // Email format if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) { $errors['email'] = 'Invalid email format'; } // Length check if (strlen($_POST['password']) < 8) { $errors['password'] = 'Password must be at least 8 characters'; } // Numeric range $age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT); if ($age === false || $age < 18 || $age > 120) { $errors['age'] = 'Invalid age'; } // Pattern matching if (!preg_match('/^[A-Z]{2}\d{6}$/', $_POST['code'])) { $errors['code'] = 'Invalid code format'; } if (empty($errors)) { // Process form }
Input Sanitization
Sanitization cleans user input by removing or encoding dangerous characters; always sanitize for the output context ( HTML, SQL, URL) rather than just on input.
// Remove HTML tags $clean = strip_tags($_POST['bio']); // HTML entities (for displaying in HTML) $safe = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8'); // Sanitize filters $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); $url = filter_var($_POST['website'], FILTER_SANITIZE_URL); $int = filter_var($_POST['age'], FILTER_SANITIZE_NUMBER_INT); // Trim whitespace $username = trim($_POST['username']); // Escape for shell commands (use sparingly!) $arg = escapeshellarg($_POST['filename']); // For SQL - use prepared statements instead! // Never: $sql = "SELECT * FROM users WHERE name = '$name'";
filter_var and Filter Functions
filter_var() and filter_input() provide built-in validation and sanitization filters for common data types, reducing the need for regex and manual validation logic.
// Validation filters (return value or false) filter_var('user@example.com', FILTER_VALIDATE_EMAIL); // Valid filter_var('123', FILTER_VALIDATE_INT); // 123 filter_var('192.168.1.1', FILTER_VALIDATE_IP); // Valid filter_var('https://example.com', FILTER_VALIDATE_URL); // Valid // With options filter_var(15, FILTER_VALIDATE_INT, [ 'options' => ['min_range' => 1, 'max_range' => 100] ]); // Input filters (from superglobals) $id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT); $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); // Filter arrays $data = filter_input_array(INPUT_POST, [ 'email' => FILTER_VALIDATE_EMAIL, 'age' => FILTER_VALIDATE_INT, 'name' => FILTER_SANITIZE_STRING, ]);
CSRF Protection
Cross-Site Request Forgery protection uses tokens to verify that form submissions originate from your site; generate a token per session, embed it in forms, and validate on submission.
// Generate token (once per session) session_start(); if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // Include in form echo '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($_SESSION['csrf_token']) . '">'; // Validate on submission if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { die('CSRF validation failed'); } // Process form safely }
┌─────────────────────────────────────────┐ │ 1. Server generates token │ │ 2. Token stored in session │ │ 3. Token embedded in form (hidden) │ │ 4. Form submitted with token │ │ 5. Server validates token matches │ └─────────────────────────────────────────┘
File Uploads
File uploads require enctype="multipart/form-data", proper size limits in php.ini, and thorough validation of type, size, name, and content before storing securely.
// php.ini settings // upload_max_filesize = 10M // post_max_size = 12M // HTML: <form enctype="multipart/form-data"> if ($_FILES['avatar']['error'] === UPLOAD_ERR_OK) { // Validate MIME type (don't trust 'type' from client) $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($_FILES['avatar']['tmp_name']); if (!in_array($mime, ['image/jpeg', 'image/png'])) { die('Invalid file type'); } // Generate safe filename $ext = pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION); $newName = bin2hex(random_bytes(16)) . '.' . $ext; // Store outside webroot if possible move_uploaded_file($_FILES['avatar']['tmp_name'], '/var/uploads/' . $newName); }
Sessions and Cookies
Starting and Destroying Sessions
Call session_start() before any output to initialize session handling; destroy sessions properly with session_destroy() after clearing data and invalidating the cookie.
// Start session (must be before ANY output) session_start(); // Use session $_SESSION['user_id'] = 123; // Proper session destruction session_start(); $_SESSION = []; // Clear all data // Delete session cookie if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly'] ); } session_destroy(); // Destroy session file
Session Variables
$_SESSION is a superglobal associative array that persists data across page requests; you can store any serializable PHP value including strings, arrays, and objects.
session_start(); // Set values $_SESSION['username'] = 'john'; $_SESSION['cart'] = ['item1', 'item2']; $_SESSION['user'] = ['id' => 1, 'role' => 'admin']; // Get values (with defaults) $username = $_SESSION['username'] ?? 'Guest'; // Check if exists if (isset($_SESSION['logged_in'])) { // User is logged in } // Remove specific variable unset($_SESSION['cart']); // Clear all $_SESSION = [];
Session Configuration
Configure sessions via php.ini or ini_set() before session_start() to control storage location, cookie parameters, garbage collection, and security settings.
// Before session_start() ini_set('session.cookie_httponly', 1); // Prevent JS access ini_set('session.cookie_secure', 1); // HTTPS only ini_set('session.use_strict_mode', 1); // Reject uninitialized IDs ini_set('session.cookie_samesite', 'Lax'); // CSRF protection // Session name and save path session_name('MYAPP_SESSION'); session_save_path('/var/sessions'); // Lifetime ini_set('session.gc_maxlifetime', 3600); // Server-side ini_set('session.cookie_lifetime', 0); // Browser session session_start(); // Regenerate ID periodically (prevents fixation) session_regenerate_id(true);
Custom Session Handlers
Implement SessionHandlerInterface to store sessions in databases, Redis, or other backends instead of files, enabling horizontal scaling and custom persistence logic.
class RedisSessionHandler implements SessionHandlerInterface { private Redis $redis; public function open($path, $name): bool { $this->redis = new Redis(); return $this->redis->connect('127.0.0.1'); } public function close(): bool { return true; } public function read($id): string|false { return $this->redis->get("session:$id") ?: ''; } public function write($id, $data): bool { return $this->redis->setex("session:$id", 3600, $data); } public function destroy($id): bool { return $this->redis->del("session:$id") > 0; } public function gc($max_lifetime): int|false { return 0; } } session_set_save_handler(new RedisSessionHandler(), true); session_start();
Session Security
Protect sessions by regenerating IDs after login, using secure cookie flags, validating user agent/IP, and implementing proper timeout mechanisms.
session_start(); // Regenerate after privilege change function login($user) { session_regenerate_id(true); // Delete old session $_SESSION['user_id'] = $user['id']; $_SESSION['created'] = time(); $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT']; } // Validate session function validateSession(): bool { if (!isset($_SESSION['user_id'])) return false; // Check timeout (30 minutes) if (time() - $_SESSION['created'] > 1800) { session_destroy(); return false; } // Validate fingerprint if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) { session_destroy(); return false; } return true; }
Creating and Reading Cookies
Use setcookie() to create cookies (before any output) with name, value, expiration, path, domain, and security flags;
read them via the $_COOKIE superglobal.
// Set cookie (must be before ANY output) setcookie('theme', 'dark', time() + 86400 * 30, '/'); // 30 days // Modern array syntax (PHP 7.3+) setcookie('preference', 'value', [ 'expires' => time() + 86400, 'path' => '/', 'domain' => '.example.com', 'secure' => true, 'httponly' => true, 'samesite' => 'Lax' ]); // Read cookie $theme = $_COOKIE['theme'] ?? 'light'; // Delete cookie (set expiration in past) setcookie('theme', '', time() - 3600, '/'); // Note: $_COOKIE won't show new cookies until next request
Cookie Security
Secure cookies using HttpOnly (prevents JS access), Secure (HTTPS only), SameSite (CSRF protection), and proper expiration; never store sensitive data directly in cookies.
// Secure cookie settings setcookie('session_id', $value, [ 'expires' => time() + 3600, 'path' => '/', 'secure' => true, // HTTPS only 'httponly' => true, // No JavaScript access 'samesite' => 'Strict' // Strict CSRF protection ]); // For sensitive data, store reference only $token = bin2hex(random_bytes(32)); setcookie('remember_token', $token, [...]); // Store actual data server-side, keyed by token // Never do this: // setcookie('user_data', json_encode(['admin' => true]));
┌─────────────────────────────────────────────────────┐ │ Cookie Security Flags │ ├──────────────┬──────────────────────────────────────┤ │ Secure │ Only sent over HTTPS │ │ HttpOnly │ Not accessible via JavaScript │ │ SameSite │ Strict/Lax/None - CSRF protection │ │ Path/Domain │ Scope restriction │ └──────────────┴──────────────────────────────────────┘
Session vs Cookies
Sessions store data server-side (more secure, unlimited size) with a client-side ID cookie, while cookies store data client-side (limited to ~4KB, visible/modifiable by users).
┌─────────────────────────────────────────────────────────┐ │ Sessions vs Cookies │ ├─────────────────┬───────────────────┬───────────────────┤ │ Feature │ Session │ Cookie │ ├─────────────────┼───────────────────┼───────────────────┤ │ Storage │ Server-side │ Client-side │ │ Size limit │ None (practical) │ ~4KB per cookie │ │ Security │ More secure │ Less secure │ │ Expiration │ Server-controlled │ Client-controlled │ │ Performance │ Server memory/IO │ Network overhead │ │ Use cases │ Auth, cart, data │ Preferences, ID │ └─────────────────┴───────────────────┴───────────────────┘ Session: Browser ←→ (session_id cookie) ←→ Server (data) Cookie: Browser (data) ←→ Server (receives data)
HTTP and Web Concepts
HTTP Headers
Use header() to send HTTP headers for redirects, content types, caching, and security policies; must be called before
any output including whitespace.
// Content type header('Content-Type: application/json'); header('Content-Type: text/html; charset=utf-8'); // Security headers header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); header('Content-Security-Policy: default-src \'self\''); header('Strict-Transport-Security: max-age=31536000'); // Caching header('Cache-Control: no-cache, no-store, must-revalidate'); header('Cache-Control: max-age=3600'); // Custom headers header('X-Custom-Header: value'); // Remove default headers header_remove('X-Powered-By'); // Check if headers already sent if (!headers_sent($file, $line)) { header('Location: /'); }
Request and Response Cycle
PHP runs within a request-response cycle: the server receives an HTTP request, PHP processes it (accessing superglobals), generates output, and sends an HTTP response back.
Usage
Start Server
php -S localhost:8000
API Endpoints
| Method | URL | Description |
|---|---|---|
| GET | /api.php | Get all items |
| GET | /api.php?id=1 | Get single item |
| POST | /api.php | Create item |
| PUT | /api.php?id=1 | Update item |
| DELETE | /api.php?id=1 | Delete item |
Test with cURL
# Create curl -X POST http://localhost:8000/api.php \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@example.com"}' # Read all curl http://localhost:8000/api.php # Read one curl http://localhost:8000/api.php?id=1 # Update curl -X PUT http://localhost:8000/api.php?id=1 \ -H "Content-Type: application/json" \ -d '{"name":"Jane","email":"jane@example.com"}' # Delete curl -X DELETE http://localhost:8000/api.php?id=1
Sample Responses
// POST Response (201) { "id": 1, "name": "John", "email": "john@example.com" } // GET all Response (200) [ { "id": 1, "name": "John", "email": "john@example.com" } ] // Error Response (404) { "error": "Not found" }