<?php
class UserManager {
private $connection;
public function __construct($host, $username, $password, $database) {
$this->connection = new mysqli($host, $username, $password, $database);
if ($this->connection->connect_error) {
die("Connection failed: " . $this->connection->connect_error);
}
// Set charset to prevent character set confusion attacks
$this->connection->set_charset("utf8mb4");
}
private function validateUserId($userId) {
if (!is_numeric($userId) || $userId <= 0) {
throw new InvalidArgumentException("Invalid user ID");
}
return (int)$userId;
}
private function validateEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 100) {
throw new InvalidArgumentException("Invalid email format");
}
return trim($email);
}
private function validateDepartment($department) {
$validDepartments = ['IT', 'HR', 'Finance', 'Marketing', 'Sales'];
if (!in_array($department, $validDepartments)) {
throw new InvalidArgumentException("Invalid department: $department");
}
return $department;
}
// SECURE: Prepared statement with parameter binding
public function getUserById($userIdStr) {
$userId = $this->validateUserId($userIdStr);
$stmt = $this->connection->prepare("SELECT id, name, email, department, status FROM users WHERE id = ?");
if (!$stmt) {
throw new RuntimeException("Prepare failed: " . $this->connection->error);
}
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
$stmt->close();
return $user;
}
// SECURE: Parameterized query with validation
public function searchUsers($name, $department) {
// Input validation
if (strlen($name) > 100) {
throw new InvalidArgumentException("Search name too long");
}
if (!empty($department)) {
$department = $this->validateDepartment($department);
}
// Build query dynamically with parameters
$conditions = ["1=1"];
$types = "";
$params = [];
if (!empty($name)) {
$conditions[] = "name LIKE ?";
$types .= "s";
$params[] = "%$name%";
}
if (!empty($department)) {
$conditions[] = "department = ?";
$types .= "s";
$params[] = $department;
}
$query = "SELECT id, name, email, department FROM users WHERE " .
implode(" AND ", $conditions) . " LIMIT 100";
$stmt = $this->connection->prepare($query);
if (!$stmt) {
throw new RuntimeException("Prepare failed: " . $this->connection->error);
}
if (!empty($params)) {
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
$result = $stmt->get_result();
$users = [];
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
$stmt->close();
return $users;
}
// SECURE: Prepared statement for inserts
public function createUser($name, $email, $department) {
// Input validation
if (empty($name) || strlen($name) > 100) {
throw new InvalidArgumentException("Invalid name");
}
$email = $this->validateEmail($email);
$department = $this->validateDepartment($department);
$stmt = $this->connection->prepare(
"INSERT INTO users (name, email, department, created_at) VALUES (?, ?, ?, NOW())"
);
if (!$stmt) {
throw new RuntimeException("Prepare failed: " . $this->connection->error);
}
$stmt->bind_param("sss", $name, $email, $department);
$success = $stmt->execute();
if ($success) {
$userId = $this->connection->insert_id;
$stmt->close();
return $userId;
} else {
$error = $stmt->error;
$stmt->close();
throw new RuntimeException("Insert failed: $error");
}
}
// SECURE: Whitelisted sort columns
public function getUsersSorted($sortBy, $direction) {
$validSortColumns = [
'name' => 'name',
'email' => 'email',
'department' => 'department',
'created' => 'created_at'
];
$validDirections = ['ASC', 'DESC'];
if (!isset($validSortColumns[$sortBy])) {
throw new InvalidArgumentException("Invalid sort column: $sortBy");
}
$direction = strtoupper($direction);
if (!in_array($direction, $validDirections)) {
throw new InvalidArgumentException("Invalid sort direction: $direction");
}
$column = $validSortColumns[$sortBy];
// Safe because both column and direction are whitelisted
$query = "SELECT id, name, email, department FROM users ORDER BY $column $direction LIMIT 100";
$result = $this->connection->query($query);
if (!$result) {
throw new RuntimeException("Query failed: " . $this->connection->error);
}
$users = [];
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
return $users;
}
// SECURE: Parameterized update with validation
public function updateUserStatus($userEmail, $newStatus) {
$email = $this->validateEmail($userEmail);
$validStatuses = ['active', 'inactive', 'suspended', 'pending'];
if (!in_array($newStatus, $validStatuses)) {
throw new InvalidArgumentException("Invalid status: $newStatus");
}
$stmt = $this->connection->prepare(
"UPDATE users SET status = ?, updated_at = NOW() WHERE email = ?"
);
if (!$stmt) {
throw new RuntimeException("Prepare failed: " . $this->connection->error);
}
$stmt->bind_param("ss", $newStatus, $email);
$success = $stmt->execute();
$affectedRows = $stmt->affected_rows;
$stmt->close();
if (!$success) {
throw new RuntimeException("Update failed: " . $this->connection->error);
}
return $affectedRows > 0;
}
// Example with transaction support
public function batchUpdateStatuses($updates) {
$this->connection->autocommit(FALSE);
try {
$stmt = $this->connection->prepare(
"UPDATE users SET status = ?, updated_at = NOW() WHERE email = ?"
);
if (!$stmt) {
throw new RuntimeException("Prepare failed: " . $this->connection->error);
}
$totalUpdated = 0;
foreach ($updates as $update) {
$email = $this->validateEmail($update['email']);
$status = $update['status'];
$validStatuses = ['active', 'inactive', 'suspended', 'pending'];
if (!in_array($status, $validStatuses)) {
throw new InvalidArgumentException("Invalid status: $status");
}
$stmt->bind_param("ss", $status, $email);
$stmt->execute();
$totalUpdated += $stmt->affected_rows;
}
$stmt->close();
$this->connection->commit();
$this->connection->autocommit(TRUE);
return $totalUpdated;
} catch (Exception $e) {
$this->connection->rollback();
$this->connection->autocommit(TRUE);
throw $e;
}
}
public function __destruct() {
if ($this->connection) {
$this->connection->close();
}
}
}
// Safe usage examples:
try {
$userManager = new UserManager("localhost", "user", "password", "testdb");
// These are now safe:
$user = $userManager->getUserById("123");
$users = $userManager->searchUsers("John", "IT");
$newUserId = $userManager->createUser("Jane Doe", "jane@example.com", "HR");
$sorted = $userManager->getUsersSorted("name", "ASC");
echo "Operations completed successfully
";
} catch (InvalidArgumentException $e) {
echo "Validation error: " . $e->getMessage() . "
";
} catch (RuntimeException $e) {
echo "Database error: " . $e->getMessage() . "
";
} catch (Exception $e) {
echo "Unexpected error: " . $e->getMessage() . "
";
}
?>