Skip to main content

Course Progress

Loading...

PHP and MySQL Integration

Duration: 95 minutes
Module 3: Session 5

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:Better for new projects, portable code, multiple databases
  • MySQLi:Better for MySQL-specific features, slight performance edge
  • Both support prepared statements (essential for security)
  • WordPress uses its own $wpdb abstraction (based on MySQLi)

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
Build these PHP applications:
  1. Create a secure user registration and login system
  2. Build a CRUD application with PDO
  3. Implement a shopping cart with transactions
  4. Create a search system with pagination
  5. Build an admin panel with role-based access
  6. Implement database backup and restore
  7. Create an API with database integration
  8. Build a database migration system

Additional Resources