Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Rest Upload Bundle Laravel Package

silarhi/rest-upload-bundle

A Symfony bundle for adding RESTful file-upload endpoints to your application. It streamlines receiving, storing, and managing uploaded files through a simple API, reducing boilerplate for upload workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check: this is a Symfony bundle, not a Laravel package. No ServiceProvider, no facade, no publishable config/rest-upload.php. Day-to-day, a Laravel developer will only touch it in two real scenarios:

  1. You work in a Symfony codebase (legacy service, client project, mixed-stack monorepo). Then you install and register it directly.
  2. You work in a Laravel codebase and use it as a reference implementation for accepting base64-in-JSON and raw-binary uploads alongside multipart/form-data on one endpoint.

Given 0 stars, 0 dependents, and no CVE surface, do not add it to composer.json for a Laravel app. Read the source, copy the pattern.

In a Symfony app — the minimal path:

composer require silarhi/rest-upload-bundle

Symfony Flex has no recipe for it, so register it manually:

// config/bundles.php
return [
    // ...
    SRIO\RestUploadBundle\SRIORestUploadBundle::class => ['all' => true],
];

Then read, in this order, before writing any controller:

  • src/ — the request listener (this is the whole product) and the uploaded-file factory.
  • The bundle's DependencyInjection/Configuration.php — this is the true config surface; the README is thinner than the tree.
  • The event/factory that inspects Content-Type and converts the request body into a Symfony\Component\HttpFoundation\File\UploadedFile.

That last piece is the point. The bundle's job is: JSON {"file": "<base64>"} → UploadedFile, so that standard File/Image/MimeType constraints and $file->move() work unchanged in your controller.

First use case — a single upload endpoint that accepts either shape:

#[Route('/api/upload', methods: ['POST', 'PUT'])]
public function upload(Request $request): JsonResponse
{
    // The bundle has already normalized the request:
    // - multipart  → $request->files->get('file')
    // - base64 JSON → also $request->files->get('file')
    // - raw binary → also $request->files->get('file')
    $file = $request->files->get('file');
    if (!$file instanceof UploadedFile) {
        return $this->json(['error' => 'No file provided'], 400);
    }

    $file->move($this->getParameter('uploads_dir'), $file->getClientOriginalName());

    return $this->json(['path' => '/uploads/' . $file->getClientOriginalName()], 201);
}

In Laravel — the equivalent first step: write a middleware (NormalizeUploadRequest) that checks Content-Type; if application/json, base64-decode $request->input('file') to a temp path and call UploadedFile::createFromBase(); if application/octet-stream, do the same with $request->getContent(). Then everything downstream — FormRequest validation, Storage::disk('uploads')->putFile(), Image rules — stays exactly as your team already writes it. The pattern is worth 10 lines of middleware; the bundle is not worth a framework detour.

Implementation Patterns

Pattern 1 — Format dispatch behind a single route. The bundle's core idea: one /upload resource, three body shapes, one controller. In Laravel, implement it as middleware that normalizes to a UploadedFile before the request reaches the controller:

// app/Http/Middleware/NormalizeUpload.php
public function handle(Request $request, Closure $next)
{
    if ($request->isMethod('POST') || $request->isMethod('PUT')) {
        $ct = $request->header('Content-Type', '');
        if (str_contains($ct, 'application/json') && $b64 = $request->input('file')) {
            $tmp = tempnam(sys_get_temp_dir(), 'up_');
            file_put_contents($tmp, base64_decode($b64, true));
            $request->files->set('file', new UploadedFile($tmp, 'upload', null, null, true));
        } elseif (str_starts_with($ct, 'application/octet-stream')) {
            $tmp = tempnam(sys_get_temp_dir(), 'up_');
            file_put_contents($tmp, $request->getContent());
            $request->files->set('file', new UploadedFile($tmp, 'upload', null, null, true));
        }
    }
    return $next($request);
}

Register it in bootstrap/app.php or Kernel::middleware where it runs before your FormRequest validation. That's the whole "pattern transfer."

