serbanghita/Mobile-Detect · error · CacheException
cacheKeyFn is not a function.
Error message
cacheKeyFn is not a function.
What it means
createCacheKey() (src/MobileDetect.php:1733) builds the composite string 'rule:userAgent:flattenedHeaders' and passes it through the configured 'cacheKeyFn' callback; if that config value is not callable it throws CacheException 'cacheKeyFn is not a function.'. The composite contains colons and raw header text, so the callback (default 'sha1', src/MobileDetect.php:258) is what makes it a valid cache key — this exception means the hashing step is broken. Reached through isMobile()/isTablet()/is() it is wrapped as a 'Cache problem in ...' MobileDetectException with the respective code, so the raw form appears in the chained previous exception or when createCacheKey() is exercised directly.
Source
Thrown at src/MobileDetect.php:1733
}
/**
* Creates the cache key string based on the defined fn.
* Function can be customized in the constructor. See `$config['cacheKeyFn']`.
*
* @throws CacheException
*/
protected function createCacheKey(string $key): string
{
$userAgentKey = $this->hasUserAgent() ? $this->userAgent : '';
$httpHeadersKey = $this->hasHttpHeaders() ? static::flattenHeaders($this->httpHeaders) : '';
$cacheKey = "$key:$userAgentKey:$httpHeadersKey";
$cacheKeyFn = $this->config['cacheKeyFn'];
if (!is_callable($cacheKeyFn)) {
throw new CacheException('cacheKeyFn is not a function.');
}
return call_user_func($cacheKeyFn, $cacheKey);
}
public static function flattenHeaders(array $httpHeaders): string
{
$key = '';
foreach ($httpHeaders as $name => $value) {
$key .= "$name: $value" . PHP_EOL;
}
return trim($key);
}
/**
* Get the properties array.
*
* @return arrayView on GitHub (pinned to 6ab7b0404d)
Solutions
- Use a callable: 'sha1' (default, 40-char hex), 'md5', 'crc32b', or fn(string $k): string => hash('sha256', $k) — output must match /^[A-Za-z0-9_.]{1,64}$/.
- If you need a custom scheme, define the function/closure before constructing the detector and verify with is_callable($fn).
- If it already fired inside detection, catch MobileDetectException (IS_MOBILE_ERR/IS_TABLET_ERR/IS_MAGIC_ERR) and inspect getPrevious() — this CacheException is the cause.
Example fix
// before
$detect = new MobileDetect(['cacheKeyFn' => 'base64']); // not a function
$detect->isMobile(); // -> Cache problem in isMobile(): cacheKeyFn is not a function.
// after
$detect = new MobileDetect(['cacheKeyFn' => 'sha1']);
// or custom:
$detect = new MobileDetect(['cacheKeyFn' => fn(string $key): string => hash('sha256', $key)]); Defensive patterns
Strategy: validation
Validate before calling
$config = ['cacheKeyFn' => 'sha1']; // or a verified closure
if (isset($customFn) && !is_callable($customFn)) {
throw new \InvalidArgumentException('cacheKeyFn must be callable, e.g. sha1');
}
$detect = new MobileDetect($config); Type guard
function isSafeCacheKeyFn(mixed $fn): bool
{
if (!is_callable($fn)) { return false; }
$out = $fn('mobile:Mozilla/5.0 (Windows NT 10.0) HTTP_ACCEPT=text/html');
return is_string($out) && preg_match('/^[A-Za-z0-9_.]{1,64}$/', $out) === 1;
} Try / catch
use Detection\Cache\CacheException;
try {
$detect->isMobile();
} catch (MobileDetectException $e) {
$prev = $e->getPrevious();
if ($prev instanceof CacheException && str_contains($prev->getMessage(), 'cacheKeyFn')) {
// rebuild the detector with default config and retry once
$fresh = new MobileDetect();
$fresh->setUserAgent($ua);
$isMobile = $fresh->isMobile();
}
} Prevention
- Stick to 'sha1' unless you must customize; then prefer built-ins ('md5', hash closures) over string function names.
- Don't port 'base64' from pre-4.x examples — it is not a function and its output also violates the key charset.
- Unit-test the config you actually ship, including env-sourced values, so a null or 'base64' never reaches production.
When it happens
Trigger: new MobileDetect(['cacheKeyFn' => 'base64']) — 'base64' is not a PHP function (base64_encode is, and its output would still be an invalid key); 'cacheKeyFn' => 'my_hash' before defining my_hash(); a class-string of a class without __invoke; ['CacheKeyMaker', 'make'] where the class doesn't exist; an env-sourced value that arrives empty or null.
Common situations: Copy-pasting config from pre-4.x docs/issues — the source comment at src/MobileDetect.php:256-257 notes base64 was the old choice before moving to sha1; config loaded from .env/JSON where the callback name arrives as a string that's blank; refactors that move the hashing function after the detector's construction.
Related errors
- IS_MOBILE_ERR
- Cache key must be a string.
- Invalid key: '$key'. Must be alphanumeric, can contain _ and
- TTL must be null, int, or DateInterval.
- %s must be iterable.
AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21).
Data as JSON: /api/errors/a1ad5378142214cc.
Report an issue: GitHub.