yiisoft/yii2 · error · InvalidArgumentException
Expected expected value to be a string,
Error message
Expected expected value to be a string,
What it means
Thrown by yii\base\Security::compareString() when the $expected argument (the trusted/reference side of a timing-attack-safe comparison) is not a PHP string. The method wraps hash_equals(), which requires strings, so it guards both arguments with is_string() first. The message appends gettype() of the offending value, e.g. 'Expected expected value to be a string, NULL given.'
Source
Thrown at framework/base/Security.php:547
// Form the prefix that specifies Blowfish (bcrypt) algorithm and cost parameter.
$salt = sprintf('$2y$%02d$', $cost);
// Append the random salt data in the required base64 format.
$salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));
return $salt;
}
/**
* Performs string comparison using timing attack resistant approach.
* @see https://codereview.stackexchange.com/q/13512
* @param string $expected string to compare.
* @param string $actual user-supplied string.
* @return bool whether strings are equal.
*/
public function compareString($expected, $actual)
{
if (!is_string($expected)) {
throw new InvalidArgumentException('Expected expected value to be a string, ' . gettype($expected) . ' given.');
}
if (!is_string($actual)) {
throw new InvalidArgumentException('Expected actual value to be a string, ' . gettype($actual) . ' given.');
}
return hash_equals($expected, $actual);
}
/**
* Masks a token to make it uncompressible.
* Applies a random mask to the token and prepends the mask used to the result making the string always unique.
* Used to mitigate BREACH attack by randomizing how token is outputted on each request.
* @param string $token An unmasked token.
* @return string A masked token.
* @since 2.0.12
*/
public function maskToken($token)View on GitHub (pinned to 66f00d18a2)
Solutions
- Read the gettype() named in the message and trace where that $expected value comes from.
- Fix the source: correct the config key path, add a NOT NULL default to the column, or regenerate/seed the missing secret.
- If absence is legitimate, short-circuit before calling: treat a non-string expected value as an automatic mismatch instead of an exception.
- For nullable values use is_string($expected) && Yii::$app->security->compareString($expected, $actual).
Example fix
// before
$valid = Yii::$app->security->compareString($user->api_secret, $requestSecret);
// $user->api_secret is NULL for rows created before the column existed
// after
$valid = is_string($user->api_secret)
&& Yii::$app->security->compareString($user->api_secret, (string) $requestSecret); Defensive patterns
Strategy: type-guard
Validate before calling
if (!is_string($expected)) {
// regenerate/refresh the secret, or treat as an automatic mismatch
return false;
}
return Yii::$app->security->compareString($expected, $actual); Type guard
function assertComparableSecret($value): string
{
if (!is_string($value)) {
throw new \InvalidArgumentException(
'Expected secret to be a string, ' . gettype($value) . ' given.'
);
}
return $value;
} Prevention
- Never feed nullable lookups straight into compareString(); check is_string() first
- Define secrets as NOT NULL with sane defaults so the reference side is always a string
- Fail loudly at boot if required security keys are missing from config, instead of null at compare time
When it happens
Trigger: Calling compareString(null, $token) when a secret read from config with a wrong key returns null; comparing against an empty DB column (NULL api_secret, deleted cookie validation key); passing an int/float constant or an array/object as $expected; a json_decode() or unserialize() result used directly as the reference value.
Common situations: API-key or HMAC verification where the stored secret is null for legacy rows; cookie/security key missing from the production config; fixtures or seeds that leave token columns null; refactoring a constant to a config value that is not actually defined in that environment.
Related errors
- Expected actual value to be a string,
- Encryption requires the OpenSSL PHP extension
- {cipher} is not an allowed cipher
- Invalid parameters to hash_hkdf()
- Invalid parameters to hash_pbkdf2()
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/a0a4b7597ec73a59.
Report an issue: GitHub.