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

Php Image Uploader Laravel Package

ptcong/php-image-uploader

A lightweight PHP library for handling image uploads, with validation, file type checks, resizing, and save handling. Suitable for simple upload workflows in PHP apps and Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the runtime contract

composer require ptcong/php-image-uploader

This is a plain PHP library — there is no service provider, no facade, no config publish, no auto-discovery. If composer require resolves, you get a ptcong\RemoteImageUploader\* namespace you must wire up yourself. Before writing any app code, verify it actually loads under your PHP version:

php -r "require 'vendor/autoload.php'; var_dump(class_exists('ptcong\\RemoteImageUploader\\Factory'));"

Treat a false here, or a PHP 8 deprecation/fatal from the adapters, as a fork-or-abandon decision point rather than something you patch into your app.

2. Where to look first, in this order

  • README.md — per-adapter credential snippets; the config keys genuinely differ between adapters.
  • src/Factory.php (or equivalent factory/entry class) — this is how you instantiate an adapter and is the only API surface you should depend on.
  • src/Adapters/ — read Imgur first, because it is the only adapter with a live API. Picasa, Flickr, and Imageshack are historical.
  • The adapter's auth methods (getAuthUrl / login / token setters) — only relevant if you're doing the OAuth dance yourself.

3. Configuration in a Laravel app

Put credentials in config/services.php so you get env() caching and per-environment overrides for free:

// config/services.php
'image_uploader' => [
    'default' => env('IMAGE_UPLOADER_DRIVER', 'imgur'),
    'drivers' => [
        'imgur' => [
            'client_id'     => env('IMGUR_CLIENT_ID'),
            'client_secret' => env('IMGUR_CLIENT_SECRET'),
            'access_token'  => env('IMGUR_ACCESS_TOKEN'), // null => anonymous
        ],
    ],
],

4. First use case: validated upload → remote URL → Eloquent

// app/Services/RemoteImageService.php
namespace App\Services;

use ptcong\RemoteImageUploader\Factory;

class RemoteImageService
{
    public function upload(string $absolutePath): array
    {
        $driver = config('services.image_uploader.default');
        $config = config("services.image_uploader.drivers.{$driver}");

        $uploader = Factory::create($driver, $config);  // confirm exact factory signature in src/
        $uploader->upload($absolutePath);

        return [
            'driver'     => $driver,
            'url'        => $uploader->getImageUrl(),
            'thumbnail'  => method_exists($uploader, 'getThumbnailUrl') ? $uploader->getThumbnailUrl() : null,
            'deletehash' => method_exists($uploader, 'getDeleteHash') ? $uploader->getDeleteHash() : null,
        ];
    }
}
// Controller
public function store(Request $request, RemoteImageService $images)
{
    $request->validate(['image' => ['required', 'image', 'max:10240']]);

    $result = $images->upload($request->file('image')->getRealPath());

    return Media::create([
        'user_id'     => $request->user()->id,
        'driver'      => $result['driver'],
        'remote_url'  => $result['url'],
        'delete_hash' => $result['deletehash'],
    ]);
}

That end-to-end path — validated file, uploaded to Imgur, URL persisted to a model — is the only workflow worth building the rest of your integration around.

Implementation Patterns

Wrap it behind a contract, never call Factory::create() from controllers. The library is old, untyped, and returns loosely-shaped data. One RemoteImageService (or an interface + adapter classes) gives you a seam to fake in tests and swap later:

// AppServiceProvider::register()
$this->app->singleton(ImageUploader::class, fn () => new RemoteImageService(
    config('services.image_uploader')
));

Mirror Laravel’s Manager pattern for multi-driver support. If you ever need Imgur and a second backend, don’t call the factory in five places — build a tiny resolver keyed off config('services.image_uploader.drivers') that caches instantiated uploaders per request, so you can add your own adapter (S3, Cloudinary) next to the library’s without touching callers.

Always stage through a temp file for Storage-backed uploads. UploadedFile::getRealPath() works for direct request uploads, but anything coming from Storage::get() has no real path. Write it out, upload, then clean up in a finally:

$tmp = tempnam(sys_get_temp_dir(), 'img');
try {
    file_put_contents($tmp, Storage::get($path));
    return $images->upload($tmp);
} finally {
    @unlink($tmp);
}

Push uploads into a queued job. These are blocking cURL calls to consumer-grade APIs; putting them in the request cycle ties your response time to Imgur’s latency. The job pattern also buys you retries, which you will need:

