Search by

smallpict / smallpict-php

4IP

Official PHP SDK for SmallPict Image Optimization API

Package info

github.com/smallPict/smallpict-php

Homepage

pkg:composer/smallpict/smallpict-php

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.0.8 2026-11-09 00:50 UTC

This package is auto-updated.

Last update: 2026-11-09 00:56:15 UTC


README

Official PHP SDK for the SmallPict Image Optimization API โ€” high-performance next-gen image transcoding (AVIF, WebP), smart compression, Edge CDN delivery, and cache purging.

Latest Version on Packagist Software License PHP Version

โšก Features

  • ๐Ÿ˜ Broad PHP Compatibility: Supports PHP 7.4 through PHP 8.5 seamlessly.
  • ๐Ÿ”Œ Zero-Dependency Default: Built-in high-performance native cURL client; PSR-18 compatible for modern stacks.
  • ๐Ÿ“ฆ Optional Laravel Auto-Discovery: Instant integration via SmallPictServiceProvider and SmallPict Facade.
  • ๐Ÿ›ก๏ธ Secure HMAC-SHA256 & Bearer Auth: Tamper-proof payload verification.
  • โœจ 4 Core Unified Methods: optimize(), getQuota(), purgeCdn(), and validateKey().
  • ๐Ÿ”„ Resilience & Fault Tolerance: Automatic Idempotency-Key UUID injection, 30s timeouts, and exponential backoff with jitter on HTTP 429/5xx.
  • ๐Ÿ”’ Zero-Leak Privacy: API keys and credentials are automatically redacted from error traces and exception logs.

๐Ÿ“ฅ Installation

composer require smallpict/smallpict-php

๐Ÿš€ Quick Start

1. Standalone PHP Example

<?php

require_once __DIR__ . '/vendor/autoload.php';

use SmallPict\Client;
use SmallPict\Models\OptimizeOptions;
use SmallPict\Models\ImageFormat;

$client = new Client(
    getenv('SMALLPICT_API_KEY'),
    getenv('SMALLPICT_SECRET_KEY') // Optional HMAC Secret Key
);

$imageContent = file_get_contents('hero-banner.png');

$result = $client->optimize(
    $imageContent,
    new OptimizeOptions(
        ImageFormat::AVIF, // Target format: 'avif', 'webp', 'auto', etc.
        80,                // Quality: 1-100
        1920               // Max width in pixels
    )
);

echo "Optimized CDN URL: " . $result->getUrl() . "\n";
echo "Saved: " . $result->getSavingsPercentage() . "% (" . $result->getBytesSaved() . " bytes)\n";

// Compression mode: CompressionMode::LOSSY (default), EXTREME, LOSSLESS or AUTO.
// AUTO picks, per image, the lowest quality that keeps the visual score (SSIMULACRA 2) at or
// above 70 and ignores quality (WP Pro/Business, Velocity, Momentum, sandbox: 5 per minute).
// Where it cannot search (PNG or GIF output, animation, time limit) the job is lossy.
// The server checks it against your plan and answers 403 mode_not_in_plan
// (PermissionDeniedException, allowed modes in getDetails()['allowed_modes']).
// The old 'lossless' => true option is deprecated; use 'mode' instead.
$result = $client->optimize($imageContent, [
    'format' => ImageFormat::WEBP,
    'mode'   => \SmallPict\Models\CompressionMode::LOSSLESS,
]);

// Extreme gives the smallest files on every plan. Leave quality out so the server
// uses its default for the output format; a quality you set is kept or raised to the
// format floor. Mode comparison: https://smallpict.app/en/docs/image-settings
$result = $client->optimize($imageContent, [
    'mode' => \SmallPict\Models\CompressionMode::EXTREME,
]);

// Optional upload folder (1-3 segments, e.g. "blog/2026"), sent as options.folder.
// The server lower-cases it; read it back with $result->getFolder() or getJobStatus()->getFolder().
// A malformed folder throws ValidationException (server code 400 invalid_folder).
$result = $client->optimize($imageContent, ['folder' => 'blog/2026']);

What optimize() does

optimize() returns the finished job, so getStatus() is always 'completed':

  1. It reads the source into memory (binary string, file path or stream resource) and sends POST /v1/optimize with the file name, type and exact byte count. The API queues the job and returns a pre-signed upload URL.
  2. It PUTs the bytes to that URL at once, with only Content-Type and Content-Length (no API key or signature) and no retry. If the storage refuses the upload, it throws SmallPictException with error code upload_failed and the HTTP status.
  3. It polls getJobStatus() every poll interval (default 1 second) for up to the wait timeout (default 120 seconds), then returns the URL, format, bytes saved, mode and quality from the job status.

A failed job throws SmallPictException with error code job_failed, the server's message and getDetails()['job_id']. A job still running after the wait timeout throws TimeoutException naming the job id; the job keeps running on the server, so you can call getJobStatus($jobId) later.

