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

Upload Laravel Package

siriusphp/upload

SiriusPHP Upload is a framework-agnostic PHP library for handling file uploads with flexible, rule-based validation. It checks files by size, extension, MIME type, and image dimensions, and supports optional image processing such as resizing and cropping.

View on GitHub
Deep Wiki
Context7

Getting Started

siriusphp/upload is not a Laravel package — there is no service provider, facade, config file, or publishable assets. It is a plain library you wire into a controller or a small service class yourself. That is the first thing to internalize: everything Laravel gives you for free (Storage, UploadedFile, validation messages, disk config) you either keep using alongside it or replace manually.

composer require siriusphp/upload

Where to look first, in order:

  1. Sirius\Upload\Handler — the orchestrator. You construct it with the $_FILES array, attach a container and rules, then call process().
  2. Sirius\Upload\Container\Local and Sirius\Upload\Container\Filesystem — where the file physically lands. Filesystem wraps a Flysystem adapter, which is the only reason this library is interesting to a Laravel developer (see Gotchas).
  3. src/Rule/ — Extension, Size, Image, Required. These are the actual value-add.
  4. Sirius\Upload\Result — the object returned by process(). It carries validity, messages, and the stored filename.

Minimal first use case — replacing $file->store(...) plus a pile of manual checks in a controller:

use Sirius\Upload\Handler;
use Sirius\Upload\Container\Local;

public function store(Request $request)
{
    $handler = new Handler($_FILES);
    $handler->setContainer(new Local(storage_path('app/public/uploads')));

    $handler->addRule('extension', ['allowed' => ['jpg', 'jpeg', 'png', 'webp']]);
    $handler->addRule('size',      ['max' => '5M']);
    $handler->addRule('image',     ['width' => 1600, 'height' => 1600]);

    $result = $handler->process($_FILES['avatar']);   // field name in some versions

    if (! $result->isValid()) {
        throw ValidationException::withMessages(['avatar' => $result->getMessages()]);
    }

    // $result->getName() is the stored filename inside the container
    $user->update(['avatar' => 'uploads/' . $result->getName()]);
}

That is essentially the whole library. If that snippet already looks like something you could write in ten lines with Laravel's native tools, that instinct is correct — see the tips below.

Implementation Patterns

Wrap it in a dedicated class, not a controller. The Handler is stateful (container + rules) and request-scoped. Build a small AvatarUploader / DocumentUploader service that owns the rule set for one domain concept, and inject it where needed. This keeps $_FILES touching exactly one place in the codebase.

class DocumentUploader
{
    public function __construct(private string $diskPath) {}

    public function handle(array $fileArray): Result
    {
        $handler = new Handler([$fileArray]);
        $handler->setContainer(new Local($this->diskPath));
        $handler->addRule('extension', ['allowed' => ['pdf', 'docx']]);
        $handler->addRule('size', ['max' => '10M']);

        return $handler->process($fileArray);
    }
}

Rules are per-handler, so scope them per use case. There is no global rule registry. An avatar handler and an invoice handler should be different objects with different rules; do not try to build one "universal uploader."

Bridge Laravel validation and Sirius validation deliberately. A robust pattern is: run Laravel's $request->validate([...]) first so you get standard 422 responses and the lang/ messages your frontend already understands, then use Sirius for the storage step and any constraint Laravel does not express well (e.g., exact pixel bounds). Map $result->getMessages() into a ValidationException so the API contract stays consistent.

Process, then persist, then maybe roll back. process() writes to the container immediately. If your subsequent Eloquent write fails, you own the cleanup:

DB::transaction(function () use ($result, $request) {
    $model = Model::create(['file' => $result->getName()]);
    // if this throws, catch outside and delete the container file
});

Alternatively, validate with a throwaway handler, commit the DB row, then call process() last. Slower to reason about but avoids orphan files.

Multiple file inputs need normalization. For <input name="photos[]" multiple>, $_FILES['photos'] is column-major (['name' => [...], 'tmp_name' => [...]]). Sirius expects a single file array per process() call, so transpose the array first and loop. Do not assume the library does this for you.

Use the Filesystem container when you actually need S3/R2/etc. If you are staying on local disk, Local is fine. If you need object storage, the Flysystem-backed container lets you keep one API across disks — but check the Flysystem major version it pins before committing (see Gotchas), because Laravel 10/11 ship Flysystem v3.

Test at the rule level rather than the HTTP level. The rules operate on Sirius\Upload\File objects, which you can construct from a raw array pointing at a fixture file. That gives you fast unit tests for "rejects 10MB PDF" and "rejects 3000px image" without a real multipart request.

Gotchas and Tips

It consumes $_FILES arrays, not UploadedFile objects. Do not pass $request->file('avatar'). Sirius reads keys like tmp_name, name, size, error, type. In Laravel you either pass $_FILES['avatar'] directly or rebuild the array from the UploadedFile. This mismatch is the single biggest source of confusion.

Testing breaks on move_uploaded_file(). The Local container ultimately performs a real upload move. UploadedFile::fake() files are not real HTTP uploads (is_uploaded_file() returns false), so feature tests that exercise the full handler can fail for reasons unrelated to your logic. Unit-test the rules, or use a container that copies rather than moves.

PHP upload errors are data, not exceptions. UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_PARTIAL, etc. surface as invalid results with messages. Always check isValid() before calling getName(); do not assume process() throws.

The package is old and lightly maintained. Its dependency constraints (Symfony components, league/flysystem) were last meaningfully updated years ago. Before adopting, run composer why-not siriusphp/upload against your Laravel version and expect friction — particularly Flysystem v1 vs v3, which are incompatible. If you only need local disk, you can often skip the container entirely and just use Laravel's Storage.

Overlap with Laravel is large. Storage::disk()->putFile() plus 'image', 'mimes:', 'max:', and 'dimensions:' validation rules already cover most of what the Extension, Size, and Image rules do. Adopt Sirius only if you specifically want its container abstraction or its Result-object flow; otherwise you are adding a dependency and a second validation vocabulary.

Message translation is separate from Laravel. Sirius rules return their own English messages. They do not read lang/en/validation.php. If your app is localized, you will be mapping rule messages yourself, likely in the exception bridge.

Filenames are sanitized and collisions are handled — but verify the behavior you get. Understand whether the handler overwrites on name collision or appends a suffix, and where non-ASCII characters go. If your product needs a deterministic naming scheme (UUIDs, hash prefixes), rename after process() or set the name explicitly rather than relying on defaults.

The image rule validates, it does not resize. It reads dimensions via getimagesize/GD and rejects out-of-bounds images. It will not generate thumbnails or strip EXIF. Large images still cost memory during validation, and you still need Intervention Image (or similar) plus a queued job for derivatives.

Security is on you. An extension rule checks the submitted filename, not the bytes. Pair it with a real MIME/content check, store uploads outside the webroot or on a disk with PHP execution disabled, and never trust getClientFilename() as a path component. The library moves the file; it does not make it safe.

Don't share a Handler instance. It holds container state and mutates internal file state during process(). New instance per request, per file.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core