What are the best practices for creating a class that extends MySQLi in PHP while ensuring modularity and functionality?

When creating a class that extends MySQLi in PHP, it is important to ensure modularity and functionality by following best practices. This includes encapsulating database operations within the class, using prepared statements to prevent SQL injection, and implementing error handling to gracefully handle exceptions.

class CustomMySQLi extends MySQLi {
    public function __construct($host, $username, $password, $database) {
        parent::__construct($host, $username, $password, $database);
        if ($this->connect_error) {
            die('Connection error: ' . $this->connect_error);
        }
    }

    public function executeQuery($query, $params = []) {
        $stmt = $this->prepare($query);
        if (!$stmt) {
            die('Query error: ' . $this->error);
        }

        if (!empty($params)) {
            $types = str_repeat('s', count($params));
            $stmt->bind_param($types, ...$params);
        }

        $stmt->execute();
        $result = $stmt->get_result();
        $stmt->close();

        return $result->fetch_all(MYSQLI_ASSOC);
    }
}

// Example usage
$db = new CustomMySQLi('localhost', 'username', 'password', 'database');
$results = $db->executeQuery('SELECT * FROM users WHERE id = ?', [1]);

foreach ($results as $row) {
    echo $row['username'] . '<br>';
}