{"record":{"id":"99cbbf7e9149173f","repo":"getgrav/grav","slug":"cache-key-length-must-be-greater-than-zero","errorCode":null,"errorMessage":"Cache key length must be greater than zero","messagePattern":"Cache key length must be greater than zero","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Cache/CacheTrait.php","lineNumber":314,"sourceCode":"    }\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            );\n        }\n    }\n\n    /**\n     * @param array $keys\n     * @return void\n     * @throws InvalidArgumentException\n     */","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Cache/CacheTrait.php#L296-L332","documentation":"CacheTrait::validateKey() rejects empty strings: an empty key is ambiguous (often a symptom of a failed lookup upstream) and PSR-16 reserves it as invalid. The check uses isset($key[0]) so any zero-length string triggers InvalidArgumentException('Cache key length must be greater than zero').","triggerScenarios":"Calling $cache->get('') or $cache->get($name) where $name is '' because a variable defaulted to empty (failed ->value() call, missing config key, optional route parameter absent); multi-ops where an array contains '' among the keys.","commonSituations":"Caching keyed by an optional URL segment or field that is absent on some records; null coalescing that produces '' instead of a fallback; trimming user input to nothing and using it as a key; empty defaults from configuration (system.yaml -> custom cache key prefix missing).","solutions":["Guard the variable: if ($key === '') { fall back to a computed key or skip caching }.","Prefix keys so they can never be empty: $key = 'page-' . $slug; even a missing slug yields a non-empty string.","Validate multi-op inputs: array_filter($keys, 'strlen') before getMultiple/deleteMultiple."],"exampleFix":"// before\n$cache->get($this->route()); // returns '' for home -> throws\n\n// after\n$route = $this->route() ?: '/';\n$cache->get('page-' . md5($route));","handlingStrategy":"validation","validationCode":"if (!is_string($key) || $key === '') {\n    $key = 'fallback-' . md5(json_encode($context) ?: 'default');\n}\n$cache->get($key);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefix every key ('page-', 'frag-') so emptiness is impossible.","Treat '' keys as a bug in the upstream lookup — fix the source variable, not the cache call.","array_filter($keys, 'strlen') before getMultiple/deleteMultiple."],"tags":["grav","cache","psr-16","invalid-key","empty-string"],"backgroundTag":"invalid-cache-key","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}