- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
894 lines
24 KiB
Markdown
894 lines
24 KiB
Markdown
# LiveComponent File Uploads
|
|
|
|
Komplette Dokumentation des File Upload Systems für LiveComponents mit Drag & Drop, Multi-File Support und Progress Tracking.
|
|
|
|
## Übersicht
|
|
|
|
Das File Upload System ermöglicht es, Dateien direkt in LiveComponents hochzuladen mit:
|
|
|
|
- **Drag & Drop Support** - Intuitive Dateiauswahl durch Ziehen & Ablegen
|
|
- **Multi-File Uploads** - Mehrere Dateien gleichzeitig hochladen
|
|
- **Progress Tracking** - Echtzeit-Fortschrittsanzeige pro Datei und gesamt
|
|
- **Preview Funktionalität** - Bild-Vorschauen und Datei-Icons
|
|
- **Client-Side Validation** - Validierung vor dem Upload (MIME-Type, Größe, Extension)
|
|
- **CSRF Protection** - Automatische CSRF-Token-Integration
|
|
- **Component State Management** - Nahtlose Integration mit LiveComponent State
|
|
|
|
## Architektur
|
|
|
|
```
|
|
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
|
│ FileUploadWidget │───▶│ ComponentFileUploader│───▶│ Backend Route │
|
|
│ (UI Component) │ │ (Upload Manager) │ │ /upload endpoint │
|
|
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
|
│ │ │
|
|
Drop Zone UI Multi-File Queue State Update
|
|
File List Progress Tracking HTML Refresh
|
|
Progress Bars CSRF Handling Event Dispatch
|
|
```
|
|
|
|
### Komponenten
|
|
|
|
1. **Backend** (`src/Framework/LiveComponents/`)
|
|
- `Controllers/LiveComponentController::handleUpload()` - Upload Route Handler
|
|
- `LiveComponentHandler::handleUpload()` - Business Logic
|
|
- `Contracts/SupportsFileUpload` - Interface für uploadbare Components
|
|
- `ValueObjects/FileUploadProgress` - Progress Tracking VO
|
|
- `ValueObjects/UploadedComponentFile` - File Metadata VO
|
|
|
|
2. **Frontend** (`resources/js/modules/livecomponent/`)
|
|
- `ComponentFileUploader.js` - Core Upload Manager
|
|
- `FileUploadWidget.js` - Pre-built UI Component
|
|
- `DragDropZone` - Drag & Drop Handler
|
|
- `FileValidator` - Client-Side Validation
|
|
- `UploadProgress` - Progress Tracking
|
|
|
|
3. **Styling** (`resources/css/components/`)
|
|
- `file-upload-widget.css` - Complete UI Styling
|
|
|
|
## Backend Implementation
|
|
|
|
### Interface: SupportsFileUpload
|
|
|
|
LiveComponents, die Uploads unterstützen, müssen das `SupportsFileUpload` Interface implementieren:
|
|
|
|
```php
|
|
<?php
|
|
|
|
use App\Framework\LiveComponents\Contracts\SupportsFileUpload;
|
|
use App\Framework\Http\UploadedFile;
|
|
use App\Framework\LiveComponents\ComponentEventDispatcher;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentData;
|
|
|
|
final class DocumentUploadComponent extends AbstractLiveComponent implements SupportsFileUpload
|
|
{
|
|
private array $uploadedFiles = [];
|
|
|
|
/**
|
|
* Handle file upload
|
|
*/
|
|
public function handleUpload(UploadedFile $file, ?ComponentEventDispatcher $events = null): ComponentData
|
|
{
|
|
// 1. Validate file (already done by framework, but you can add custom validation)
|
|
if (!$this->isValidDocument($file)) {
|
|
throw new \InvalidArgumentException('Invalid document type');
|
|
}
|
|
|
|
// 2. Process uploaded file
|
|
$savedPath = $this->saveFile($file);
|
|
|
|
// 3. Update component state
|
|
$this->uploadedFiles[] = [
|
|
'name' => $file->getClientFilename(),
|
|
'path' => $savedPath,
|
|
'size' => $file->getSize(),
|
|
'uploaded_at' => time()
|
|
];
|
|
|
|
// 4. Dispatch events if needed
|
|
if ($events) {
|
|
$events->dispatch('file-uploaded', [
|
|
'filename' => $file->getClientFilename(),
|
|
'path' => $savedPath
|
|
]);
|
|
}
|
|
|
|
// 5. Return updated component data
|
|
return ComponentData::fromArray([
|
|
'uploaded_files' => $this->uploadedFiles
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Validate uploaded file
|
|
*/
|
|
public function validateUpload(UploadedFile $file): array
|
|
{
|
|
$errors = [];
|
|
|
|
// File type validation
|
|
$allowedTypes = $this->getAllowedMimeTypes();
|
|
if (!empty($allowedTypes) && !in_array($file->getClientMediaType(), $allowedTypes)) {
|
|
$errors[] = "File type {$file->getClientMediaType()} is not allowed";
|
|
}
|
|
|
|
// File size validation
|
|
$maxSize = $this->getMaxFileSize();
|
|
if ($file->getSize() > $maxSize) {
|
|
$errors[] = "File size exceeds maximum allowed size";
|
|
}
|
|
|
|
// Custom validation
|
|
if (!$this->isValidDocument($file)) {
|
|
$errors[] = "Invalid document format";
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
/**
|
|
* Get allowed MIME types
|
|
*/
|
|
public function getAllowedMimeTypes(): array
|
|
{
|
|
return [
|
|
'application/pdf',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'image/jpeg',
|
|
'image/png'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get maximum file size in bytes
|
|
*/
|
|
public function getMaxFileSize(): int
|
|
{
|
|
return 10 * 1024 * 1024; // 10MB
|
|
}
|
|
|
|
private function isValidDocument(UploadedFile $file): bool
|
|
{
|
|
// Custom validation logic
|
|
return true;
|
|
}
|
|
|
|
private function saveFile(UploadedFile $file): string
|
|
{
|
|
// Save file to storage
|
|
$uploadDir = '/var/www/storage/uploads';
|
|
$filename = uniqid() . '_' . $file->getClientFilename();
|
|
$file->moveTo($uploadDir . '/' . $filename);
|
|
return $filename;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Upload Endpoint
|
|
|
|
**Route**: `POST /live-component/{id}/upload`
|
|
|
|
**Request Format**:
|
|
```
|
|
Content-Type: multipart/form-data
|
|
|
|
file: <binary file data>
|
|
state: <JSON string of current component state>
|
|
params: <JSON string of additional parameters>
|
|
_csrf_token: <CSRF token>
|
|
```
|
|
|
|
**Response Format**:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"html": "<updated component HTML>",
|
|
"state": {
|
|
"id": "document-upload:abc123",
|
|
"component": "DocumentUploadComponent",
|
|
"data": {
|
|
"uploaded_files": [...]
|
|
}
|
|
},
|
|
"events": [...],
|
|
"file": {
|
|
"name": "document.pdf",
|
|
"size": 1048576,
|
|
"type": "application/pdf"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Error Response**:
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "File validation failed",
|
|
"errors": [
|
|
"File type application/octet-stream is not allowed"
|
|
]
|
|
}
|
|
```
|
|
|
|
## Frontend Implementation
|
|
|
|
### Quick Start: FileUploadWidget
|
|
|
|
Der einfachste Weg, File Uploads zu implementieren, ist die Verwendung des `FileUploadWidget`:
|
|
|
|
```html
|
|
<!-- In your LiveComponent template -->
|
|
<div data-live-component="document-upload:abc123">
|
|
<!-- Upload Widget Container -->
|
|
<div id="upload-widget"></div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
import { FileUploadWidget } from '/resources/js/modules/livecomponent/FileUploadWidget.js';
|
|
|
|
// Initialize widget
|
|
const widget = new FileUploadWidget(
|
|
document.getElementById('upload-widget'),
|
|
{
|
|
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
allowedMimeTypes: ['application/pdf', 'image/jpeg', 'image/png'],
|
|
allowedExtensions: ['pdf', 'jpg', 'jpeg', 'png'],
|
|
maxFiles: 5,
|
|
multiple: true,
|
|
autoUpload: true,
|
|
showPreviews: true,
|
|
showProgress: true
|
|
}
|
|
);
|
|
</script>
|
|
```
|
|
|
|
### Advanced: ComponentFileUploader
|
|
|
|
Für vollständige Kontrolle verwenden Sie direkt `ComponentFileUploader`:
|
|
|
|
```javascript
|
|
import { ComponentFileUploader } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
|
|
|
|
const componentElement = document.querySelector('[data-live-id="document-upload:abc123"]');
|
|
const dropZone = document.getElementById('drop-zone');
|
|
const fileInput = document.getElementById('file-input');
|
|
|
|
const uploader = new ComponentFileUploader(componentElement, {
|
|
// Configuration
|
|
maxFileSize: 10 * 1024 * 1024,
|
|
allowedMimeTypes: ['application/pdf', 'image/jpeg'],
|
|
maxFiles: 10,
|
|
autoUpload: true,
|
|
multiple: true,
|
|
maxConcurrentUploads: 2,
|
|
|
|
// UI Elements
|
|
dropZone: dropZone,
|
|
fileInput: fileInput,
|
|
|
|
// Callbacks
|
|
onFileAdded: ({ fileId, file, progress }) => {
|
|
console.log('File added:', file.name);
|
|
// Update UI
|
|
},
|
|
|
|
onUploadStart: ({ fileId, file }) => {
|
|
console.log('Upload started:', file.name);
|
|
},
|
|
|
|
onUploadProgress: ({ fileId, percentage, uploadSpeed, remainingTime }) => {
|
|
console.log(`Upload progress: ${percentage}%`);
|
|
// Update progress bar
|
|
},
|
|
|
|
onUploadComplete: ({ fileId, file, response }) => {
|
|
console.log('Upload complete:', file.name);
|
|
// Handle success
|
|
},
|
|
|
|
onUploadError: ({ fileId, file, error }) => {
|
|
console.error('Upload failed:', error);
|
|
// Handle error
|
|
},
|
|
|
|
onAllUploadsComplete: ({ totalFiles, successCount, errorCount }) => {
|
|
console.log(`All uploads complete: ${successCount}/${totalFiles} succeeded`);
|
|
}
|
|
});
|
|
|
|
// Programmatically add files
|
|
uploader.addFiles([file1, file2, file3]);
|
|
|
|
// Start uploads (if autoUpload is false)
|
|
uploader.uploadAll();
|
|
|
|
// Cancel all uploads
|
|
uploader.cancelAll();
|
|
|
|
// Get statistics
|
|
const stats = uploader.getStats();
|
|
console.log(`Progress: ${stats.overallProgress}%`);
|
|
```
|
|
|
|
### Custom Drag & Drop
|
|
|
|
```javascript
|
|
import { DragDropZone } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
|
|
|
|
const dropZone = new DragDropZone(document.getElementById('drop-area'), {
|
|
onFilesDropped: (files) => {
|
|
console.log('Files dropped:', files);
|
|
uploader.addFiles(files);
|
|
},
|
|
onDragEnter: () => {
|
|
console.log('Drag enter');
|
|
},
|
|
onDragLeave: () => {
|
|
console.log('Drag leave');
|
|
}
|
|
});
|
|
```
|
|
|
|
### Client-Side Validation
|
|
|
|
```javascript
|
|
import { FileValidator } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
|
|
|
|
const validator = new FileValidator({
|
|
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
allowedMimeTypes: ['image/jpeg', 'image/png'],
|
|
allowedExtensions: ['jpg', 'jpeg', 'png'],
|
|
minFileSize: 1024 // 1KB minimum
|
|
});
|
|
|
|
// Validate single file
|
|
const errors = validator.validate(file);
|
|
if (errors.length > 0) {
|
|
console.error('Validation errors:', errors);
|
|
}
|
|
|
|
// Quick validation
|
|
if (!validator.isValid(file)) {
|
|
console.error('File is not valid');
|
|
}
|
|
```
|
|
|
|
## Configuration Options
|
|
|
|
### ComponentFileUploader Options
|
|
|
|
```javascript
|
|
{
|
|
// File Constraints
|
|
maxFileSize: 10 * 1024 * 1024, // Maximum file size in bytes (default: 10MB)
|
|
allowedMimeTypes: [], // Array of allowed MIME types (empty = allow all)
|
|
allowedExtensions: [], // Array of allowed file extensions (empty = allow all)
|
|
maxFiles: 10, // Maximum number of files (default: 10)
|
|
|
|
// Upload Behavior
|
|
autoUpload: true, // Auto-upload on file add (default: true)
|
|
multiple: true, // Allow multiple files (default: true)
|
|
maxConcurrentUploads: 2, // Max concurrent uploads (default: 2)
|
|
|
|
// Endpoints
|
|
endpoint: '/live-component/{id}/upload', // Upload endpoint (default: auto-detected)
|
|
|
|
// UI Elements (optional)
|
|
dropZone: HTMLElement, // Drop zone element
|
|
fileInput: HTMLElement, // File input element
|
|
|
|
// Callbacks
|
|
onFileAdded: (data) => {}, // Called when file is added
|
|
onFileRemoved: (data) => {}, // Called when file is removed
|
|
onUploadStart: (data) => {}, // Called when upload starts
|
|
onUploadProgress: (data) => {}, // Called during upload
|
|
onUploadComplete: (data) => {}, // Called on upload success
|
|
onUploadError: (data) => {}, // Called on upload error
|
|
onAllUploadsComplete: (data) => {} // Called when all uploads are done
|
|
}
|
|
```
|
|
|
|
### FileUploadWidget Options
|
|
|
|
```javascript
|
|
{
|
|
// Inherits all ComponentFileUploader options, plus:
|
|
|
|
// UI Configuration
|
|
showPreviews: true, // Show image previews (default: true)
|
|
showProgress: true, // Show progress bars (default: true)
|
|
showFileList: true, // Show file list (default: true)
|
|
|
|
// Text Configuration
|
|
dropZoneText: 'Drag & drop files here or click to browse',
|
|
browseButtonText: 'Browse Files',
|
|
uploadButtonText: 'Upload All'
|
|
}
|
|
```
|
|
|
|
## Callback Data Structures
|
|
|
|
### onFileAdded
|
|
|
|
```javascript
|
|
{
|
|
fileId: 'unique-file-id',
|
|
file: File, // Native File object
|
|
progress: {
|
|
fileId: 'unique-file-id',
|
|
fileName: 'document.pdf',
|
|
fileSize: 1048576,
|
|
uploadedBytes: 0,
|
|
percentage: 0,
|
|
status: 'pending',
|
|
error: null,
|
|
uploadSpeed: 0,
|
|
remainingTime: 0
|
|
}
|
|
}
|
|
```
|
|
|
|
### onUploadProgress
|
|
|
|
```javascript
|
|
{
|
|
fileId: 'unique-file-id',
|
|
fileName: 'document.pdf',
|
|
fileSize: 1048576,
|
|
uploadedBytes: 524288, // Bytes uploaded so far
|
|
percentage: 50, // Upload percentage (0-100)
|
|
status: 'uploading',
|
|
uploadSpeed: 1048576, // Bytes per second
|
|
remainingTime: 0.5 // Seconds remaining
|
|
}
|
|
```
|
|
|
|
### onUploadComplete
|
|
|
|
```javascript
|
|
{
|
|
fileId: 'unique-file-id',
|
|
file: File,
|
|
response: {
|
|
success: true,
|
|
html: '<updated component HTML>',
|
|
state: {...},
|
|
events: [...],
|
|
file: {
|
|
name: 'document.pdf',
|
|
size: 1048576,
|
|
type: 'application/pdf'
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### onUploadError
|
|
|
|
```javascript
|
|
{
|
|
fileId: 'unique-file-id',
|
|
file: File,
|
|
error: 'File validation failed'
|
|
}
|
|
```
|
|
|
|
## Use Cases & Examples
|
|
|
|
### Basic Image Upload
|
|
|
|
```php
|
|
final class ProfileImageUpload extends AbstractLiveComponent implements SupportsFileUpload
|
|
{
|
|
private ?string $profileImage = null;
|
|
|
|
public function handleUpload(UploadedFile $file, ?ComponentEventDispatcher $events = null): ComponentData
|
|
{
|
|
// Save image
|
|
$filename = $this->imageService->save($file, 'profiles');
|
|
|
|
// Update state
|
|
$this->profileImage = $filename;
|
|
|
|
return ComponentData::fromArray([
|
|
'profile_image' => $this->profileImage
|
|
]);
|
|
}
|
|
|
|
public function validateUpload(UploadedFile $file): array
|
|
{
|
|
$errors = [];
|
|
|
|
// Only allow images
|
|
if (!str_starts_with($file->getClientMediaType(), 'image/')) {
|
|
$errors[] = 'Only images are allowed';
|
|
}
|
|
|
|
// Max 2MB
|
|
if ($file->getSize() > 2 * 1024 * 1024) {
|
|
$errors[] = 'Image must be smaller than 2MB';
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
public function getAllowedMimeTypes(): array
|
|
{
|
|
return ['image/jpeg', 'image/png', 'image/webp'];
|
|
}
|
|
|
|
public function getMaxFileSize(): int
|
|
{
|
|
return 2 * 1024 * 1024;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Multi-Document Upload with Progress
|
|
|
|
```javascript
|
|
import { ComponentFileUploader } from './ComponentFileUploader.js';
|
|
|
|
const uploader = new ComponentFileUploader(componentElement, {
|
|
maxFiles: 20,
|
|
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
allowedMimeTypes: [
|
|
'application/pdf',
|
|
'application/msword',
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
],
|
|
|
|
onUploadProgress: ({ fileId, percentage, uploadSpeed, remainingTime }) => {
|
|
// Update progress UI
|
|
const progressBar = document.querySelector(`[data-file-id="${fileId}"] .progress-bar`);
|
|
progressBar.style.width = `${percentage}%`;
|
|
|
|
const progressText = document.querySelector(`[data-file-id="${fileId}"] .progress-text`);
|
|
progressText.textContent = `${percentage}% - ${formatSpeed(uploadSpeed)} - ${formatTime(remainingTime)} remaining`;
|
|
},
|
|
|
|
onAllUploadsComplete: ({ successCount, errorCount }) => {
|
|
if (errorCount === 0) {
|
|
alert(`All ${successCount} files uploaded successfully!`);
|
|
} else {
|
|
alert(`${successCount} files uploaded, ${errorCount} failed`);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### Custom Validation Messages
|
|
|
|
```javascript
|
|
const validator = new FileValidator({
|
|
maxFileSize: 10 * 1024 * 1024,
|
|
allowedMimeTypes: ['application/pdf']
|
|
});
|
|
|
|
const errors = validator.validate(file);
|
|
|
|
// Translate errors
|
|
const translatedErrors = errors.map(error => {
|
|
if (error.includes('File size')) {
|
|
return 'Dateigröße überschreitet das Maximum';
|
|
} else if (error.includes('File type')) {
|
|
return 'Nur PDF-Dateien sind erlaubt';
|
|
}
|
|
return error;
|
|
});
|
|
|
|
if (translatedErrors.length > 0) {
|
|
showErrorMessages(translatedErrors);
|
|
}
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### CSRF Protection
|
|
|
|
Das System verwendet automatisch CSRF-Tokens:
|
|
|
|
```javascript
|
|
// CSRF token wird automatisch aus Component-Element gelesen
|
|
const csrfToken = componentElement.dataset.csrfToken;
|
|
|
|
// Und in jedem Upload-Request gesendet
|
|
xhr.setRequestHeader('X-CSRF-Form-ID', csrfTokens.form_id);
|
|
xhr.setRequestHeader('X-CSRF-Token', csrfTokens.token);
|
|
```
|
|
|
|
### File Validation
|
|
|
|
**Backend Validation ist PFLICHT**:
|
|
|
|
```php
|
|
public function validateUpload(UploadedFile $file): array
|
|
{
|
|
$errors = [];
|
|
|
|
// 1. MIME type validation
|
|
$allowedTypes = $this->getAllowedMimeTypes();
|
|
if (!in_array($file->getClientMediaType(), $allowedTypes)) {
|
|
$errors[] = 'Invalid file type';
|
|
}
|
|
|
|
// 2. File extension validation
|
|
$extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
|
|
if (!in_array(strtolower($extension), ['pdf', 'jpg', 'png'])) {
|
|
$errors[] = 'Invalid file extension';
|
|
}
|
|
|
|
// 3. File size validation
|
|
if ($file->getSize() > $this->getMaxFileSize()) {
|
|
$errors[] = 'File too large';
|
|
}
|
|
|
|
// 4. File content validation (check magic bytes)
|
|
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
|
$actualMimeType = $finfo->file($file->getStream()->getMetadata('uri'));
|
|
if ($actualMimeType !== $file->getClientMediaType()) {
|
|
$errors[] = 'File content does not match declared type';
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
```
|
|
|
|
### Secure File Storage
|
|
|
|
```php
|
|
private function saveFile(UploadedFile $file): string
|
|
{
|
|
// 1. Generate secure filename (no user input)
|
|
$filename = bin2hex(random_bytes(16)) . '.' . $this->getSecureExtension($file);
|
|
|
|
// 2. Store outside web root
|
|
$uploadDir = '/var/www/storage/uploads';
|
|
|
|
// 3. Set restrictive permissions
|
|
$file->moveTo($uploadDir . '/' . $filename);
|
|
chmod($uploadDir . '/' . $filename, 0600);
|
|
|
|
return $filename;
|
|
}
|
|
|
|
private function getSecureExtension(UploadedFile $file): string
|
|
{
|
|
// Use MIME type to determine extension, not user-provided extension
|
|
return match ($file->getClientMediaType()) {
|
|
'application/pdf' => 'pdf',
|
|
'image/jpeg' => 'jpg',
|
|
'image/png' => 'png',
|
|
default => 'bin'
|
|
};
|
|
}
|
|
```
|
|
|
|
## Performance Optimization
|
|
|
|
### Concurrent Uploads
|
|
|
|
```javascript
|
|
const uploader = new ComponentFileUploader(componentElement, {
|
|
maxConcurrentUploads: 3, // Upload 3 files simultaneously
|
|
autoUpload: true
|
|
});
|
|
```
|
|
|
|
### Chunked Uploads (Large Files)
|
|
|
|
Für große Dateien sollten Chunked Uploads implementiert werden:
|
|
|
|
```javascript
|
|
// TODO: Chunked upload support (geplant für v2.0)
|
|
// Aktuell empfohlen: maxFileSize Limit für große Dateien
|
|
```
|
|
|
|
### Progress Throttling
|
|
|
|
```javascript
|
|
let lastProgressUpdate = 0;
|
|
|
|
onUploadProgress: ({ fileId, percentage }) => {
|
|
const now = Date.now();
|
|
|
|
// Only update UI every 100ms
|
|
if (now - lastProgressUpdate > 100) {
|
|
updateProgressBar(fileId, percentage);
|
|
lastProgressUpdate = now;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Problem: Upload schlägt fehl mit 413 (Request Entity Too Large)
|
|
|
|
**Lösung**: Erhöhe PHP Upload Limits:
|
|
|
|
```ini
|
|
; php.ini
|
|
upload_max_filesize = 50M
|
|
post_max_size = 50M
|
|
```
|
|
|
|
### Problem: CSRF Token fehlt
|
|
|
|
**Lösung**: Stelle sicher, dass Component CSRF Token hat:
|
|
|
|
```html
|
|
<div data-live-component="upload:123" data-csrf-token="<?= $csrfToken ?>">
|
|
...
|
|
</div>
|
|
```
|
|
|
|
### Problem: Uploads sind langsam
|
|
|
|
**Lösungen**:
|
|
1. Erhöhe `maxConcurrentUploads`
|
|
2. Implementiere Chunked Uploads
|
|
3. Komprimiere Dateien vor Upload (z.B. Bilder)
|
|
4. Verwende CDN für statische Assets
|
|
|
|
### Problem: Browser hängt bei vielen Dateien
|
|
|
|
**Lösung**: Limitiere `maxFiles` und zeige Queue-Status:
|
|
|
|
```javascript
|
|
const uploader = new ComponentFileUploader(componentElement, {
|
|
maxFiles: 20, // Limit gleichzeitig ausgewählte Dateien
|
|
maxConcurrentUploads: 2 // Aber nur 2 gleichzeitig hochladen
|
|
});
|
|
```
|
|
|
|
### Problem: Drag & Drop funktioniert nicht
|
|
|
|
**Lösung**: Prüfe Event Listener Setup:
|
|
|
|
```javascript
|
|
// Stelle sicher, dass Drop Zone Element existiert
|
|
const dropZone = document.getElementById('drop-zone');
|
|
if (!dropZone) {
|
|
console.error('Drop zone element not found');
|
|
}
|
|
|
|
// Prüfe CSS cursor
|
|
dropZone.style.cursor = 'pointer';
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Immer Backend-Validierung
|
|
|
|
```php
|
|
// ❌ Niemals nur Frontend-Validierung verlassen
|
|
// ✅ Immer Backend validateUpload() implementieren
|
|
```
|
|
|
|
### 2. Sichere Dateinamen
|
|
|
|
```php
|
|
// ❌ User-provided filenames verwenden
|
|
$file->moveTo('/uploads/' . $file->getClientFilename());
|
|
|
|
// ✅ Sichere, generierte Dateinamen
|
|
$file->moveTo('/uploads/' . bin2hex(random_bytes(16)) . '.pdf');
|
|
```
|
|
|
|
### 3. Progress Feedback
|
|
|
|
```javascript
|
|
// ✅ Immer Progress anzeigen für bessere UX
|
|
onUploadProgress: ({ percentage }) => {
|
|
updateProgressBar(percentage);
|
|
updateStatusText(`Uploading: ${percentage}%`);
|
|
}
|
|
```
|
|
|
|
### 4. Error Handling
|
|
|
|
```javascript
|
|
// ✅ User-freundliche Fehlermeldungen
|
|
onUploadError: ({ error }) => {
|
|
const userMessage = translateError(error);
|
|
showNotification(userMessage, 'error');
|
|
logError(error); // Log für Debugging
|
|
}
|
|
```
|
|
|
|
### 5. File Size Limits
|
|
|
|
```php
|
|
// ✅ Realistische Limits setzen
|
|
public function getMaxFileSize(): int
|
|
{
|
|
// 10MB für Dokumente
|
|
return 10 * 1024 * 1024;
|
|
|
|
// 5MB für Bilder
|
|
// return 5 * 1024 * 1024;
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Unit Tests (Frontend)
|
|
|
|
```javascript
|
|
import { FileValidator } from './ComponentFileUploader.js';
|
|
|
|
describe('FileValidator', () => {
|
|
it('validates file size', () => {
|
|
const validator = new FileValidator({
|
|
maxFileSize: 1024 * 1024 // 1MB
|
|
});
|
|
|
|
const file = new File(['x'.repeat(2 * 1024 * 1024)], 'large.pdf');
|
|
const errors = validator.validate(file);
|
|
|
|
expect(errors.length).toBeGreaterThan(0);
|
|
expect(errors[0]).toContain('File size');
|
|
});
|
|
|
|
it('validates MIME type', () => {
|
|
const validator = new FileValidator({
|
|
allowedMimeTypes: ['application/pdf']
|
|
});
|
|
|
|
const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
|
|
const errors = validator.validate(file);
|
|
|
|
expect(errors.length).toBeGreaterThan(0);
|
|
expect(errors[0]).toContain('not allowed');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests (Backend)
|
|
|
|
```php
|
|
it('handles file upload successfully', function () {
|
|
$component = new DocumentUploadComponent();
|
|
|
|
$file = createUploadedFile('test.pdf', 'application/pdf', 1024);
|
|
|
|
$result = $component->handleUpload($file);
|
|
|
|
expect($result->toArray())->toHaveKey('uploaded_files');
|
|
});
|
|
|
|
it('validates file type', function () {
|
|
$component = new DocumentUploadComponent();
|
|
|
|
$file = createUploadedFile('test.exe', 'application/octet-stream', 1024);
|
|
|
|
$errors = $component->validateUpload($file);
|
|
|
|
expect($errors)->not->toBeEmpty();
|
|
});
|
|
```
|
|
|
|
## Zusammenfassung
|
|
|
|
Das File Upload System bietet:
|
|
|
|
- ✅ **Einfache Integration** - FileUploadWidget für schnellen Start
|
|
- ✅ **Flexible API** - ComponentFileUploader für vollständige Kontrolle
|
|
- ✅ **Drag & Drop** - Intuitive Dateiauswahl
|
|
- ✅ **Multi-File Support** - Mehrere Dateien gleichzeitig
|
|
- ✅ **Progress Tracking** - Echtzeit-Fortschrittsanzeige
|
|
- ✅ **Validation** - Client & Server-Side
|
|
- ✅ **Security** - CSRF Protection, sichere Dateinamen
|
|
- ✅ **Performance** - Concurrent Uploads, Queue Management
|
|
- ✅ **Responsive Design** - Mobile-optimiert
|
|
- ✅ **Dark Mode** - Automatische Theme-Unterstützung
|
|
|
|
**Framework Integration**:
|
|
- Value Objects für Type Safety
|
|
- Event System Integration
|
|
- Component State Management
|
|
- CSRF Token Handling
|
|
- Automatic HTML Refresh
|