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 array

View on GitHub (pinned to 6ab7b0404d)

Solutions

  1. 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}$/.
  2. If you need a custom scheme, define the function/closure before constructing the detector and verify with is_callable($fn).
  3. 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

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


AI-assisted analysis of serbanghita/Mobile-Detect@6ab7b0404d (2026-08-21). Data as JSON: /api/errors/a1ad5378142214cc. Report an issue: GitHub.