orbis-cms / attachments-images-converter
Converts and compresses Orchid attachment images to a unified format per attachment group.
Package info
gitlab.com/orbis-cms/attachments-images-converter
pkg:composer/orbis-cms/attachments-images-converter
Requires
- php: ^8.3
- ext-gd: *
- intervention/image: ^3.0
- laravel/framework: ^13.0
- orbis-cms/core: 0.0.*
- orchid/platform: ^14.0
Requires (Dev)
- laravel/pint: ^1.24
- league/flysystem-memory: ^3.31
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 22:40:06 UTC
README
Converts Orchid attachment images to a chosen format (webp by default), resizes them to fit per-group bounds without upscaling, generates cropped variants, and optionally keeps the original file. Settings are declared per model and per attachment group.
Requirements
- PHP with the
gdextension (the converter uses Intervention Image's GD driver; WebP output needs a GD build with WebP support). - A queue worker. Conversion runs in
GenerateImageConversionsJob, queued after the surrounding database transaction commits.
Installation
Publish and run the migration (adds generated_conversions to Orchid's attachments table):
php artisan vendor:publish --tag=attachments-images-migrations
php artisan migrate
How it works
- A model that owns attachments declares its group and its conversion
settings, side by side, using
DeclaresImageConversions:
use OrbisCms\AttachmentsImages\Models\Traits\DeclaresImageConversions;
use OrbisCms\AttachmentsImages\Support\ConversionSettings;
use OrbisCms\AttachmentsImages\Support\ImageFormat;
class Entity extends Model
{
use DeclaresImageConversions;
public function imagesAttachmentGroup(): string
{
return 'images';
}
public static function imagesConversions(): ?ConversionSettings
{
return new ConversionSettings(
main: [1920, 1920],
sizes: [
'thumb' => [300, 300],
'medium' => [800, 600],
],
format: ImageFormat::Webp,
keepOriginal: false,
);
}
}
| Option | Meaning |
|---|---|
main | [maxWidth, maxHeight] of the main image. Images are only ever scaled down; 0 leaves that side unbounded. |
sizes | Named variants, each [width, height], cropped to fill exactly (cover). |
format | ImageFormat::Webp (default), Jpg or Png. Sets the stored extension and mime type. |
keepOriginal | Keep the uploaded file. It is recorded under the _original key; when the source already has the target format it is copied to <name>@original.<ext>. |
Return null (the default) to disable conversion for the model's group.
- Register the model once, at boot, in your application's service provider:
use OrbisCms\AttachmentsImages\Registries\AttachmentConversionRegistry;
public function boot(AttachmentConversionRegistry $imageConversions): void
{
$imageConversions->register(Entity::class);
}
A group can be registered only once: registering a second model with the
same imagesAttachmentGroup() throws a LogicException, so give each
model its own group.
- Your own
Attachmentmodel subclass usesHasImageConversionsand is registered with Orchid, so it's actually the class that gets instantiated on upload:
namespace App\Models;
use OrbisCms\AttachmentsImages\Models\Traits\HasImageConversions;
class Attachment extends \Orchid\Attachment\Models\Attachment
{
use HasImageConversions;
}
use Orchid\Platform\Dashboard;
Dashboard::useModel(\Orchid\Attachment\Models\Attachment::class, \App\Models\Attachment::class);
Without this binding no conversion is triggered.
- From there it's automatic: uploading an image into a registered group
dispatches
GenerateImageConversionsJob, which converts/resizes the main file, generates the namedsizes, and (optionally) preserves the original. Read a conversion's URL with$attachment->conversionUrl('thumb').
Supported images
JPEG (including image/jpg and image/pjpeg), PNG, WebP and BMP sources are
converted. BMP is read but never written. Everything else, GIF and SVG
included, is left exactly as uploaded: no conversion, no variants.
Disks
Files are read and written through Laravel's Storage as bytes, so any disk
works, including S3. A failed write makes the job throw
(ConversionWriteFailedException) even when the disk is not configured with
'throw' => true.
Reading URLs
conversionUrl() does not touch the disk: it returns the recorded URL, or the
main file's URL when that conversion was never recorded. Pass throw: true to
also verify the file exists and get a ConversionNotFoundException instead of
a fallback; on remote disks that costs one request per call.
Failures and retries
If a step fails, the job throws and the source file and the attachment's
metadata are left as they were; retry the job. A missing source file throws
ConversionSourceMissingException, a failed write
ConversionWriteFailedException.
Deleting
Deleting an attachment also deletes its main file and every generated variant
(including _original).
Upload deduplication is disabled
The package turns off Orchid's upload deduplication for every upload that goes
through Orchid\Attachment\File resolved from the container, including
Orchid's own upload endpoint. Each upload gets its own file. Code that builds
new File(...) directly keeps Orchid's deduplication.
Converting existing attachments
Attachments that were uploaded before the package was installed are converted with:
php artisan attachments-images:convert-existing
The command converts every attachment in a registered group that has no
generated conversions yet, using the same code as a normal upload. It runs
synchronously, so no queue worker is needed. The attachment model must use
HasImageConversions.
Use --dry-run to see how many attachments would be converted without
changing anything.
Backup. Before converting, the command saves:
- a CSV dump of the selected rows of the
attachmentstable, on thelocaldisk atattachments-images-backup/<timestamp>/attachments.csv; - a copy of each original file on the disk it lives on, at
attachments-images-backup/<timestamp>/<original path>.
If the backup cannot be written, nothing is converted. The command never deletes the backup. When it finishes it prints the commands that remove the dump and the copies on each disk.
Failures. An attachment that cannot be converted (a corrupt or missing
file, or a format the package does not handle, such as GIF or SVG) does not
stop the run. The command lists these in a table, saves them to
attachments-images-backup/<timestamp>/failures.csv on the local disk and
exits with code 1.
Running the command again converts only the attachments that still have no generated conversions.
Config
The only global setting is the encoding quality (default 80). Publish
the config file to change it:
php artisan vendor:publish --tag=attachments-images-config