Pattern 2 — Keep validation in your existing stack. The bundle does not add a validation DSL; it relies on Symfony's File/Image constraints and MimeType. The Laravel analogue is unchanged: 'file' => ['required', 'file', 'max:10240', 'mimes:jpg,png,pdf']. Do not re-implement validation in the dispatch layer — normalize first, validate second, store third.

Pattern 3 — Storage abstraction, not local move(). The bundle's move()-to-directory default is fine for a demo; it is a scaling blocker in production. In Laravel, always Storage::disk('s3')->putFile('uploads', $file) via Flysystem. In Symfony, swap the "write file" step for a Flysystem service. The dispatch logic stays untouched.

Pattern 4 — Handler-per-format for teams. If you expect more shapes (chunked/resumable, remote-URL fetch, zipped batches), define:

interface UploadHandler {
    public function supports(Request $request): bool;
    public function toUploadedFile(Request $request): UploadedFile;
}

…and iterate a tagged collection in the middleware. That's the structure the bundle hints at but does not enforce; putting it in explicitly is what makes it maintainable.

Pattern 5 — Testing. With normalization as middleware, your existing feature test:

$file = UploadedFile::fake()->image('avatar.jpg');
$this->postJson('/api/upload', ['file' => base64_encode(file_get_contents($file))])
     ->assertCreated();

gives you coverage of the JSON path for free. Add one test per Content-Type variant; that's the entire test surface.

Gotchas and Tips

  • Base64 bloats memory ~33%, and worse on top of PHP's already-loaded request body. Set memory_limit and post_max_size explicitly, and enforce a hard per-request cap in middleware before you decode anything. The bundle does not protect you here; validation runs after the string is in memory.
  • base64_decode with strict=true. Silent corruption from a trailing newline or data-URI prefix (data:image/png;base64,) is a common bug. Strip the prefix, then strict-decode; reject invalid payloads with a 400 before constructing UploadedFile.
  • Filename sanitization is on you. $file->getClientOriginalName() is user-controlled and can contain ../ or null bytes. Never pass it straight to move() or a storage path. Use a generated name (Str::uuid() + safe extension) and keep the original only in metadata.
  • Content-Type spoofing. A client can send Content-Type: image/png with a PHP payload. Always re-check the real MIME via finfo (Laravel's mimes/mimetypes rules already do this for UploadedFile created via createFromBase — verify your middleware sets it consistently).
  • Temp-file leakage. Both the Symfony bundle and any Laravel reimplementation create temp files. Wrap in try/finally and register a reaper (cron or Laravel's schedule) for aborted-request orphans. This is the #1 silent operational failure of upload endpoints.
  • Symfony bundle registration quirk: because there is no Flex recipe, forgetting the config/bundles.php line means the listener never runs and $request->files->get('file') silently returns null for JSON requests. Symptom looks like a validation bug; the cause is a missing registration.
  • Listener priority. Symfony request listeners run in priority order. If your own form/login/auth listeners read the request body first, you can get double-reads or php://input exhaustion. Confirm the bundle's listener runs at a sensible priority before yours.
  • No UploadedFile from JSON? Check test: mode in createFromBase. When constructing UploadedFile manually, the 5th argument ($test = true) suppresses is_uploaded_file() failures — required for tests and for streamed bodies, easy to forget, and the resulting error message (The file ... was not uploaded) is unhelpful.
  • Chunked/resumable is not free. If you need tus or S3 multipart presigned, do not bend this pattern — go straight to the protocol. The single-endpoint dispatch is a solution for body shapes, not for large or resumable uploads.
  • Adopt-or-fork decision: the bundle is small (a listener + factory). A fork's real cost is ongoing ownership, not the initial port. For a Laravel team, the 1–2 days to reimplement natively will beat the multi-year cost of owning a vendored Symfony bundle. The correct day-to-day usage is: study it once, then build your own 60-line middleware.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource