picsmage/laravel

Official Laravel SDK for serving ready PicsMage Open Graph images.

Maintainers

Package info

github.com/esyx0/picsmage-laravel

pkg:composer/picsmage/laravel

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-08-09 17:04 UTC

This package is auto-updated.

Last update: 2026-08-09 20:19:40 UTC


README

The official Laravel SDK for synchronizing a site image catalog and serving only last-known-ready, immutable PicsMage Open Graph image URLs. Page rendering reads Laravel Cache only: it never calls PicsMage, queries a database, dispatches a job, or waits for image generation.

Requires PHP 8.4 and Laravel 13. The package is licensed under MIT.

Install

composer require picsmage/laravel
php artisan vendor:publish --tag=picsmage-config

Configure the site-scoped credential through the host's secret store:

PICSMAGE_URL=https://picsmage.com
PICSMAGE_SITE_ID=01...
PICSMAGE_API_KEY=pm_live_...
PICSMAGE_CDN_HOST=cdn.picsmage.com
PICSMAGE_CACHE_STORE=file
PICSMAGE_MANIFEST_REFRESH_ENABLED=true
PICSMAGE_MANIFEST_REFRESH_INTERVAL=5

Do not print, commit, or log the API key.

Register the catalog

Create one application-owned catalog. It owns only external IDs and current page metadata; PicsMage owns generation, retry, quota, readiness, and object/CDN reconciliation.

<?php

namespace App\Seo;

use PicsMage\Laravel\Contracts\ImageCatalog;
use PicsMage\Laravel\Data\ImageDefinition;

final class OgImageCatalog implements ImageCatalog
{
    public function definitions(): iterable
    {
        yield new ImageDefinition(
            externalId: 'blog:index',
            canonicalUrl: 'https://example.com/blog',
            title: 'Example blog',
            description: 'Useful updates from Example.',
        );

        // Yield the rest of the site's definitions.
    }
}

Register it in the application's service provider:

use App\Seo\OgImageCatalog;
use PicsMage\Laravel\Facades\PicsMage;

public function boot(): void
{
    PicsMage::catalog(OgImageCatalog::class);
}

Deploy and prewarm

Validate without network access:

php artisan picsmage:catalog:sync --dry-run

Synchronize the entire catalog in one atomic request, wait on one catalog-level status resource, then refresh the ready manifest:

php artisan picsmage:prewarm --wait --timeout=900

The package optionally registers picsmage:manifest:refresh every five minutes with onOneServer() and withoutOverlapping(). PicsMage uses its own PICSMAGE_CACHE_STORE instead of inheriting the host default, so a database-backed application cache does not add a query to page rendering. The default file store is suitable for workers on one Forge server because Laravel's storage directory is shared across releases; use a shared Redis store for multiple application servers. The selected store must support atomic locks. The host must run Laravel's normal scheduler:

* * * * * cd /path-to-app && php artisan schedule:run >> /dev/null 2>&1

Other commands:

php artisan picsmage:catalog:sync
php artisan picsmage:manifest:refresh

An empty full replacement requires the explicit --allow-empty option.

Render OG metadata

Pass the current definition metadata directly. This computes its source digest without loading the registered catalog, so domain catalogs may safely use this method while the registered catalog enumerates those same sources.

$image = PicsMage::ogImage(
    externalId: 'blog:index',
    title: $title,
    description: $description,
    canonicalUrl: $canonicalUrl,
    fallback: asset('images/og-fallback.webp'),
    alt: 'Example social preview',
);

return [
    'title' => $title,
    'description' => $description,
    ...$image->toSeoArray(),
];

toSeoArray() returns:

[
    'image_url' => 'https://cdn.picsmage.com/.../{fingerprint}.jpg',
    'image_alt' => 'Example social preview',
    'image_type' => 'image/jpeg',
    'image_width' => 1200,
    'image_height' => 630,
]

Generated entries are always JPEG. Fallback URLs report PNG, WebP, or SVG MIME from their path, defaulting to JPEG.

An already-constructed definition can be resolved with no catalog lookup:

$url = PicsMage::urlFor($definition, $fallback);

PicsMage::url($externalId, $fallback) is available as a convenience when loading the registered catalog at runtime is safe. Recursive catalog resolution fails closed to the fallback.

The SDK returns the fallback for a cold or malformed cache, an unknown ID, a changed source digest, an invalid URL/fingerprint/dimension/media type, or a cache read exception. A refresh transport failure, rate limit, or server error leaves the last-known-good manifest untouched. A valid 200 replaces the whole map, so absence is a tombstone; 304 retains it.

Testing

$fake = PicsMage::fake()
    ->ready('blog:index', 'https://cdn.example.test/ready.jpg')
    ->missing('guide:intro');

// Exercise application SEO code...

$fake->assertCatalogDefinitionCount(2)
    ->assertCatalogContains('blog:index')
    ->assertResolved('blog:index')
    ->assertFallbackUsed('guide:intro');

Public HTTP contract expected from PicsMage

The SDK expects these authenticated, site-scoped endpoints:

  • GET|PUT /api/v1/sites/{site}/catalog
  • GET /api/v1/sites/{site}/catalog/revisions/{catalog_revision}
  • GET /api/v1/sites/{site}/ready-manifest

Catalog replacement uses a strong ETag precondition, an Idempotency-Key, and a SHA-256 revision over canonical JSON { "schema_version": 1, "entries": [...] }. The ready manifest supports strong ETag/If-None-Match, returns 200 or 304, and contains only 1200×630 JPEG entries whose HTTPS CDN basename is exactly {fingerprint}.jpg with no credentials, port, query, or fragment.

The package retries only connection failures, 5xx responses, and explicitly retryable 429 rate_limited responses, with explicit connect/total timeouts and at most three attempts. Authentication, validation, quota, conflict, and precondition errors fail immediately. Laravel events expose only safe site/revision/count/error-code data or a hashed external ID; the package never logs credentials or page content.

Package development

composer install
composer validate --strict
composer check-platform-reqs
composer audit --locked
composer check