{"record":{"id":"81b729831b0051bb","repo":"Leantime/leantime","slug":"file-not-included-in-request-or-has-invalid-format","errorCode":null,"errorMessage":"File not included in request or has invalid format","messagePattern":"File not included in request or has invalid format","errorType":"exception","errorClass":"Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException","httpStatus":null,"severity":"error","filePath":"app/Domain/Files/Services/Files.php","lineNumber":112,"sourceCode":"    /**\n     * @throws BindingResolutionException\n     *\n     * @api\n     */\n    public function upload($file, $module, $moduleId, $entity = null, $disk = 'default'): array|string|false\n    {\n        try {\n            // Validate input parameters\n            if (empty($module) || empty($moduleId)) {\n                Log::warning('Upload attempted with missing module or moduleId', [\n                    'module' => $module,\n                    'moduleId' => $moduleId,\n                ]);\n                throw new FileValidationException('Missing module or moduleId', FileValidationException::VALIDATION_ERROR);\n            }\n\n            if (! isset($file['file']) || ! is_array($file['file'])) {\n                throw new FileNotFoundException('File not included in request or has invalid format');\n            }\n        } catch (FileValidationException $e) {\n            Log::warning('File validation failed: '.$e->getMessage());\n\n            return $e->getUserMessage();\n        }\n\n        // Normalize module names for consistency\n        if ($module === 'projects') {\n            $module = 'project';\n        }\n        if ($module === 'tickets') {\n            $module = 'ticket';\n        }\n\n        // Authorize against the target's owning project before writing anything (commenter+;\n        // admin/owner bypass). This guards the JSON-RPC path, which reaches the @api upload()\n        // directly, without the Upload controller's userCanUploadToModule pre-check.","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Files/Services/Files.php#L94-L130","documentation":"upload() requires the $file argument to contain the uploaded file under the exact key 'file' (i.e. a $_FILES-shaped array whose file input is named 'file'). If $file['file'] is missing or not an array it throws Symfony's FileNotFoundException (Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException) — crucially this is NOT Leantime's FileValidationException, so the adjacent catch block does not catch it and the exception propagates out of upload() as an unhandled 500 / JSON-RPC error envelope.","triggerScenarios":"An upload form whose file input is named anything other than 'file' (e.g. 'attachment') produces $file['attachment']; passing a bare Symfony UploadedFile object or a scalar instead of a $_FILES array; a multipart POST that never reached PHP intact so $_FILES is empty.","commonSituations":"Renaming the file input during a frontend redesign; API clients posting the file at the top level of the request instead of as the multipart field 'file'; requests silently stripped by post_max_size exhaustion (PHP drops $_FILES); test harnesses with hand-built fake arrays missing the 'file' key.","solutions":["Name the file input exactly 'file' so PHP populates $_FILES['file'] and pass $_FILES as $file","Verify isset($file['file']) && is_array($file['file']) before calling upload","If you cannot control the input name, remap it first ($_FILES['file'] = $_FILES['attachment']) and wrap the call in a try/catch for Symfony FileNotFoundException"],"exampleFix":"<!-- before -->\n<input type=\"file\" name=\"attachment\">\n\n<!-- after -->\n<input type=\"file\" name=\"file\">","handlingStrategy":"try-catch","validationCode":"if (! isset($file['file']) || ! is_array($file['file'])) {\n    // upload() would throw an uncaught Symfony FileNotFoundException here\n    throw new \\InvalidArgumentException(\"File must be sent as a multipart field named 'file'\");\n}","typeGuard":"function hasUploadedFileAtFileKey(array $file): bool\n{\n    return isset($file['file']) && is_array($file['file']);\n}","tryCatchPattern":"try {\n    $result = $filesService->upload($_FILES, $module, $moduleId);\n} catch (\\Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException $e) {\n    // NOT caught inside upload() — it propagates; fix the form's file input name to 'file'\n    log::error($e); // per repo convention use Log facade\n    return response('Upload must include a file field named \"file\"', 400);\n}","preventionTips":["Name the file input exactly 'file' in every upload form — the service reads $file['file']","Know the trap: this Symfony exception is NOT converted to a friendly string like FileValidationException is","If PHP silently drops uploads (post_max_size exceeded), $_FILES is empty and hits this same path — check Content-Length first"],"tags":["files","upload","symfony","multipart-formdata","http-500"],"backgroundTag":"file-upload-validation","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}