Comparing two secrets with === leaks how much of the secret you got right. String comparison returns as soon as it finds a differing byte, so a token whose first character is wrong is rejected marginally faster than one whose first eight characters are correct. Over enough requests that difference is measurable, and an attacker can recover the value one byte at a time.
// Leaks the position of the first mismatch.
if ($provided === $expected) { /* ... */ }
// Constant time in the length of $expected.
if (hash_equals($expected, $provided)) { /* ... */ }
hash_equals() arrived in 5.6 and takes the known value first, the user-supplied value second — the argument order matters, because the function runs in time proportional to the first argument. Use it for password reset tokens, API keys, webhook signatures and remember-me cookies. It is not for hashing passwords; password_verify() already compares in constant time.