PHP and MySQL Integration
Learning Objectives
- Understand PDO vs MySQLi differences and use cases
- Establish secure database connections
- Execute queries safely with prepared statements
- Handle database errors and exceptions
- Implement transaction management in PHP
- Prevent SQL injection attacks
- Manage connection pooling and performance
- Build database abstraction layers
PDO vs MySQLi Comparison
PHP offers two main ways to connect to MySQL: PDO (PHP Data Objects) and MySQLi. Let's understand their differences! 🔌
Quick Decision Guide
PDO Connection and Basic Operations
graph TB
CONNECT[Connect to DB] --> PREPARE[Prepare Statement]
PREPARE --> BIND[Bind Parameters]
BIND --> EXECUTE[Execute Query]
EXECUTE --> FETCH[Fetch Results]
FETCH --> CLOSE[Close Connection]
EXECUTE --> ERROR[Handle Errors]
ERROR --> LOG[Log Error]
style CONNECT fill:#dcfce7
style PREPARE fill:#dbeafe
style EXECUTE fill:#fef3c7
style ERROR fill:#fee2e2
PDO Examples
<?php
// ========================================
// PDO CONNECTION
// ========================================
// Configuration
$config = [
'host' => 'localhost',
'dbname' => 'myapp',
'username' => 'dbuser',
'password' => 'securepass123',
'charset' => 'utf8mb4'
];
try {
// Create connection
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset={$config['charset']}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $config['username'], $config['password'], $options);
} catch (PDOException $e) {
// Log error (don't expose details to users)
error_log("Connection failed: " . $e->getMessage());
die("Database connection error");
}
// ========================================
// SELECT QUERY WITH PREPARED STATEMENTS
// ========================================
try {
// Prepare statement with named parameters
$sql = "SELECT * FROM users WHERE email = :email AND status = :status";
$stmt = $pdo->prepare($sql);
// Bind and execute
$stmt->execute([
':email' => 'user@example.com',
':status' => 'active'
]);
// Fetch single row
$user = $stmt->fetch();
// Or fetch all rows
$users = $stmt->fetchAll();
// Fetch column
$sql = "SELECT COUNT(*) FROM users WHERE status = :status";
$stmt = $pdo->prepare($sql);
$stmt->execute([':status' => 'active']);
$count = $stmt->fetchColumn();
} catch (PDOException $e) {
error_log("Query failed: " . $e->getMessage());
}
// ========================================
// INSERT WITH PREPARED STATEMENTS
// ========================================
try {
$sql = "INSERT INTO users (username, email, password_hash, created_at)
VALUES (:username, :email, :password, NOW())";
$stmt = $pdo->prepare($sql);
$result = $stmt->execute([
':username' => 'johndoe',
':email' => 'john@example.com',
':password' => password_hash('secret123', PASSWORD_DEFAULT)
]);
if ($result) {
$userId = $pdo->lastInsertId();
echo "User created with ID: " . $userId;
}
} catch (PDOException $e) {
error_log("Insert failed: " . $e->getMessage());
}
// ========================================
// UPDATE WITH PREPARED STATEMENTS
// ========================================
try {
$sql = "UPDATE users
SET last_login = NOW(), login_count = login_count + 1
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':id' => $userId]);
$affectedRows = $stmt->rowCount();
echo "Updated $affectedRows rows";
} catch (PDOException $e) {
error_log("Update failed: " . $e->getMessage());
}
// ========================================
// TRANSACTIONS
// ========================================
try {
$pdo->beginTransaction();
// Debit account
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE id = :id");
$stmt->execute([':amount' => 100, ':id' => 1]);
// Credit account
$stmt = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE id = :id");
$stmt->execute([':amount' => 100, ':id' => 2]);
// Log transaction
$stmt = $pdo->prepare("INSERT INTO transactions (from_id, to_id, amount) VALUES (?, ?, ?)");
$stmt->execute([1, 2, 100]);
$pdo->commit();
echo "Transaction successful";
} catch (Exception $e) {
$pdo->rollBack();
error_log("Transaction failed: " . $e->getMessage());
}
// ========================================
// FETCH MODES
// ========================================
// Fetch as object
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_OBJ);
echo $user->email; // Object property access
// Fetch into class
class User {
public $id;
public $username;
public $email;
}
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();
// Fetch key-value pairs
$sql = "SELECT id, username FROM users";
$users = $pdo->query($sql)->fetchAll(PDO::FETCH_KEY_PAIR);
MySQLi Connection and Operations
MySQLi Examples
<?php
// ========================================
// MYSQLI CONNECTION (OBJECT-ORIENTED)
// ========================================
$mysqli = new mysqli('localhost', 'username', 'password', 'database');
// Check connection
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Set charset
$mysqli->set_charset("utf8mb4");
// ========================================
// MYSQLI PREPARED STATEMENTS
// ========================================
// SELECT query
$sql = "SELECT id, username, email FROM users WHERE status = ? AND role = ?";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
// Bind parameters (s = string, i = integer, d = double, b = blob)
$status = 'active';
$role = 'admin';
$stmt->bind_param("ss", $status, $role);
// Execute
$stmt->execute();
// Bind result variables
$stmt->bind_result($id, $username, $email);
// Fetch results
while ($stmt->fetch()) {
echo "User: $username ($email)\n";
}
$stmt->close();
}
// ========================================
// INSERT WITH MYSQLI
// ========================================
$sql = "INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)";
$stmt = $mysqli->prepare($sql);
if ($stmt) {
$username = 'johndoe';
$email = 'john@example.com';
$password = password_hash('secret123', PASSWORD_DEFAULT);
$stmt->bind_param("sss", $username, $email, $password);
if ($stmt->execute()) {
$userId = $mysqli->insert_id;
echo "User created with ID: " . $userId;
} else {
echo "Error: " . $stmt->error;
}
$stmt->close();
}
// ========================================
// MYSQLI TRANSACTIONS
// ========================================
// Start transaction
$mysqli->autocommit(FALSE);
try {
// Multiple queries
$mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$mysqli->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
// Check for errors
if ($mysqli->error) {
throw new Exception($mysqli->error);
}
// Commit transaction
$mysqli->commit();
echo "Transaction successful";
} catch (Exception $e) {
// Rollback on error
$mysqli->rollback();
echo "Transaction failed: " . $e->getMessage();
}
// Reset autocommit
$mysqli->autocommit(TRUE);
// ========================================
// PROCEDURAL STYLE
// ========================================
$connection = mysqli_connect('localhost', 'username', 'password', 'database');
if (!$connection) {
die("Connection failed: " . mysqli_connect_error());
}
// Query
$result = mysqli_query($connection, "SELECT * FROM users");
// Fetch results
while ($row = mysqli_fetch_assoc($result)) {
echo $row['username'] . "\n";
}
// Free result
mysqli_free_result($result);
// Close connection
mysqli_close($connection);
// ========================================
// CALLING STORED PROCEDURES
// ========================================
// Call procedure with OUT parameters
$stmt = $mysqli->prepare("CALL GetUserStats(?, @total_orders, @total_spent)");
$userId = 123;
$stmt->bind_param("i", $userId);
$stmt->execute();
// Get OUT parameters
$result = $mysqli->query("SELECT @total_orders, @total_spent");
$row = $result->fetch_row();
echo "Orders: {$row[0]}, Spent: {$row[1]}";
// ========================================
// MULTI-QUERY
// ========================================
$sql = "SELECT * FROM users; SELECT * FROM orders";
if ($mysqli->multi_query($sql)) {
do {
// Process result set
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_assoc()) {
// Process row
}
$result->free();
}
} while ($mysqli->next_result());
}
Security Best Practices
Security Implementation
<?php
// ========================================
// INPUT VALIDATION AND SANITIZATION
// ========================================
class DatabaseSecurity {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
/**
* Safe user authentication
*/
public function authenticateUser($email, $password) {
// Validate email
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return false;
}
// Prepare statement
$stmt = $this->pdo->prepare(
"SELECT id, username, password_hash
FROM users
WHERE email = :email
AND status = 'active'
LIMIT 1"
);
$stmt->execute([':email' => $email]);
$user = $stmt->fetch();
// Verify password
if ($user && password_verify($password, $user['password_hash'])) {
// Update last login
$this->updateLastLogin($user['id']);
// Return user data (exclude password)
unset($user['password_hash']);
return $user;
}
// Log failed attempt
$this->logFailedLogin($email);
return false;
}
/**
* Prevent SQL injection in dynamic queries
*/
public function safeSearch($searchTerm, $category = null) {
// Base query
$sql = "SELECT * FROM products WHERE 1=1";
$params = [];
// Add search condition
if (!empty($searchTerm)) {
// Sanitize for LIKE query
$searchTerm = str_replace(['%', '_'], ['\%', '\_'], $searchTerm);
$sql .= " AND (name LIKE :search OR description LIKE :search)";
$params[':search'] = "%{$searchTerm}%";
}
// Add category filter
if ($category !== null) {
// Validate category is integer
if (!is_numeric($category)) {
throw new InvalidArgumentException("Invalid category");
}
$sql .= " AND category_id = :category";
$params[':category'] = (int)$category;
}
// Execute safely
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* Safe dynamic ORDER BY
*/
public function getUsers($orderBy = 'id', $direction = 'ASC') {
// Whitelist allowed columns
$allowedColumns = ['id', 'username', 'email', 'created_at'];
$allowedDirections = ['ASC', 'DESC'];
// Validate input
if (!in_array($orderBy, $allowedColumns)) {
$orderBy = 'id';
}
if (!in_array(strtoupper($direction), $allowedDirections)) {
$direction = 'ASC';
}
// Safe to use now (not from user input directly)
$sql = "SELECT * FROM users ORDER BY {$orderBy} {$direction}";
return $this->pdo->query($sql)->fetchAll();
}
/**
* Prevent mass assignment
*/
public function updateUser($userId, array $data) {
// Whitelist allowed fields
$allowedFields = ['username', 'email', 'phone'];
// Filter data
$filtered = array_intersect_key(
$data,
array_flip($allowedFields)
);
// Build UPDATE query
$fields = [];
foreach ($filtered as $key => $value) {
$fields[] = "{$key} = :{$key}";
}
if (empty($fields)) {
return false;
}
$sql = "UPDATE users SET " . implode(', ', $fields) .
" WHERE id = :id";
$filtered['id'] = $userId;
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($filtered);
}
/**
* Rate limiting
*/
private function checkRateLimit($identifier) {
$stmt = $this->pdo->prepare(
"SELECT COUNT(*) FROM login_attempts
WHERE ip_address = :ip
AND attempted_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE)"
);
$stmt->execute([':ip' => $identifier]);
$attempts = $stmt->fetchColumn();
if ($attempts > 5) {
throw new Exception("Too many attempts. Please try later.");
}
}
/**
* Log security events
*/
private function logSecurityEvent($event, $details) {
$stmt = $this->pdo->prepare(
"INSERT INTO security_log (event_type, details, ip_address, created_at)
VALUES (:event, :details, :ip, NOW())"
);
$stmt->execute([
':event' => $event,
':details' => json_encode($details),
':ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
]);
}
}
// ========================================
// CONNECTION SECURITY
// ========================================
// Store credentials outside web root
// config/database.php (above public_html)
return [
'host' => getenv('DB_HOST'),
'dbname' => getenv('DB_NAME'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-cert.pem',
]
];
// ========================================
// ERROR HANDLING
// ========================================
class DatabaseConnection {
private static $instance = null;
private $pdo;
private function __construct() {
try {
$config = require '../config/database.php';
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4";
$this->pdo = new PDO(
$dsn,
$config['username'],
$config['password'],
$config['options']
);
} catch (PDOException $e) {
// Log error, don't expose details
error_log("Database connection failed: " . $e->getMessage());
// Show user-friendly error
if (PHP_SAPI === 'cli') {
die("Database connection error\n");
} else {
http_response_code(500);
include '../templates/error-500.php';
exit;
}
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance->pdo;
}
// Prevent cloning
private function __clone() {}
// Prevent unserialization
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}
Practice Exercise
PHP MySQL Integration Challenges