{"record":{"id":"f6af558f6121d918","repo":"getgrav/grav","slug":"cache-key-must-be-string-s-given","errorCode":null,"errorMessage":"Cache key must be string, \"%s\" given","messagePattern":"Cache key must be string, \"(.+?)\" given","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Cache/CacheTrait.php","lineNumber":306,"sourceCode":"    {\n        $success = true;\n\n        foreach ($keys as $key) {\n            $success = $this->doDelete($key) && $success;\n        }\n\n        return $success;\n    }\n\n    /**\n     * @param string|mixed $key\n     * @return void\n     * @throws InvalidArgumentException\n     */\n    protected function validateKey(mixed $key): void\n    {\n        if (!is_string($key)) {\n            throw new InvalidArgumentException(\n                sprintf(\n                    'Cache key must be string, \"%s\" given',\n                    get_debug_type($key)\n                )\n            );\n        }\n        if (!isset($key[0])) {\n            throw new InvalidArgumentException('Cache key length must be greater than zero');\n        }\n        if (strlen($key) > 64) {\n            throw new InvalidArgumentException(\n                sprintf('Cache key length must be less than 65 characters, key had %d characters', strlen($key))\n            );\n        }\n        if (strpbrk($key, '{}()/\\@:') !== false) {\n            throw new InvalidArgumentException(\n                sprintf('Cache key \"%s\" contains reserved characters {}()/\\@:', $key)\n            );","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Cache/CacheTrait.php#L288-L324","documentation":"The shared PSR-16 key validator in CacheTrait (used by every Grav cache adapter's get/set/delete/has) requires keys to be strings. Passing an int, float, bool, null, or object throws InvalidArgumentException with the debug type of the value. This is stricter than PHP coercion: Grav validates explicitly so that key semantics are deterministic across backends.","triggerScenarios":"Calling $cache->get(42) with a numeric database ID; passing null because a lookup variable failed to resolve; passing an int-keyed value from a foreach over an array with numeric keys; calling getMultiple/deleteMultiple with ['a', 0 => 'b'] where validateKeys() forwards non-string keys here.","commonSituations":"Using entity/page IDs directly as cache keys; array_map/foreach producing mixed key types; database rows whose IDs come back as ints (PDO default) being reused as keys; passing false from a failed strpos check as a key.","solutions":["Cast keys to string at call sites: $cache->get((string) $id) or $cache->get('page-' . $id).","Build keys deliberately with a prefix + scalar interpolation so the result is always a string.","Before multi-ops, sanitize: $keys = array_map('strval', $keys); to avoid validateKeys() routing each bad key into this error."],"exampleFix":"// before\n$cache->get($page->id()); // int -> 'Cache key must be string, \"int\" given'\n\n// after\n$cache->get('page-' . $page->id()); // interpolated -> always string","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isValidCacheKey(mixed $key): bool\n{\n    return is_string($key) && $key !== '' && strlen($key) <= 64\n        && strpbrk($key, '{}()/\\\\@:') === false;\n}\n\n// usage\n$cache->get(isValidCacheKey($key) ? $key : 'page-' . md5((string) $key));","tryCatchPattern":null,"preventionTips":["Interpolate IDs into key strings: 'user-' . $id, never $id alone.","array_map('strval', $keys) before multi-operations.","Run a single key-builder helper with a string return type through your codebase."],"tags":["grav","cache","psr-16","invalid-key","type-validation"],"backgroundTag":"invalid-cache-key","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}