you bind the string value 'name' to the parameter in the sql. That means that for each row processed, the SQL will see the same string, namely 'name'.
The point is that 'name' is not interpreted as the Literal name which matches the column name, but a VARCHAR value 'name' which has been set or bound to a replacement variable ?.
In this case, if you have a variable ORDER BY clause, you would have two (or more) versions of your SQL, which you can select with a regular if / then / else.
Another way is to concatenate the sorting column in your string directly rather than using bind variables. I would STRONGLY suggest against as this lays the foundation work for future SQL Injection. Either way, if you concatenate a variable input string to your SQL, then make sure it is sufficiently sanitized and checked to avoid issues.
Concatenation in PHP would be something simple like this:
$orderby = "name";
$stmt = $conn->prepare("SELECT id, name FROM user order by ".$orderby." desc");
$stmt->execute();
See also PHP - concatenate or directly insert variables in string (had to correct, used initially a syntax that only worked for the PHP echo command).
All the other Implementing Languages (C#, Java, etc) combined with any database (oracle, MySQL, TSQL, etc) would face same issues and solutions: You will have to make the distinction between bind variables and literals in your SQL.
If you still want to use a bind variable - there is a way, but you would have to modify your sql as follows:
SELECT id, name FROM user
ORDER BY CASE ? WHEN 'name' THEN name WHEN 'id' THEN id ELSE NULL END
Actually good thinking by you (in your comment) to still use bind variables somehow. Now I do not have the issue with SQL Injection anymore either. However, now you need to account for every possible sorting that can happen, and have that coded in your SQL instead.