tobento / service-picture-generator
Responsive picture generation workflow for PHP applications, including queueing, storage, and fallback handling.
Installs: 3
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/tobento/service-picture-generator
Requires
- php: >=8.4
- league/mime-type-detection: ^1.16
- psr/log: ^3.0
- tobento/service-file-storage: ^2.0
- tobento/service-imager: ^2.0
- tobento/service-picture: ^2.0
- tobento/service-queue: ^2.0
- tobento/service-tag: ^2.0
Requires (Dev)
- mockery/mockery: ^1.6
- monolog/monolog: ^3.9
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^12.3
- tobento/service-cache: ^2.0
- tobento/service-console: ^2.0
- tobento/service-container: ^2.0
- vimeo/psalm: ^6.13
Suggests
- tobento/service-cache: Required for unique queue jobs
- tobento/service-console: Required for console commands
README
The Picture Generator Service provides a powerful, framework-agnostic system for generating responsive images and <picture> elements in PHP applications.
It enables developers to define reusable picture configurations, generate optimized image variants, and store metadata for fast retrieval - all without relying on URL-based transformations.
The service is designed to be secure, predictable, and fully decoupled from HTTP.
It supports synchronous and queued generation, integrates with any file-storage backend, and outputs standard HTML <picture> elements that work in any PHP framework or templating system.
Key Features
- Definition-based picture generation Create named picture definitions that describe breakpoints, formats, crops, and responsive strategies - reusable across your entire application.
- Secure, URL-free architecture No query parameters, no runtime transformations, and no exposure of private storage. All images are generated server-side using controlled definitions.
- Pre-generation & regeneration Generate images on upload, on demand, or via CLI commands. Regenerate all images when definitions change.
- Queue support Offload heavy image processing to background workers using a
PictureQueueHandlerInterface. - Metadata repository Store and retrieve picture metadata (JSON) for fast rendering of
<picture>tags. - Storage-agnostic Works with any
StoragesInterfaceimplementation (local, cloud, Flysystem, etc.). - SEO-friendly static URLs All generated images use clean, human-readable paths without query strings or signatures. This results in stable, shareable URLs that search engines can index reliably and that remain consistent over time.
- PSR-compliant Uses PSR-7 streams and PSR-3 logging for maximum interoperability.
Table of Contents
Getting Started
Add the latest version of the picture generator project running this command.
composer require tobento/service-picture-generator
Requirements
- PHP 8.4 or above
Documentation
Workflow
When you display a picture, the service checks whether the required images for the given
path and definition already exist.
-
If the images are not yet generated and queueing is enabled (default),
a picture job is dispatched to the configured queue.
The view immediately receives a lightweight fallback picture based on the original resource. -
If queueing is disabled (
queue: false),
the images are generated synchronously during the request and the final<picture>or<img>tag is returned immediately. -
Once the queued job finishes, all defined image variants are created and stored.
Subsequent requests automatically render the fully generated picture.
This workflow ensures fast responses when using background processing, while still allowing on-demand synchronous generation when needed.
Fallback Images
If a picture cannot be generated immediately (for example, when queueing is enabled), a fallback image is returned:
- If the file has a public URL, the fallback uses that URL.
- If no public URL is available, a base64-encoded data URI is returned.
- If fallback creation fails (e.g., the file is not a valid image), a
NullPictureTagis returned and a warning is logged.
Basic Usage
Once you have created the Picture Generator, you can use it to display
responsive pictures based on a path, resource, and definition.
Within your view file, call the generate method and render the returned picture tag:
Example Using a Named Definition
Named definitions allow you to reuse picture configurations across your application.
See the Picture Definitions section for how to register them.
<?= $pictureGenerator->generate( // The path to the original image within the storage: path: 'path/to/image.jpg', // The storage name where the image is located: resource: 'storage-name', // The named picture definition to use: definition: 'name', // Whether to queue image generation (recommended): queue: true, // default // Whether private storages may be read: allowPrivateStorage: false, // default )->imgAttr('alt', 'Alt Text') ?>
About allowPrivateStorage
Use this option when the image source is a private storage.
(For most cases, using a public storage as the image source is recommended.)
false(default): private storages are not read and aNullPictureTagis returned.true: allow reading from private storage (useful for backend-only sources such as uploads or protected files).
Depending on your definition, the output may look like:
<picture> <source srcset="https://example.com/path/to/image.webp" type="image/webp"> <source srcset="https://example.com/path/to/image.jpg" type="image/jpeg"> <img src="https://example.com/path/to/image.jpg" alt="Alt Text"> </picture>
Using named definitions provides several advantages:
- You can crop images based on the definition
- You can define different picture sets per view theme
Example Using a Definition Instance
use Tobento\Service\Picture\Definition\ArrayDefinition; <?= $pictureGenerator->generate( path: 'path/to/image.jpg', resource: 'storage-name', definition: new ArrayDefinition('product-main', [ 'img' => [ 'src' => [600], 'alt' => 'Alternative Text', 'loading' => 'lazy', ], // You may define any sources: 'sources' => [ [ 'media' => '(min-width: 800px)', 'srcset' => [ '' => [1200, 500], ], 'type' => 'image/webp', ], [ 'media' => '(max-width: 600px)', 'srcset' => [ '' => [600, 400], ], 'type' => 'image/webp', ], ], ]), ) ?>
See the Picture Definition section for more details on defining picture configurations.
Example Using an Imager Resource
use Tobento\Service\Imager\ResourceInterface; <?= $pictureGenerator->generate( path: 'path/to/image.jpg', resource: $resource, // ResourceInterface definition: 'name', )->imgAttr('alt', 'Alt Text') ?>
See Picture Creating - Create Picture From Resource for more information.
Creating Picture Generator
To use the picture generator, you first need to create the required components:
a PictureRepository, an optional PictureQueueHandler, and finally the PictureGenerator
itself.
use Psr\Log\LoggerInterface; use Tobento\Service\FileStorage\StoragesInterface; use Tobento\Service\Picture\DefinitionsInterface; use Tobento\Service\Picture\Generator\PictureGenerator; use Tobento\Service\Picture\Generator\PictureGeneratorInterface; use Tobento\Service\Picture\Generator\PictureRepository; use Tobento\Service\Picture\Generator\PictureRepositoryInterface; use Tobento\Service\Picture\Generator\Queue\PictureQueueHandler; use Tobento\Service\Picture\Generator\Queue\PictureQueueHandlerInterface; use Tobento\Service\Picture\PictureCreatorInterface; use Tobento\Service\Queue\QueueInterface; // The repository stores picture metadata and generated image paths: $pictureRepository = new PictureRepository( pictureStorageName: 'generated-picture-data', // Storage for picture metadata imageStorageName: 'generated-images', // Storage for generated images storages: $storages, // StoragesInterface ); // Optional queue handler for background image generation: $queueHandler = new PictureQueueHandler( queue: $queue, // QueueInterface queueName: 'name', // Queue name to dispatch picture jobs to ); // Create the picture generator: $pictureGenerator = new PictureGenerator( pictureRepository: $pictureRepository, // PictureRepositoryInterface storages: $storages, // StoragesInterface definitions: $definitions, // DefinitionsInterface queueHandler: $queueHandler, // Or null for no queueing at all uniqueJob: true, // Whether queued jobs should be unique pictureCreator: null, // Custom PictureCreatorInterface or null (default) logger: $logger, // LoggerInterface or null ); var_dump($pictureGenerator instanceof PictureGeneratorInterface); // bool(true)
Private Storage Behavior
When generating pictures from private file storage, the behavior depends on the
allowPrivateStorage and queue parameters:
| Condition | Behavior |
|---|---|
allowPrivateStorage = false |
Returns a NullPictureTag and does not queue a job |
allowPrivateStorage = true and queue = false |
Generates the picture synchronously |
allowPrivateStorage = true and queue = true |
Queues a job and returns a fallback image |
Related Documentation
-
Picture Service - Definitions
Learn how to register and manage picture definitions -
Queue Service
For configuring queues used for background image generation -
File Storage Service
For configuring storages used for original and generated images
Queue Worker
If you use background image generation via the queue handler,
you may install the Console Service to run the queue worker:
composer require tobento/service-console
See the Queue Service - Console for details on running the worker.
Unique Jobs
The queue handler uses unique jobs by default.
To enable unique job locking, the Queue Service requires a PSR-16 CacheInterface implementation.
You may use any PSR-16 compatible cache. For example, if you use a container and want a simple drop-in implementation:
composer require tobento/service-cache
See the Queue Service - Unique Parameter
for details on how unique job locking works and how to provide your own cache.
Warning:
If unique jobs are disabled or no cache is available, duplicate picture generation jobs may be queued and processed. This can lead to redundant work, increased queue load, and multiple workers generating the same images.
Logging
If a LoggerInterface is provided, the picture generator may log useful information such as picture generation steps, queue dispatching, or errors that occur during processing. Logging is optional and depends on the logger implementation you supply.
Picture Definitions
Picture definitions can be stored wherever you prefer.
You are free to organize them in any directory structure that fits your application.
A common convention is to place JSON definition files inside a dedicated folder, for example:
app/
views/
picture-definitions/
product-main.json
...
For a detailed explanation of how JSON-based definitions work, how they are structured, and how to load them, see the
Json Files Definitions section of the Picture Service documentation.
Clearing Generated Pictures
You can clear (delete) generated pictures either via the console command or directly through the PictureGenerator.
Using Console Command
See Picture Clear Command section.
Using Picture Generator
use Tobento\App\Media\Picture\PictureGeneratorInterface; class SomeService { public function __construct( protected PictureGeneratorInterface $pictureGenerator, ) {} private function deleteGeneratedPictures(): void { $repository = $this->pictureGenerator->pictureRepository(); // Delete the generated picture (all created images) for a specific path and definition: $repository->delete( path: 'foo/image.jpg', definition: 'product-main', ); // Delete all generated pictures for a specific definition: $repository->deleteAll( definition: 'product-main', ); // Delete all generated pictures for a specific original path: $repository->deleteAllByPath( path: 'foo/image.jpg', ); } }
Console
Picture Clear Command
This package integrates with the Console Service to provide a command for clearing generated pictures.
Requirements
composer require tobento/service-console
Example
To clear all generated pictures, run:
php ap picture:clear
To clear generated pictures for specific definitions only:
php ap picture:clear --def=product-main --def=post
Using the App Ecosystem
If you are building on the Tobento App Ecosystem, you may use the app-media package, which automatically configures the picture generator, queue worker, cache, and storages. In that case, no manual setup is required.
Framework Integration Notes
This package is framework-agnostic.
If you integrate it into a framework that provides its own queue or filesystem
(e.g. Laravel, Symfony, etc.), you may need to implement small adapters:
-
Queue Adapter Queue Service
ImplementTobento\Service\Queue\QueueInterfaceto forward jobs to your framework's queue system. -
File Storage Adapter File Storage Service
ImplementTobento\Service\FileStorage\StorageInterfaceto map your framework's filesystem to the storage abstraction used by this package.
These adapters are typically thin wrappers, as both interfaces are intentionally minimal.