ns/file-upload-bundle
A file upload bundle for PHP applications that streamlines handling uploaded files, including validation, storage, and integration with your existing framework setup.
The ns/file-upload-bundle is a Symfony bundle (not Laravel — it's a classic Symfony Bundle registered in config/bundles.php). First steps:
composer require noblet-solutions/file-upload-bundle
config/bundles.php:
return [
// ...
NobletSolutions\FileUploadBundle\NobletFileUploadBundle::class => ['all' => true],
];
public/uploads or var/uploads):
# config/packages/noblet_file_upload.yaml
noblet_file_upload:
upload_dir: '%kernel.project_dir%/public/uploads'
web_path: '/uploads'
prePersist/preUpdate.Look first at:
DependencyInjection/Configuration.php for exact config keys.Entity/ (or Model/) folder for the base entity/trait your domain entities should extend.EventListener/ or EventListener/UploadListener.php file — that's where the pre-persist file-moving logic lives and where debugging usually lands you.Entity via trait / base class
use NobletSolutions\FileUploadBundle\Model\FileUploadTrait;
class Document
{
use FileUploadTrait;
// ...
}
The trait typically supplies $file, $path, $getFile(), $setFile(), and an internal temp-file holder. Your entity never worries about where the file lives — the listener does the filesystem work.
Annotated upload field
Because the bundle uses a Doctrine event listener, it discovers which property to upload by annotation/attribute on the entity property:
#[Uploadable(targetField: 'file')]
private ?File $file = null;
Confirm the exact attribute name against the bundle's source — this is the single most common integration failure point.
Controller + Form
$form = $this->createForm(DocumentType::class, new Document());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($form->getData());
$em->flush(); // listener fires here, file is moved
}
The upload happens on flush, not on form submit. Anything you inspect before flush() still shows the temp path.
Uploader service (manual use)
For files not tied to an entity (CSV imports, async jobs), inject the uploader directly:
public function __construct(private FileUploaderInterface $uploader) {}
$path = $this->uploader->upload($uploadedFile);
This is the escape hatch when Doctrine listeners are awkward (e.g., DTOs, batch imports).
Form type integration
The bundle usually ships an UploadType/FileType form element that handles the UploadedFile → File translation. Prefer it over Symfony's plain FileType so the temporary-file metadata the listener expects is set.
Workflow tip
mkdir in bootstrap or a deployment step, and set ownership (e.g., www-data) — a silent false from rename() is the classic first-bug./tmp/phpXXXX. Log the resolved target field when debugging.preUpdate does not fire without a change. Doctrine skips updates when no mapped field changed. If a user re-uploads the same filename with the same path, the listener may not run — force a field change ($entity->setUpdatedAt(new \DateTime())) or call $em->getUnitOfWork()->recomputeChangeSet(...).report.pdf overwrite each other. Decorate the uploader service and prefix with a UUID/hash.postUpdate listener.bin/console cache:clear — the annotation reader caches metadata aggressively.FileUploaderInterface); override the noblet_file_upload.uploader service alias in services.yaml to swap in your own implementation (S3, Flysystem, virus scanning).@Assert\File(maxSize=...) on the entity property — otherwise you inherit whatever php.ini allows.rename() real files. Point upload_dir at %kernel.cache_dir%/test-uploads in config/packages/test/noblet_file_upload.yaml and clean up in tearDown().How can I help you explore Laravel packages today?