What are the best practices for handling prepared statements and parameter binding in PHP when working with custom database classes?

When working with custom database classes in PHP, it is important to use prepared statements and parameter binding to prevent SQL injection attacks. This involves preparing the SQL query with placeholders for parameters, then binding the actual parameter values to the placeholders before executing the query. This helps sanitize user input and ensures that the query is executed safely.

// Example of using prepared statements and parameter binding in a custom database class

class CustomDatabase {
    private $connection;

    public function __construct($host, $username, $password, $dbname) {
        $this->connection = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
    }

    public function executeQuery($sql, $params) {
        $stmt = $this->connection->prepare($sql);
        
        foreach ($params as $key => $value) {
            $stmt->bindValue(":$key", $value);
        }
        
        $stmt->execute();
        
        return $stmt;
    }
}

// Example usage
$db = new CustomDatabase('localhost', 'username', 'password', 'dbname');
$sql = "SELECT * FROM users WHERE username = :username";
$params = array('username' => 'john_doe');
$result = $db->executeQuery($sql, $params);