{"record":{"id":"9d540983cd8f23f7","repo":"phalcon/cphalcon","slug":"path-closure-didn-t-return-a-valid-string","errorCode":null,"errorMessage":"'path' closure didn't return a valid string","messagePattern":"'path' closure didn't return a valid string","errorType":"exception","errorClass":"Phalcon\\Mvc\\View\\Engine\\Volt\\Exceptions\\InvalidPathClosureReturn","httpStatus":null,"severity":"error","filePath":"phalcon/Mvc/View/Engine/Volt/Compiler.zep","lineNumber":457,"sourceCode":"            if extendsMode {\n                let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledSeparator . \"e\" . compiledSeparator . compiledExtension;\n            } else {\n                let compiledTemplatePath = compiledPath . prefix . templateSepPath . compiledExtension;\n            }\n        } elseif typeof compiledPath == \"object\" && compiledPath instanceof Closure {\n            /**\n             * A closure can dynamically compile the path\n             */\n            let compiledTemplatePath = call_user_func_array(\n                compiledPath,\n                [templatePath, options, extendsMode]\n            );\n\n            /**\n             * The closure must return a valid path\n             */\n            if unlikely typeof compiledTemplatePath != \"string\" {\n                throw new InvalidPathClosureReturn();\n            }\n        } else {\n            throw new InvalidPathType();\n        }\n\n        /**\n         * Compile always must be used only in the development stage\n         */\n        if !this->phpFileExists(compiledTemplatePath) || compileAlways {\n            /**\n             * The file needs to be compiled because it either does not exist or\n             * needs to compiled every time\n             */\n            let compilation = this->compileFile(\n                templatePath,\n                compiledTemplatePath,\n                extendsMode\n            );","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Mvc/View/Engine/Volt/Compiler.zep#L439-L475","documentation":"The Volt 'path' option may be a directory string or a closure invoked as (templatePath, options, extendsMode) to compute the compiled-file location dynamically; the closure's return value must be a string, and InvalidPathClosureReturn is thrown when it is not (phalcon/Mvc/View/Engine/Volt/Compiler.zep:457) — e.g. null from a missing return, void functions, or accidental array returns. (Its sibling InvalidPathType covers a 'path' option that is neither string nor closure.)","triggerScenarios":"A path closure that builds a path conditionally and forgets a return branch; returning the result of mkdir() (bool) or an array from a helper; using a function with side effects that returns void; returning null when a hash function fails.","commonSituations":"Per-tenant or hashed compiled paths computed in a closure; refactoring the closure to call a service whose method returns void; debugging code left in the closure that echoes instead of returns.","solutions":["Make every branch of the closure return a string (absolute path of the compiled file)","Ensure parent directories exist inside the closure (mkdir recursive) and still return the path string","Add a return-type hint or assertion: $path = ...; assert(is_string($path)); return $path;"],"exampleFix":"// before\n$volt->setOptions(['path' => function ($templatePath) {\n    if (!is_dir($dir)) { mkdir($dir, 0777, true); } // bool returned when branch taken\n}]);\n\n// after\n$volt->setOptions(['path' => function ($templatePath) {\n    $dir = cache_path('volt/');\n    if (!is_dir($dir)) { mkdir($dir, 0777, true); }\n    return $dir . md5($templatePath) . '.php';\n}]);","handlingStrategy":"validation","validationCode":"$pathClosure = function (string $templatePath, array $options, bool $extendsMode): string {\n    $dir = cache_path('volt/');\n    if (!is_dir($dir)) { mkdir($dir, 0777, true); }\n    return $dir . md5($templatePath) . '.php';\n};\n$result = $pathClosure('x.phtml', [], false);\nif (!is_string($result) || $result === '') {\n    throw new \\LogicException('Volt path closure must return a non-empty string');\n}","typeGuard":null,"tryCatchPattern":"use Phalcon\\Mvc\\View\\Engine\\Volt\\Exceptions\\InvalidPathClosureReturn;\n// The throw happens at compile time inside Volt; catch around rendering:\ntry {\n    $view->render('page/index');\n} catch (InvalidPathClosureReturn $e) {\n    $logger->error('Volt path closure must return a string path');\n    throw $e;\n}","preventionTips":["Give path closures an explicit ': string' return type so PHP itself enforces it","Return the path in every branch; mkdir() results are bool, never return them","Unit-test the closure standalone before wiring it into Volt options"],"tags":["phalcon","volt","compiler","closures","options","return-type"],"backgroundTag":"invalid-callback-return","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}