Leantime/leantime · error · Symfony\Component\Filesystem\Exception\FileNotFoundException

File not included in request or has invalid format

Error message

File not included in request or has invalid format

What it means

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.

Source

Thrown at app/Domain/Files/Services/Files.php:112

    /**
     * @throws BindingResolutionException
     *
     * @api
     */
    public function upload($file, $module, $moduleId, $entity = null, $disk = 'default'): array|string|false
    {
        try {
            // Validate input parameters
            if (empty($module) || empty($moduleId)) {
                Log::warning('Upload attempted with missing module or moduleId', [
                    'module' => $module,
                    'moduleId' => $moduleId,
                ]);
                throw new FileValidationException('Missing module or moduleId', FileValidationException::VALIDATION_ERROR);
            }

            if (! isset($file['file']) || ! is_array($file['file'])) {
                throw new FileNotFoundException('File not included in request or has invalid format');
            }
        } catch (FileValidationException $e) {
            Log::warning('File validation failed: '.$e->getMessage());

            return $e->getUserMessage();
        }

        // Normalize module names for consistency
        if ($module === 'projects') {
            $module = 'project';
        }
        if ($module === 'tickets') {
            $module = 'ticket';
        }

        // Authorize against the target's owning project before writing anything (commenter+;
        // admin/owner bypass). This guards the JSON-RPC path, which reaches the @api upload()
        // directly, without the Upload controller's userCanUploadToModule pre-check.

View on GitHub (pinned to 9a9f49f100)

Solutions

  1. Name the file input exactly 'file' so PHP populates $_FILES['file'] and pass $_FILES as $file
  2. Verify isset($file['file']) && is_array($file['file']) before calling upload
  3. 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

Example fix

<!-- before -->
<input type="file" name="attachment">

<!-- after -->
<input type="file" name="file">
Defensive patterns

Strategy: try-catch

Validate before calling

if (! isset($file['file']) || ! is_array($file['file'])) {
    // upload() would throw an uncaught Symfony FileNotFoundException here
    throw new \InvalidArgumentException("File must be sent as a multipart field named 'file'");
}

Type guard

function hasUploadedFileAtFileKey(array $file): bool
{
    return isset($file['file']) && is_array($file['file']);
}

Try / catch

try {
    $result = $filesService->upload($_FILES, $module, $moduleId);
} catch (\Symfony\Component\Filesystem\Exception\FileNotFoundException $e) {
    // NOT caught inside upload() — it propagates; fix the form's file input name to 'file'
    log::error($e); // per repo convention use Log facade
    return response('Upload must include a file field named "file"', 400);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/81b729831b0051bb. Report an issue: GitHub.