In a Laravel controller, how do you read an uploaded profile photo and save it to a disk with store()?
answer
- UploadedFile from $request->file()
- directory only, name is generated
- returns the path relative to the disk root
- second argument picks the disk
- FILESYSTEM_DISK defaults to local
basics
~10 s$request->file('photo') returns an Illuminate\Http\UploadedFile; calling ->store('avatars') writes it to the default disk under a random 40-character name with a content-based extension and returns the relative path, or false if the write fails.
solid answer
~40 s`$request->file('photo')` (or `$request->photo`) returns an `Illuminate\Http\UploadedFile`, or `null` when no file was sent, and `hasFile('photo')` tells you whether a usable upload is there. `->store('avatars')` takes a **directory**, not a filename: it generates a name with `hashName()` (40 random characters plus the extension guessed from the file's contents), streams the file onto the default disk and returns the path relative to that disk's root, such as `avatars/Xy3....jpg`, which you save on the user row. Pass a disk as the second argument, `store('avatars', 'public')`, or an options array. The skeleton's default is `FILESYSTEM_DISK=local`, rooted at `storage/app/private`. With the skeleton's `'throw' => false`, a failed write returns `false` instead of throwing.
code
php · 22 lines<?php
use Illuminate\Http\Request;
public function updatePhoto(Request $request)
{
// validation of type and size runs first (not shown)
if (! $request->hasFile('photo')) {
return back()->withErrors(['photo' => 'Choose a photo to upload.']);
}
$path = $request->file('photo')->store('avatars', 'public'); // e.g. avatars/Qm9...x2.jpg
if ($path === false) {
return back()->withErrors(['photo' => 'Could not save the photo.']);
}
$request->user()->update(['photo_path' => $path]);
return back();
}go deeper
Recall the chain: file('photo') gives an UploadedFile, store('avatars') saves it under a random name and returns the path you keep in the database.
Explain hashName(), the disk argument, the relative return value, and why the skeleton's local disk under storage/app/private is not web-accessible.
Treat the return value as fallible, configure throw on disks where silent failure is unacceptable, and keep validation strictly before storage.
Decide where user media lives, local disk or object storage, and how paths stored in the database survive a change of disk.
## From multipart request to UploadedFile When a browser submits a form with `enctype="multipart/form-data"` and an `<input type="file" name="photo">`, PHP writes the file to a temporary location and Laravel wraps each entry in an **`Illuminate\Http\UploadedFile`**. That class extends Symfony's `UploadedFile`, which in turn extends PHP's `SplFileInfo`, and adds Laravel's storage helpers. On a dating app's "edit profile" screen, the controller reads the photo like this: - `$request->file('photo')` returns the `UploadedFile`, or `null` when the user chose no file. Dot paths work, so `file('photos.0')` reaches the first of several. - `$request->photo` does the same through the request's dynamic properties. - `$request->hasFile('photo')` returns `true` only when there is a file object with a real temporary path, so it is the quick "did a usable upload arrive?" check. - `$request->allFiles()` returns every uploaded file keyed by input name. ## What store() does `store($path = '', $options = [])` is a thin wrapper: 1. It computes a file name with **`hashName()`**: 40 random characters from `Str::random(40)` plus a dot and the extension from `guessExtension()`, which inspects the file's contents rather than trusting the client. 2. It calls `storeAs($path, $thatName, $options)`. 3. `storeAs()` resolves the disk from the filesystem manager and calls the disk's `putFileAs()`, which opens the temporary file as a **stream** and writes it, so large photos are not loaded into memory. 4. It returns the stored path, `trim($path.'/'.$name, '/')`, relative to the disk root, or `false` if the write failed. | Call | Directory | File name | Disk | |---|---|---|---| | `store('avatars')` | `avatars` | random 40 chars + guessed extension | default disk | | `store('avatars', 'public')` | `avatars` | random | `public` | | `store('avatars', ['disk' => 's3'])` | `avatars` | random | `s3` | | `storeAs('avatars', "{$user->id}.jpg")` | `avatars` | the name you pass | default disk | The second argument may be a string (the disk name) or an options array; a string is turned into `['disk' => ...]`. ## Where the file lands in a fresh Laravel 13 app The skeleton's `config/filesystems.php` reads `FILESYSTEM_DISK` with a default of `local`, and the `local` disk's root is `storage_path('app/private')`. So `store('avatars')` in a new app writes to `storage/app/private/avatars/...`, a directory that is **not** under `public/` and is not directly reachable by URL. Profile photos that browsers should load are usually written to the `public` disk (rooted at `storage/app/public`) or a cloud disk. Configuring disks and the public symlink is a separate topic; for the upload itself, the point is to pass the disk you mean. ## Saving the path and handling failure Store the **returned path**, not an absolute filesystem path and not the client's file name: - the path stays valid if you move the app, because the disk root is configuration; - the random name avoids collisions between two users who both upload `IMG_0001.jpg`; - the name leaks nothing the user typed. Every disk in the skeleton sets `'throw' => false` and `'report' => false`, so a failed write returns `false` silently. Check the result, or set `'throw' => true` on the disk so failures raise an exception you will notice: ```php $path = $request->file('photo')->store('avatars', 'public'); if ($path === false) { return back()->withErrors(['photo' => 'Could not save the photo.']); } ``` ## Before you call store() Storing is not validating. The size, type and dimensions rules for an image belong in validation, which should run first so that a PDF renamed to `.jpg`, or a 40 MB file, never reaches the disk. Deleting the user's previous photo, generating thumbnails and serving the file are follow-up steps with their own APIs. ## Common mistakes interviewers listen for 1. **Passing a file name to `store()`.** `store('avatars/me.jpg')` treats the whole string as a directory and creates `avatars/me.jpg/<random>.jpg`. 2. **Saving `getClientOriginalName()` as the stored name.** It is client-controlled and collides across users; `store()` avoids it by design. 3. **Assuming `$request->input('photo')` returns the file.** `input()` reads fields only; files come from `file()`, `allFiles()` or `all()`. 4. **Forgetting `enctype="multipart/form-data"`** on the form. Without it the browser sends only the file name as text, and `file('photo')` is `null`. 5. **Storing the absolute path.** Moving the app or switching the disk root then breaks every saved reference.
- Why does store() take a directory rather than a full file path?Because it names the file for you with `hashName()`: 40 random characters plus an extension guessed from the file's contents. Passing `'avatars/me.jpg'` would create a directory called `me.jpg` with a random file inside. When you need a specific name, call `storeAs('avatars', 'me.jpg')` instead.
- Where does store('avatars') write in a fresh Laravel 13 app, and can a browser load that file?The skeleton's default disk is `local` (`FILESYSTEM_DISK=local`), rooted at `storage/app/private`, so the file lands in `storage/app/private/avatars`. That directory is outside `public/`, so there is no direct URL to it. Photos meant for browsers usually go to the `public` disk or a cloud disk.
saying these in an interview costs you the question
- store('avatars/me.jpg') saves the file as me.jpg
- store() returns the absolute path on the server
- store() keeps the client's original file name
- A failed store() always throws an exception
- The default disk in a new app is public