Uploading files
File uploads are tricky. Submit them to a #[LiveAction] with the files modifier
on data-live-action then process them.
Note: files aren't persisted and will be lost on each rerender if not stored.
Current file:
none
Current files:
(none)
// ... use statements hidden - click to show
#[AsLiveComponent]
final class UploadFiles
{
use DefaultActionTrait;
public function __construct(private ValidatorInterface $validator)
{
}
#[LiveProp]
public ?string $singleUploadFilename = null;
#[LiveProp]
public ?string $singleFileUploadError = null;
/** @var list<array{filename: string, size: int|false}> */
#[LiveProp]
public array $multipleUploadFilenames = [];
#[LiveAction]
public function uploadFiles(Request $request): void
{
$singleFileUpload = $request->files->get('single');
if ($singleFileUpload) {
$this->validateSingleFile($singleFileUpload);
}
if ($singleFileUpload instanceof UploadedFile) {
[$this->singleUploadFilename] = $this->processFileUpload($singleFileUpload);
}
$multiple = $request->files->all('multiple');
foreach ($multiple as $file) {
if ($file instanceof UploadedFile) {
[$filename, $size] = $this->processFileUpload($file);
$this->multipleUploadFilenames[] = ['filename' => $filename, 'size' => $size];
}
}
}
/**
* @return array{0: string, 1: int|false}
*/
private function processFileUpload(UploadedFile $file): array
{
// in a real app, move this file somewhere
// $file->move(...);
return [$file->getClientOriginalName(), $file->getSize()];
}
private function validateSingleFile(UploadedFile $singleFileUpload): void
{
$errors = $this->validator->validate($singleFileUpload, [
new Assert\File(maxSize: '1M'),
]);
if (0 === \count($errors)) {
return;
}
$this->singleFileUploadError = $errors->get(0)->getMessage();
// causes the component to re-render
throw new UnprocessableEntityHttpException('Validation failed.');
}
}
<div class="max-w-[1240px] mx-auto" {{ attributes }}>
<p class="text-muted"><strong>Note:</strong> files aren't persisted and will be lost on each rerender if not stored.</p>
<div class="mb-4">
<twig:Form:Label for="single-file">Single file:</twig:Form:Label>
<input
type="file"
name="single"
autocomplete="off"
id="single-file"
class="block w-full text-sm file:mr-3 file:rounded-md file:border file:border-border file:bg-surface file:px-3 file:py-1.5 file:cursor-pointer"
>
<div class="text-sm text-muted mt-1">Current file:
{% if singleUploadFilename %}
{{ singleUploadFilename }}
{% else %}
none
{% endif %}
</div>
<twig:Form:Errors errors="{{ singleFileUploadError }}" />
</div>
<div class="mb-4">
<twig:Form:Label for="multiple-files">Multiple files:</twig:Form:Label>
<input type="file" class="block w-full text-sm file:mr-3 file:rounded-md file:border file:border-border file:bg-surface file:px-3 file:py-1.5 file:cursor-pointer" name="multiple[]" autocomplete="off" multiple="" id="multiple-files" >
<div class="text-sm text-muted mt-1">Current files:
{% if multipleUploadFilenames %}
<ul>
{% for file in multipleUploadFilenames %}
<li>{{ file.filename }} ({{ file.size }} bytes)</li>
{% endfor %}
</ul>
{% else %}
(none)
{% endif %}
</div>
</div>
<twig:Button
variant="primary"
data-action="live#action"
data-live-action-param="files|uploadFiles"
>Upload files!</twig:Button>
</div>
Author
lustmored
Published
2023-06-26