In Laravel, what is the difference between $request->hasFile('photo') and $request->file('photo')->isValid() for an upload?
answer
- non-empty temporary path
- UPLOAD_ERR_OK and is_uploaded_file
- file() null when nothing chosen
- arrays: hasFile() is true if any
- getError() carries the PHP code
basics
~20 shasFile() asks whether the request holds a file object with a real temporary path; isValid() asks whether that particular upload finished with no error and came through PHP's upload mechanism. For multiple files, hasFile() is true if any one qualifies.
solid answer
~40 s`hasFile($key)` reads `file($key)`, wraps a single file in an array, and returns true if **any** entry is an `SplFileInfo` with a non-empty path. `isValid()`, from the Symfony base class, returns true only when the upload's error code is `UPLOAD_ERR_OK` and `is_uploaded_file()` confirms the temporary file (test fakes skip that second check). When no file was chosen, `file()` returns `null`. When PHP rejected the file, for example for exceeding `upload_max_filesize`, `file()` returns an `UploadedFile` with an empty path and a non-zero `getError()`, so `hasFile()` is false and `isValid()` is false. For a multi-photo input, `hasFile('photos')` being true does not mean every photo is fine, so check `isValid()` per file.
code
php · 20 lines<?php
use Illuminate\Http\Request;
public function storeGallery(Request $request)
{
$saved = [];
foreach ($request->file('photos', []) as $i => $photo) {
if (! $photo->isValid()) { // hasFile('photos') can be true while this one failed
logger()->info('photo upload failed', ['index' => $i, 'error' => $photo->getError()]);
continue;
}
$saved[] = $photo->store('gallery', 'public');
}
return $saved;
}go deeper
Know that hasFile() checks whether a file came with the request and isValid() checks whether its upload succeeded.
Explain the three states of an upload field, why rejected uploads have an empty path, and why hasFile() is an any-check on arrays.
Produce precise user messages from getError(), handle partial success in multi-file uploads, and log rejected uploads for capacity tuning.
Decide how upload failures surface across web and API clients, and whether large media should bypass the app with direct-to-storage uploads.
## Three states an upload field can be in When a dating app's profile form has a photo input, the request can arrive in three distinct states, and Laravel's API lets you tell them apart: 1. **Nothing chosen.** The browser sends the part with no file, and `$request->file('photo')` returns `null`. 2. **Chosen but rejected.** PHP received the part but refused it, for instance because it exceeded `upload_max_filesize`, and recorded an error code. `file('photo')` returns an `UploadedFile`, but its temporary path is empty. 3. **Uploaded.** The file sits in PHP's temporary directory with error code `UPLOAD_ERR_OK`. `hasFile()` and `isValid()` answer different questions about these states. ## hasFile(): is there a usable file object? `hasFile($key)` is defined on the request: - it reads `file($key)`, which resolves dot paths against `allFiles()`; - it wraps a single file in an array so single and multiple inputs share one code path; - it returns true if **any** entry is an `SplFileInfo` whose `getPath()` is not empty. State 1 gives `null`, which is not an `SplFileInfo`, so `hasFile()` is false. State 2 gives an object with an empty path, because PHP leaves the temporary name empty when an upload fails, so `hasFile()` is false there too. Only state 3 passes. ## isValid(): did this upload succeed? `isValid()` is inherited from Symfony's `UploadedFile`. It returns true when the error code equals `UPLOAD_ERR_OK` **and** PHP's `is_uploaded_file()` confirms the path is a genuine upload from this request. Files built for tests with the fake factory are marked as test files and skip the `is_uploaded_file()` check, which is why they validate in feature tests. Laravel uses `isValid()` internally: `UploadedFile::get()` throws `FileNotFoundException` for an invalid file, and the validator adds the `uploaded` failure ("The photo failed to upload.") when an attribute with file rules holds an invalid upload. ## Putting them side by side | Situation | `file('photo')` | `hasFile('photo')` | `->isValid()` | |---|---|---|---| | No file chosen | `null` | false | not callable (null) | | Rejected by PHP (size, partial) | `UploadedFile`, empty path, error code set | false | false | | Uploaded successfully | `UploadedFile` | true | true | The practical consequence: `hasFile()` alone cannot tell "the user chose nothing" from "the user chose a photo that was too big". If the difference matters for the message, check `$request->file('photo')?->getError()` and compare with the `UPLOAD_ERR_*` constants. ## Multiple files For `<input type="file" name="photos[]" multiple>`, `file('photos')` returns an array of `UploadedFile` objects and `file('photos.0')` the first. Because `hasFile('photos')` is an **any** check, one good photo among five failed ones still returns true. Loop and check each: - skip or report entries whose `isValid()` is false; - store only the valid ones; - remember PHP's `max_file_uploads` setting (20 by default) caps how many files a single request may carry. ## What to rely on in practice - Use **`hasFile()`** for branching on "is there something to store?". - Use **`isValid()`** or the validator before touching the file's contents. - Let **validation** own the user-facing rules; these methods are for control flow and diagnostics. ## Why the any-check exists `hasFile()` is designed as a cheap guard for "should I bother processing this field?", and for arrays the useful answer to that question is "is there at least one usable file?". The per-file decision belongs to the loop that processes the files. Reading it as an all-check is the most common mistake, and it shows up as gallery uploads where some photos silently vanish: the controller checked `hasFile('photos')`, then called `store()` on every entry, and the invalid entries failed as soon as the code tried to store them or read their contents; `get()`, for one, raises `FileNotFoundException` for an invalid upload.
- Why does an UploadedFile created with UploadedFile::fake() pass isValid() in a feature test?Fake files are constructed in test mode. Symfony's `isValid()` then checks only that the error code is `UPLOAD_ERR_OK` and skips `is_uploaded_file()`, which would otherwise reject any file PHP did not receive through a real multipart request.
- How can a controller tell 'no photo chosen' apart from 'photo too large' when hasFile() is false in both cases?Read `$request->file('photo')`. It is `null` when nothing was chosen, but an `UploadedFile` with a non-zero `getError()`, such as `UPLOAD_ERR_INI_SIZE`, when PHP rejected the file. That lets the response say 'please choose a photo' or 'that photo is too large' instead of one generic message.
saying these in an interview costs you the question
- hasFile() calls isValid() on the file
- hasFile('photos') means every photo in the array uploaded
- file() returns null when a photo exceeded the size limit
- isValid() checks the file's MIME type
- An oversized photo makes hasFile() true and isValid() false