class UploadImageToHost implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [5, 30, 120];

    public function __construct(public int $mediaId) {}

    public function handle(RemoteImageService $images): void
    {
        $media = Media::findOrFail($this->mediaId);
        $media->update($images->upload($media->local_path));
    }

    public function failed(Throwable $e): void
    {
        Media::whereKey($this->mediaId)->update(['upload_failed_at' => now()]);
    }
}

Persist the hosted URL and the delete hash. Imgur returns a per-image deletion token; store it so you have remote-cleanup capability when a model is deleted, instead of leaking orphaned images forever. Do remote deletes in an Observer or a queued Deleting listener — never synchronously inside the model event.

For the OAuth providers (Flickr/Google), model the token as app data, not library state. The package expects tokens passed in each time; it won’t refresh them for you. Keep access_token / refresh_token / expires_at in a social_tokens table with encrypted casts, and refresh in a dedicated method that writes the new token before returning. Routes: /oauth/{provider}/redirect → getAuthUrl(), /oauth/{provider}/callback → exchange + persist.

Testing: bind a FakeImageUploader implementing your own interface in TestCase::setUp(). Do not attempt to intercept the library’s HTTP calls — it doesn’t use Guzzle/PSR-18, so there’s no mock handler to attach. Your interface boundary is the test seam, and that’s the main architectural reason to have one.

Optional facade for terse call sites — ImageUploader::driver('imgur')->upload($path) — but only once the service is stable; a facade over an untyped third-party client mostly obscures where the failure happened.

Gotchas and Tips

Provider viability is the dominant risk, and it’s not symmetric.

  • Picasa — the Picasa Web Albums API was shut down in 2016. The adapter cannot work as written; Google Photos Library API has different scopes and a different upload model entirely.
  • Imageshack — the public API has been effectively dead for years.
  • Flickr — OAuth 1.0a still functions, but the adapter’s auth flow is dated and you’ll likely fight signature issues; also check Flickr’s current upload terms for your use case.
  • Imgur — the only adapter with a realistic path. Verify current client/rate-limit rules before committing; anonymous uploads have been progressively restricted, so plan for OAuth with a registered app.

Plan for the probability that you rewrite the upload call in 6–18 months. The wrapping service is what makes that cheap.

PHP compatibility. This is an older, untyped codebase. Expect PHP 8.x issues (curl_close on a CurlHandle, dynamic properties, implicit nullable params). Do a smoke test on your exact PHP minor before building on it. If it breaks, fork and pin — don’t shim around it in application code.

Package name ≠ repo name. The composer package is ptcong/php-image-uploader; the repository is php-remote-image-uploader. Documentation, issue reports, and namespace all reference the remote-image-uploader name. Minor, but it wastes time when you’re searching for a solution to an error.

Error handling is generic. The library throws base \Exception (or returns false in places) with provider messages, not typed exceptions. In your wrapper, catch \Throwable, log the raw message and the file context, and rethrow a domain exception (ImageUploadFailed) so callers and Horizon failures are readable. Never let the raw provider string reach a user-facing flash message — Imgur’s errors can echo back parts of the request.

getRealPath() returns null surprisingly often — after move()/store(), for Storage files, and for UploadedFile instances constructed in tests via UploadedFile::fake()->createWithContent(). Guard with an explicit check and a clear exception; the library will happily fail later with an unhelpful “file not found.”

Returned URLs are inconsistent. Imgur can hand back http:// and sometimes a page URL rather than the direct image. Normalize to https:// in your wrapper and verify the suffix looks like an image (i.imgur.com/...jpg) before persisting — otherwise you store a link that 404s or renders as HTML.

Config keys are not uniform across adapters. Imgur uses client_id/client_secret; Flickr uses consumer_key/consumer_secret/oauth_token/oauth_token_secret. Don’t try to unify them into one flat config shape — keep a drivers.{name} block and pass each array through untouched.

No timeout control at the config layer. The cURL handles are created inside the adapters. If you need hard timeouts, either subclass the adapter and override the request method, or enforce it at the job level with retryUntil and a supervisor timeout. A hung upload should never occupy a worker indefinitely.

Extension points are thin but real. Subclass an existing adapter to override upload() / getImageUrl() for URL normalization, or write your own adapter class with the same public shape and register it in your own resolver alongside the library’s. Don’t patch vendor/ — fork and use a VCS repositories entry.

Debugging tip: adapters typically keep the last raw response internally. When an upload fails, dump that (or dd() the adapter object in a tinker session) before theorizing — the provider’s message usually names the exact problem (bad scope, expired token, file too large). Add a Log::debug() of the response body inside your wrapper during integration and strip it once stable.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons