File Uploads
Handling file uploads in Osumi Framework follows the same design principles as the rest of the system:
- DTOs handle and validate incoming data
- Components orchestrate the operation
- The uploaded file is available through
ORequest - You decide where and how files should be stored
This recipe shows how to accept an uploaded file (e.g. from an HTML <input type="file" name="photo">), process it in a component, and store it properly.
1. Overview of How Uploads Work
When a user submits a form with a file, PHP places the uploaded file information inside $_FILES.
Osumi Framework merges request data (params, headers, filters, files) into an ORequest instance.
From inside your component’s run() method, you can access:
$req->getFile("photo");
This returns the standard PHP upload array:
[
'name' => 'example.png',
'type' => 'image/png',
'tmp_name' => '/tmp/phpXYZ123',
'error' => 0,
'size' => 123456
]
To preserve consistency with the rest of the framework, you usually wrap this access inside a DTO, so validation and structure stay clean.
2. Creating a DTO for File Uploads
DTOs can accept any request parameter, including files.
Since uploaded files do not come from headers or filters, you read them directly from ORequest inside the DTO constructor.
Example DTO:
<?php declare(strict_types=1);
namespace Osumi\OsumiFramework\App\DTO;
use Osumi\OsumiFramework\DTO\ODTO;
use Osumi\OsumiFramework\DTO\ODTOField;
use Osumi\OsumiFramework\Web\ORequest;
class PhotoUploadDTO extends ODTO {
#[ODTOField(required: true)]
public ?array $photo = null;
public function __construct(ORequest $req) {
parent::__construct($req);
// Load the uploaded file
$this->photo = $req->getFile('photo');
// Validate file presence
if ($this->photo === null || $this->photo['error'] !== 0) {
$this->validation_errors[] = "A valid file is required.";
}
}
}
Notes:
required: trueensures the DTO will be invalid if the file is missinggetFile('photo')retrieves the upload- You can extend validation (file size, mime type, etc.)
3. Creating the Upload Component
The component receives the DTO and processes the uploaded file.
<?php declare(strict_types=1);
namespace Osumi\OsumiFramework\App\Module\Api\UploadPhoto;
use Osumi\OsumiFramework\Core\OComponent;
use Osumi\OsumiFramework\App\DTO\PhotoUploadDTO;
class UploadPhotoComponent extends OComponent {
public string $status = 'ok';
public string $message = '';
public ?string $filename = null;
public function run(PhotoUploadDTO $dto): void {
if (!$dto->isValid()) {
$this->status = 'error';
$this->message = implode(", ", $dto->getValidationErrors());
return;
}
// Access uploaded file data
$file = $dto->photo;
// Generate a destination file path
$new_name = uniqid("photo_") . "_" . basename($file['name']);
$upload_dir = $this->getConfig()->getDir('uploads');
$dest = $upload_dir . $new_name;
// Move the uploaded file
if (!move_uploaded_file($file['tmp_name'], $dest)) {
$this->status = 'error';
$this->message = 'Failed to store file.';
return;
}
$this->filename = $new_name;
$this->message = 'File uploaded successfully.';
}
}
Key points:
run()receives aPhotoUploadDTO- The DTO ensures validity
move_uploaded_file()stores the file safely- You should store uploads in a configured directory (
uploadsor similar) - Components remain clean and readable
4. HTML Form Example
When consuming this endpoint from a web page:
Important:
enctype="multipart/form-data" is required for PHP to populate $_FILES.
5. Defining the Route
Add a route that receives a POST request and maps to your upload component:
use Osumi\OsumiFramework\Routing\ORoute;
use Osumi\OsumiFramework\App\Module\Api\UploadPhoto\UploadPhotoComponent;
ORoute::post('/api/upload-photo', UploadPhotoComponent::class);
If the upload requires authentication, simply add your filter:
ORoute::post('/api/upload-photo', UploadPhotoComponent::class, [LoginFilter::class]);
6. Example JSON Response Template
If your component uses a .json template, the output might be:
{
"status": "{{ status }}",
"message": "{{ message }}",
"filename": "{{ filename }}"
}
7. Extending Validation
Common validation patterns include:
Allowed extensions:
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ['png','jpg','jpeg'])) {
$this->validation_errors[] = "Invalid file extension.";
}
Max file size:
if ($file['size'] > 2 * 1024 * 1024) { // 2MB
$this->validation_errors[] = "File is too large.";
}
Valid MIME types:
$allowed = ['image/png','image/jpeg'];
if (!in_array($file['type'], $allowed)) {
$this->validation_errors[] = "Invalid file type.";
}
You can store these rules inside a dedicated service to keep DTOs clean.
8. Storing File Metadata in Models
If you want to store upload info in a database:
$photo = new Photo();
$photo->filename = $new_name;
$photo->user_id = $userId; // if using LoginFilter
$photo->save();
This is usually done inside a service rather than directly in a component.
9. Best Practices
- Use DTOs to validate uploaded files
- Use services for file‑related logic if it grows (resizing images, generating thumbnails, etc.)
- Never trust client‑provided metadata (always inspect MIME type, extension, size)
- Keep upload directories outside of public access unless files need to be exposed
- Name files uniquely to avoid overwriting user files
- Use filters if only authenticated users may upload files
10. Summary
To implement uploads in Osumi Framework:
- Create a DTO to receive and validate the file
- Create a component to process and store it
- Add a route pointing to the component
- Use
ORequest->getFile(name)to access uploaded files - Store them using
move_uploaded_file() - Add authentication filters if necessary
This approach keeps your upload logic consistent with the framework’s design: clean, modular, and predictable.