Skip to content

Repository files navigation

Lemonade Image Component

PHPStan Tests Lint License

Standalone GD-based image component for PHP 8.1+.

This package provides image loading, resizing, cropping, generated image caching, fallback image generation and WebP output support. It is designed as a reusable Lemonade component and can also be integrated into other PHP projects when the expected storage layout is provided.

Requirements

  • PHP 8.1+
  • GD extension
  • mbstring extension

Installation

This package is not published on Packagist. Install it directly from the public GitHub repository by adding it as a Composer VCS repository in your project.

{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/johnnyxlemonade/component_image.git"
    }
  ],
  "require": {
    "lemonade/component_image": "dev-master"
  }
}

Then run:

composer update lemonade/component_image

Alternatively, configure the repository from the command line:

composer config repositories.lemonade-component-image vcs https://github.com/johnnyxlemonade/component_image.git
composer require lemonade/component_image:dev-master

Features

  • JPEG, PNG, GIF and WebP support
  • Image loading from file or string
  • Blank image creation
  • Resize, crop, fit and canvas-based transformations
  • Generated image cache
  • WebP support detection
  • Browser cache validation through If-Modified-Since
  • Filesystem cache validation against source image modification time
  • HTTP cache headers
  • Filesystem abstraction for cache writes
  • Typed component exceptions
  • Immutable request and response DTOs
  • PHPUnit test suite
  • PHPStan level 10 configuration
  • Memory-efficient streaming from cache in 8kB chunks with output buffer flushing

Basic usage

The public entrypoint is Lemonade\Image\AppImage::emit() with an immutable ImageRequest.

<?php

use Lemonade\Image\AppImage;
use Lemonade\Image\Generator\ImageRequest;

AppImage::emit(
    request: ImageRequest::create(
        level: 6,
        storageTypeId: 'gallery',
        moduleId: 12,
        artId: 345,
        baseName: 'example.jpg',
        args: 'w800-h600-z3-cffffff-q85-e1',
    ),
);

The component resolves the source file, checks browser and filesystem cache, generates a resized variant when needed, stores the generated image in cache and emits the HTTP image response.

Request parameters

