Osumi Framework
es en eu v9.8.0 GitHub

File Uploads

Handling file uploads in Osumi Framework follows the same design principles as the rest of the system:

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:


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:


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


10. Summary

To implement uploads in Osumi Framework:

  1. Create a DTO to receive and validate the file
  2. Create a component to process and store it
  3. Add a route pointing to the component
  4. Use ORequest->getFile(name) to access uploaded files
  5. Store them using move_uploaded_file()
  6. Add authentication filters if necessary

This approach keeps your upload logic consistent with the framework’s design: clean, modular, and predictable.