String comparison short-circuits on the first differing byte, which leaks how much of a secret an attacker has guessed.
// timing varies with the length of the common prefix
if ($signature === $expected) { }
// constant time, for equal-length strings
if (hash_equals($expected, $signature)) { }
// the argument order matters for a different reason:
// hash_equals leaks the length of the FIRST argument,
// so the known-good value goes first.
The argument order is the detail that is almost always wrong in code that otherwise gets this right — the function is constant-time with respect to content and not with respect to length, so the expected value belongs first. It applies to signatures, tokens, API keys and anything else compared against a value from a request; it does not apply to a password, which should be going through password_verify instead.