Parameter Type Description
level int Directory split depth used when building the object storage path from artId.
storageTypeId `string int
moduleId `string int
artId `string int
baseName `string null`
args `string null`

Image option string

Image options are passed as a dash-separated string:

w800-h600-z3-cffffff-q85-e1

Supported tokens:

Token Example Description
w w800 Target width in pixels.
h h600 Target height in pixels.
q q85 Output quality. Invalid values are ignored. Values are normalized to the supported range by the parser.
c cffffff Canvas color as a 6-character hexadecimal value without #.
e e1 Enables or disables missing-image fallback handling. Supported values are 0 and 1.
z z3 Resize mode. See the resize mode table below.

Unknown or invalid tokens are ignored. If the same option is passed multiple times, the last valid value wins.

Examples:

w800
w800-h600
w800-h600-z3
w800-h600-z1-cf5f5f5
w800-h600-q85

Presets

Size presets are provided by ImageSizePresetConfig. The default configuration includes:

Preset Size
xss 32 × 32
xs 48 × 48
sm 96 × 96
md 160 × 160
lg 320 × 320
xl 640 × 640

Presets can be scaled by appending a numeric suffix:

md2

With the default configuration, md2 resolves to 320 × 320 and md3 resolves to 480 × 480.

Preset scale suffixes are supported up to the configured maximum scale. The default maximum scale is intentionally limited to prevent excessive generated image sizes.

Original mode

The special option original preserves the source dimensions:

original

Original mode resets width and height and bypasses the normal fallback dimensions and size limits.

Resize modes

Resize mode is controlled by the z token.

Mode Enum Meaning
z0 ImageResizeMode::Shrink Shrink only.
z1 ImageResizeMode::FitWithCanvas Fit into a canvas using the normal canvas scale.
z2 ImageResizeMode::Exact Exact resize.
z3 ImageResizeMode::Fit Fit into the requested box.
z4 ImageResizeMode::FitWithBiggerCanvas Fit into a canvas using a larger canvas scale.
z5 ImageResizeMode::FitWithMaxCanvas Fit into a canvas using the maximum canvas scale.

The internal implementation represents these modes through ImageResizeMode, while the public option string remains backward-compatible with the numeric z values.

The original token is represented internally as ImageResizeMode::Original. It is intentionally not exposed through the numeric z token.

Storage layout

By default, ImageStorageConfig::createDefault() uses the following base paths:

./storage
./storage/0/cache
./storage/0/cache/0/0
./themes/frontend/error.png

Meaning:

Config value Default
Storage base ./storage
Cache base ./storage/0/cache
Fallback cache directory ./storage/0/cache/0/0
Placeholder image file ./themes/frontend/error.png

Source images are resolved under:

{storageBase}/{moduleId}/{storageTypeId}/{splitArtId}/{baseName}

Generated cache variants are stored under:

{cacheBase}/{moduleId}/{storageTypeId}/{splitArtId}/

Fallback cache images are stored under:

{fallbackCacheDirectory}/{optionsHash}.png
{fallbackCacheDirectory}/{optionsHash}.webp

Storage type aliases

The directory resolver supports these storage type aliases:

Alias Resolved storage type
template template
thumbnail 1
gallery 2
editor 5

Numeric or string storage type identifiers can also be passed directly.

Directory splitting

The artId is converted to hexadecimal and split into nested directory segments according to level.

For example, with level: 6, the component creates a deterministic nested path from the object identifier. This keeps large image collections distributed across multiple directories instead of storing everything in a single flat folder.

Configuration

You can create a custom storage configuration:

<?php

use Lemonade\Image\ImageStorageConfig;

$config = new ImageStorageConfig(
    storageRoot: '/var/www/project',
    storageDirectory: 'storage',
    cacheDirectory: 'cache',
    fallbackModuleId: '0',
    fallbackStorageTypeId: '0',
    placeholderImageFile: '/var/www/project/themes/frontend/error.png',
);

AppImageFactory::createDefault() uses ImageStorageConfig::createDefault(). For custom integration, instantiate AppImageFactory directly with a custom ImageStorageConfig.

<?php

use Lemonade\Image\AppImageFactory;
use Lemonade\Image\Fallback\ImageFallbackConfig;
use Lemonade\Image\Generator\ImageRequest;
use Lemonade\Image\Http\ImageResponseEmitter;
use Lemonade\Image\ImageStorageConfig;
use Lemonade\Image\Utils\FileSystem;

$request = ImageRequest::create(
    level: 6,
    storageTypeId: 'gallery',
    moduleId: 12,
    artId: 345,
    baseName: 'example.jpg',
    args: 'w800-h600-z3-q85',
);

$factory = new AppImageFactory(
    filesystem: new FileSystem(),
    storageConfig: new ImageStorageConfig(
        storageRoot: '/var/www/project',
    ),
    fallbackConfig: new ImageFallbackConfig(
        defaultWidth: 600,
        defaultHeight: 600,
    ),
);

$response = $factory
    ->createApplication($request)
    ->handle();

(new ImageResponseEmitter())->emit(
    response: $response,
);

This split allows advanced integrations to inspect or test the prepared ImageHttpResponse before emitting it.

Parsed options API

ImageOptionsParser converts the compact argument string into an immutable ImageOptionsDTO snapshot:

<?php

use Lemonade\Image\Options\ImageOptionsParser;
use Lemonade\Image\Options\ImageResizeMode;

$options = (new ImageOptionsParser(
    args: 'w800-h600-z3-q85',
))->toDTO();

$options->getWidth();       // 800
$options->getHeight();      // 600
$options->getResizeMode();  // ImageResizeMode::Fit
$options->getQuality();     // 85
$options->getCanvasColor(); // ffffff
$options->isMissing();      // true
$options->getHash();        // deterministic options hash

Runtime image processing should consume the DTO rather than depending on parser internals.

Custom parser configuration

ImageOptionsParser uses a default parser configuration out of the box. Applications can provide their own immutable config objects to override size presets, preset scaling, default canvas color, quality limits and dimension limits.

<?php

use Lemonade\Image\Options\Config\ImageCanvasConfig;
use Lemonade\Image\Options\Config\ImageDimensionConfig;
use Lemonade\Image\Options\Config\ImageOptionsParserConfig;
use Lemonade\Image\Options\Config\ImageQualityConfig;
use Lemonade\Image\Options\Config\ImageSizePreset;
use Lemonade\Image\Options\Config\ImageSizePresetCollection;
use Lemonade\Image\Options\Config\ImageSizePresetConfig;
use Lemonade\Image\Options\ImageOptionsParser;
use Lemonade\Image\Options\ImageResizeMode;

$config = new ImageOptionsParserConfig(
    sizePresets: new ImageSizePresetConfig(
        presets: new ImageSizePresetCollection([
            ImageSizePreset::create(code: 'card', width: 600, height: 400),
            ImageSizePreset::create(code: 'hero', width: 1600, height: 900),
        ]),
        maxScale: 4,
    ),
    canvas: new ImageCanvasConfig(
        defaultColor: 'f5f5f5',
    ),
    quality: new ImageQualityConfig(
        defaultQuality: 85,
        minQuality: 10,
        maxQuality: 95,
    ),
    dimensions: new ImageDimensionConfig(
        minWidth: 50,
        minHeight: 50,
        maxWidth: 3840,
        maxHeight: 2160,
    ),
);

$options = (new ImageOptionsParser(
    args: 'hero2-z3-q100',
    config: $config,
))->toDTO();

$options->getWidth();       // 3200
$options->getHeight();      // 1800
$options->getResizeMode();  // ImageResizeMode::Fit
$options->getQuality();     // 95
$options->getCanvasColor(); // f5f5f5

Cache behavior

The image workflow checks cache in this order:

  1. Browser cache through If-Modified-Since.
  2. Existing filesystem cache variant.
  3. Source image generation.
  4. Missing-image fallback generation.

Generated variants are saved into the cache directory. WebP variants are generated when WebP is supported by the installed GD extension.

A cached variant is reused only when it is at least as fresh as the source image. If the source image is newer than the cache variant, the image is regenerated.

When the source image is missing, only the current generated variant cache is deleted. Other cached files in the same cache directory are preserved.

Response handling

The runtime workflow produces an immutable ImageHttpResponse.

The default facade emits the response immediately:

AppImage::emit(
    request: $request,
);

Custom integrations can handle the response manually:

$response = $factory
    ->createApplication($request)
    ->handle();

$response->isBinary();
$response->isFile();
$response->isNotModified();

Fallback behavior

If the source image does not exist or generation fails, the component generates a fallback image.

If the configured placeholder image exists, it is used as the fallback source. Otherwise, the component creates an internal transparent placeholder image and renders it into the requested fallback canvas.

Fallback image dimensions are configurable through ImageFallbackConfig. These dimensions are used only as a final safety net when the request options do not provide usable width or height values.

<?php

use Lemonade\Image\Fallback\ImageFallbackConfig;

$fallbackConfig = new ImageFallbackConfig(
    defaultWidth: 600,
    defaultHeight: 600,
);

Fallback images are cached separately by option hash.

HTTP responses

The component emits image responses through ImageResponseEmitter.

Cached image files are streamed directly from disk in 8kB chunks with active output buffer flushing, ensuring a near-zero memory footprint even under heavy load. Generated images are rendered to binary output and emitted with cache headers, content type, and content length metadata.

Supported output MIME types:

Type MIME type
JPEG image/jpeg
PNG image/png
GIF image/gif
WebP image/webp

Development

Install dependencies:

composer update

Run PHP syntax lint:

composer lint

Run static analysis:

composer stan

Run tests:

composer test

Run tests in CI mode:

composer test:ci

Check coding standards:

composer cs:check

Fix coding standards:

composer cs:fix

Run smoke test:

composer smoke

Run all checks:

composer check

Composer scripts

Script Description
composer lint Run PHP syntax lint over source files.
composer smoke Run smoke checks for basic component wiring.
composer composer:validate Validate composer.json in strict mode.
composer stan Run PHPStan static analysis.
composer stan:ci Run PHPStan static analysis with GitHub Actions output format.
composer cs:check Check coding standards using PHP-CS-Fixer.
composer cs:fix Fix coding standards using PHP-CS-Fixer.
composer test Run PHPUnit tests.
composer test:ci Run PHPUnit tests without colors.
composer check:platform Check installed platform requirements.
composer check Run Composer validation, platform checks, lint, coding standards, static analysis and tests.

License

MIT

About

Lemonade image component for PHP 8.1+ with GD-based resizing, cropping, cache generation and WebP support.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Contributors

Languages