The API does not fetch URLs: an http:// or https:// source throws ValidationException before any request. Download the image and pass its bytes.

Poll interval and wait timeout are the last two Config arguments, in seconds:

use SmallPict\Config;
use SmallPict\Models\FallbackMode;

$client = new Client(new Config(
    getenv('SMALLPICT_API_KEY'),
    getenv('SMALLPICT_SECRET_KEY'),
    null,               // base URL
    30.0,               // request timeout (s)
    3,                  // max retries
    FallbackMode::THROW,
    1.0,                // poll interval (s)
    120.0               // wait timeout (s)
));

2. Laravel Integration (Laravel 10 / 11)

In config/services.php:

'smallpict' => [
    'api_key' => env('SMALLPICT_API_KEY'),
    'secret_key' => env('SMALLPICT_SECRET_KEY'),
    'fallback_mode' => 'passthrough', // 'throw' | 'passthrough'
    'poll_interval' => 1.0,   // seconds between job status polls in optimize()
    'wait_timeout' => 120.0,  // seconds optimize() waits for the job
],

In your Controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use SmallPict\Laravel\Facades\SmallPict;
use SmallPict\Models\OptimizeOptions;
use SmallPict\Models\ImageFormat;

class MediaController extends Controller
{
    public function store(Request $request)
    {
        $file = $request->file('avatar');
        
        $result = SmallPict::optimize(
            $file->getRealPath(),
            new OptimizeOptions(ImageFormat::AUTO, 85)
        );

        return response()->json([
            'cdn_url' => $result->getUrl(),
            'format' => $result->getFormat(),
            'savings' => "{$result->getSavingsPercentage()}%",
        ]);
    }
}

3. WordPress Plugin / Theme Integration

use SmallPict\Client;
use SmallPict\Models\OptimizeOptions;

function my_custom_image_upload_handler($file_path) {
    $client = new Client(get_option('smallpict_api_key'));
    
    try {
        $result = $client->optimize($file_path, new OptimizeOptions());
        return $result->getUrl();
    } catch (\SmallPict\Exceptions\SmallPictException $e) {
        // Fallback safely to original image on error
        error_log("SmallPict optimization error: " . $e->getMessage());
        return $file_path;
    }
}

๐Ÿ“Š Account Quota & Edge CDN Purge

use SmallPict\Client;

$client = new Client(getenv('SMALLPICT_API_KEY'));

// 1. Check real-time quota
$quota = $client->getQuota();
echo "Plan: {$quota->getPlan()}, Quota used: {$quota->getQuotaPercentage()}%\n";

// 2. Invalidate CDN cache
$client->purgeCdn(['https://cdn.smallpict.app/opt/hero-banner.avif']);
echo "CDN cache invalidated successfully.\n";

// 3. Purge every file uploaded into one folder (or PurgeType::ALL for all your files).
// Folder and all purges are limited per hour (10 and 2); one more throws
// RateLimitException (purge_rate_limited), which the SDK does not retry.
$client->purgeCdn([], \SmallPict\Models\PurgeType::FOLDER, 'blog/2026');

Capacity and upgrade links

getQuota()->getCapacity() returns a SmallPict\Models\Capacity for the product of your API key: getStorage() (limit_bytes, used_bytes, base_bytes, addon_blocks), getCdn() (limit_bytes, used_bytes, status), getBooster() (active, wp_quota_bytes, api_transforms, cdn_bytes, next_expiry), getLapse() (unpaid-account state), getPeriodEnd() and getManageUrl(). The groups are arrays with the API's keys. A member that does not apply to your plan is null; getCapacity() is null when the server could not read it or is too old to send it. Capacity is read-only here: storage add-ons and boosters are bought in the dashboard.

$capacity = $client->getQuota()->getCapacity();
if ($capacity !== null && $capacity->getStorage() !== null) {
    $s = $capacity->getStorage();
    echo "Storage: {$s['used_bytes']} / {$s['limit_bytes']} bytes\n";
}

Every exception has getUpgradeUrl(): the dashboard page where the missing capacity is added (details.upgrade_url), or null. storage_quota_exceeded and account_read_only throw QuotaExceededException; mode_not_in_plan throws PermissionDeniedException with getAllowedModes(). The SDK never opens these links; show them to your user.

try {
    $client->optimize($imageContent);
} catch (\SmallPict\Exceptions\QuotaExceededException $e) {
    echo $e->getMessage() . ' Add capacity: ' . ($e->getUpgradeUrl() ?? 'https://smallpict.app/dashboard/billing');
}

๐Ÿงช Testing

composer test

๐Ÿ”’ Security & Schema Compatibility (2026)

  • mimeType & contentType Dual Support: OptimizeOptions supports both getMimeType() and getContentType() methods. Outgoing optimization requests automatically include both mime_type and content_type in the payload for zero-disruption compatibility across all SmallPict API versions.

๐Ÿ“„ License

MIT ยฉ SmallPict Engineering