{"record":{"id":"a1ad5378142214cc","repo":"serbanghita/Mobile-Detect","slug":"cachekeyfn-is-not-a-function","errorCode":null,"errorMessage":"cacheKeyFn is not a function.","messagePattern":"cacheKeyFn is not a function\\.","errorType":"exception","errorClass":"CacheException","httpStatus":null,"severity":"error","filePath":"src/MobileDetect.php","lineNumber":1733,"sourceCode":"    }\n\n    /**\n     * Creates the cache key string based on the defined fn.\n     * Function can be customized in the constructor. See `$config['cacheKeyFn']`.\n     *\n     * @throws CacheException\n     */\n    protected function createCacheKey(string $key): string\n    {\n        $userAgentKey = $this->hasUserAgent() ? $this->userAgent : '';\n        $httpHeadersKey = $this->hasHttpHeaders() ? static::flattenHeaders($this->httpHeaders) : '';\n\n        $cacheKey = \"$key:$userAgentKey:$httpHeadersKey\";\n\n        $cacheKeyFn = $this->config['cacheKeyFn'];\n\n        if (!is_callable($cacheKeyFn)) {\n            throw new CacheException('cacheKeyFn is not a function.');\n        }\n\n        return call_user_func($cacheKeyFn, $cacheKey);\n    }\n\n    public static function flattenHeaders(array $httpHeaders): string\n    {\n        $key = '';\n        foreach ($httpHeaders as $name => $value) {\n            $key .= \"$name: $value\" . PHP_EOL;\n        }\n        return trim($key);\n    }\n\n    /**\n     * Get the properties array.\n     *\n     * @return array","sourceCodeStart":1715,"sourceCodeEnd":1751,"githubUrl":"https://github.com/serbanghita/Mobile-Detect/blob/6ab7b0404df1da8da2aa2c56634362842d9c2a23/src/MobileDetect.php#L1715-L1751","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$detect = new MobileDetect(['cacheKeyFn' => 'base64']); // not a function\n$detect->isMobile(); // -> Cache problem in isMobile(): cacheKeyFn is not a function.\n\n// after\n$detect = new MobileDetect(['cacheKeyFn' => 'sha1']);\n// or custom:\n$detect = new MobileDetect(['cacheKeyFn' => fn(string $key): string => hash('sha256', $key)]);","handlingStrategy":"validation","validationCode":"$config = ['cacheKeyFn' => 'sha1']; // or a verified closure\nif (isset($customFn) && !is_callable($customFn)) {\n    throw new \\InvalidArgumentException('cacheKeyFn must be callable, e.g. sha1');\n}\n$detect = new MobileDetect($config);","typeGuard":"function isSafeCacheKeyFn(mixed $fn): bool\n{\n    if (!is_callable($fn)) { return false; }\n    $out = $fn('mobile:Mozilla/5.0 (Windows NT 10.0) HTTP_ACCEPT=text/html');\n    return is_string($out) && preg_match('/^[A-Za-z0-9_.]{1,64}$/', $out) === 1;\n}","tryCatchPattern":"use Detection\\Cache\\CacheException;\n\ntry {\n    $detect->isMobile();\n} catch (MobileDetectException $e) {\n    $prev = $e->getPrevious();\n    if ($prev instanceof CacheException && str_contains($prev->getMessage(), 'cacheKeyFn')) {\n        // rebuild the detector with default config and retry once\n        $fresh = new MobileDetect();\n        $fresh->setUserAgent($ua);\n        $isMobile = $fresh->isMobile();\n    }\n}","preventionTips":["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."],"tags":["cache","configuration","callable","mobile-detect","php"],"backgroundTag":"invalid-config-callback","analyzedSha":"6ab7b0404df1da8da2aa2c56634362842d9c2a23","analyzedAt":"2026-08-21T04:49:48.090Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}