PDO, Sessions, and Authentication¶
PDO (PHP Data Objects) provides a consistent interface for database access with prepared statements to prevent SQL injection. Sessions store user state across requests via cookies. Authentication combines password hashing (password_hash/password_verify), session management, and CSRF protection. File uploads use move_uploaded_file with validation.
Key Facts¶
- PDO supports MySQL, PostgreSQL, SQLite, etc. - same API, different DSN
- Always use prepared statements -
$stmt->execute([$param])prevents SQL injection password_hash($pass, PASSWORD_DEFAULT)generates bcrypt hash (auto-salted)password_verify($input, $hash)checks against stored hash$_SESSIONpersists across requests aftersession_start(); flash messages = set + unset after displaymove_uploaded_file()validates temp path; always validate type/size before accepting
Patterns¶
PDO Database Wrapper¶
class Database {
private \PDO $pdo;
public function __construct(string $host, string $db, string $user, string $pass) {
$dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";
$this->pdo = new \PDO($dsn, $user, $pass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
]);
}
public function query(string $sql, array $params = []): \PDOStatement {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function findOne(string $table, int $id): array|false {
return $this->query("SELECT * FROM `$table` WHERE id = ?", [$id])->fetch();
}
public function lastInsertId(): string {
return $this->pdo->lastInsertId();
}
}
Session and Flash Messages¶
session_start();
// Flash messages (set in controller, display in view, auto-clear)
class Session {
public function setFlash(string $key, string $message): void {
$_SESSION['flash'][$key] = $message;
}
public function getFlash(string $key): ?string {
$msg = $_SESSION['flash'][$key] ?? null;
unset($_SESSION['flash'][$key]);
return $msg;
}
}
Authentication¶
// Registration
$hash = password_hash($password, PASSWORD_DEFAULT);
$db->query("INSERT INTO users (email, password) VALUES (?, ?)", [$email, $hash]);
// Login
$user = $db->query("SELECT * FROM users WHERE email = ?", [$email])->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard');
}
// Auth check helper
function isAuth(): bool { return isset($_SESSION['user_id']); }
function isAdmin(): bool { return ($_SESSION['role'] ?? '') === 'admin'; }
File Upload¶
function uploadFile(array $file): string {
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $ext;
$dir = 'uploads/' . date('Y/m/d');
if (!is_dir($dir)) mkdir($dir, 0755, true);
move_uploaded_file($file['tmp_name'], "$dir/$filename");
return "/$dir/$filename";
}
Gotchas¶
- Never store plain text passwords - always
password_hash()withPASSWORD_DEFAULT PDO::ERRMODE_EXCEPTIONis essential - without it, query failures return false silentlysession_start()must be called before any output (even whitespace)- PHPMailer's
addAttachment()needs filesystem path, not URL
See Also¶
- php type system - type safety in database operations
- mvc framework - Model/Database classes using PDO
- laravel authentication - Laravel's auth scaffolding (built on same principles)