Это да, косяк mysqli, совершенно неочевиное поведение. Стейтмент не позволяет использовать станартный фетч. Нужно сначала получить результат запроса, а потом уже фетчить.
Код пишу в нормальном синтаксисе, потому что от вида этих бесконечных mysqli_stmt_bind_param_stmt_execute_mysqli_stmt_param_fetch_execute у меня начинают болеть зубы.
$stmt = $bd->prepare("SELECT * FROM `users` WHERE id=? ");
$stmt->bind_param('i', $id);
$stmt->execute();
$res = $stmt->get_result(); // вот это
$row = $res->fetch_assoc();
Вообще, пользоваться чистым синтаксисом mysqli может только мазохист. Поскольку это низкоуровневая библиотека, которая раскладывает каждую задачу на кучу мелких операций. Все эти операции нужны, без них нельзя обойтись. Но совершенно не оябзательно как попугай постоянно долдонить их в каждом запросе. Можно написать
функцию, которая будет выполнять их внутри.
$row = prepared_select($bd, "SELECT * FROM `users` WHERE id=?", [$id])->fetch_assoc();
Но самое, конечно, смешное - это что SELECT запрос тебе здесь не нужен.
Базы данных работают совсем по-другому. Тебе не надо перекладывать данные из одной таблички в другую. Твоя БД потому называтся
реляционной, что в ней можно связывать таблицы. И получать данные на основании связей. То есть в таблице messages не нужны ни имя. ни аватарка. Нужен только id юзера. А все его причиндалы ты получишь потом,
при выводе, из таблицы users, используй JOIN запрос.