Installation
composer require efrane/tus-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Efrane\TusBundle\TusBundle::class => ['all' => true],
];
Configuration Publish the default config:
php bin/console config:dump-reference efrane_tus
Update config/packages/efrane_tus.yaml:
efrane_tus:
upload_dir: '%kernel.project_dir%/public/uploads'
allowed_file_types: ['image/jpeg', 'image/png', 'application/pdf']
First Use Case Upload a file via JavaScript using the tus-js-client:
const upload = new tus.Upload(file, {
endpoint: '/uploads',
metadata: { filename: file.name },
retryDelays: [0, 1000, 3000, 5000]
});
upload.start();
Resumable Uploads
tus:upload command to verify uploads:
php bin/console tus:upload
UploadedFile event listener:
// src/EventListener/TusUploadListener.php
use Efrane\TusBundle\Event\UploadedFileEvent;
class TusUploadListener
{
public function onUploadedFile(UploadedFileEvent $event)
{
$file = $event->getFile();
// Process file (e.g., store metadata, generate thumbnails)
}
}
File Validation
config/packages/efrane_tus.yaml:
efrane_tus:
max_file_size: 10M # 10MB
allowed_file_types: ['image/*', 'application/*']
use Efrane\TusBundle\Event\FileValidationEvent;
class CustomFileValidator
{
public function validate(FileValidationEvent $event)
{
if ($event->getFile()->getClientOriginalExtension() === 'pdf') {
$event->setValid(false);
$event->setMessage('PDF uploads are disabled.');
}
}
}
Progress Tracking
UploadProgressEvent to log or notify:
use Efrane\TusBundle\Event\UploadProgressEvent;
class UploadProgressLogger
{
public function onProgress(UploadProgressEvent $event)
{
$this->logger->info(
'Upload progress: ' . $event->getPercentage() . '%',
['file_id' => $event->getFileId()]
);
}
}
Frontend Integration
Use tus-js-client with Symfony’s tus:upload endpoint. Example:
const upload = new tus.Upload(file, {
endpoint: '/uploads',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Bearer ' + token
}
});
Storage Backends
Override the default storage handler by implementing Efrane\TusBundle\Storage\FileStorageInterface:
class S3FileStorage implements FileStorageInterface
{
public function saveFile(UploadedFile $file): string
{
// Upload to S3 and return URL
}
}
Register it in services.yaml:
services:
Efrane\TusBundle\Storage\FileStorageInterface: '@app.s3_file_storage'
API Endpoints Expose upload status via a custom controller:
use Efrane\TusBundle\Service\TusService;
class UploadController
{
public function __construct(private TusService $tusService)
{}
public function getUploadStatus(string $fileId): JsonResponse
{
return new JsonResponse($this->tusService->getUpload($fileId));
}
}
CORS Issues
Ensure your frontend and backend CORS settings allow PATCH, POST, and custom headers (e.g., Upload-Offset, Upload-Length). Example for Symfony:
# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
allow_origin: ['*']
allow_methods: ['GET', 'POST', 'PATCH', 'DELETE']
allow_headers: ['*']
expose_headers: ['*']
File Locking Tus uses file locks to prevent concurrent writes. If uploads fail, manually clean up locked files:
php bin/console tus:cleanup
Memory Limits
Large files may hit PHP’s memory_limit. Increase it temporarily:
php -d memory_limit=512M bin/console tus:upload
Event Order
Events fire in this order:
FileValidationEvent → UploadProgressEvent → UploadedFileEvent.
Validate early to avoid processing invalid files.
Log Upload Events
Enable debug mode and check Symfony logs for tus.* events:
monolog:
handlers:
main:
level: debug
channels: ['!event']
tus:
type: stream
path: '%kernel.logs_dir%/tus.log'
level: debug
channels: ['tus']
Check Upload Directory Permissions
Ensure upload_dir is writable:
chmod -R 775 %kernel.project_dir%/public/uploads
Custom Metadata
Pass metadata via the tus-js-client:
upload.setMetadata({ user_id: 123, project_id: 456 });
Access it in Symfony:
$metadata = $event->getFile()->getMetadata();
Terminate Uploads
Use the tus:terminate command to cancel uploads:
php bin/console tus:terminate <file_id>
Testing
Mock the FileStorageInterface for unit tests:
$storage = $this->createMock(FileStorageInterface::class);
$storage->method('saveFile')->willReturn('/path/to/file');
$this->container->set(FileStorageInterface::class, $storage);
Symfony Cache Clear cache after config changes:
php bin/console cache:clear
Security Restrict upload endpoints to authenticated users via firewall:
# config/packages/security.yaml
access_control:
- { path: ^/uploads, roles: ROLE_USER }
How can I help you explore Laravel packages today?