---
title: "Developer reference"
manual: "Image Optimization for TYPO3"
version: "2.6"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer@2.6"
source: "Developer/Index.rst"
rendered: "2026-09-25T11:13:49+00:00"
---

# Developer reference {#developer}

## Architecture {#developer-architecture}

The extension has three entry points that share the same
optimization backend:

1.  **ProcessingMiddleware** intercepts frontend requests
    matching the `/processed/` path and asks the
    [Processor](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer-processor@2.6) to create a variant
    on demand.
1.  **OptimizeOnUploadListener** reacts to PSR-14 file events
    (`AfterFileAddedEvent` and `AfterFileReplacedEvent`)
    and delegates to the shared
    [ImageOptimizer](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer-image-optimizer@2.6) service
    for in-place lossless compression.
1.  **OptimizeImagesCommand** and **AnalyzeImagesCommand**
    iterate the FAL index from the CLI. Both extend
    [AbstractImageCommand](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer-commands@2.6), which
    provides shared database iteration, progress rendering,
    and option parsing.

The **MaintenanceController** provides the backend module for
statistics, cleanup, and system-requirement checks.

## Directory structure {#developer-directory-structure}

-   Classes/
    -   Command/
        -   AbstractImageCommand.php
        -   AnalyzeImagesCommand.php
        -   OptimizeImagesCommand.php
    -   Controller/
        -   MaintenanceController.php
    -   Event/
        -   ImageProcessedEvent.php
        -   VariantServedEvent.php
    -   EventListener/
        -   OptimizeOnUploadListener.php
    -   Middleware/
        -   ProcessingMiddleware.php
    -   Service/
        -   ImageManagerAdapter.php
        -   ImageManagerFactory.php
        -   ImageOptimizer.php
        -   ImageReaderInterface.php
        -   SystemRequirementsService.php
    -   Processor.php
    -   ProcessorInterface.php
    -   ViewHelpers/
        -   SourceSetViewHelper.php
-   Configuration/
    -   Backend/
        -   Modules.php
    -   Icons.php
    -   RequestMiddlewares.php
    -   Services.yaml

## Processing middleware {#developer-middleware}

-   **class ProcessingMiddleware**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Middleware\ProcessingMiddleware`

    Frontend PSR-15 middleware registered **before**
    `typo3/cms-frontend/site` (see
    [Middleware registration](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:configuration-middleware@2.6)). Intercepts every
    request whose path starts with `/processed/` and
    delegates to the
    [\\Netresearch\\NrImageOptimize\\ProcessorInterface](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-processorinterface@2.6)
    implementation. Any other request is passed to the next
    handler unchanged.

## Processor {#developer-processor}

-   **class ProcessorInterface**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\ProcessorInterface`

    Contract for the on-demand processor -- declares
    `generateAndSend(ServerRequestInterface): ResponseInterface`.
    The middleware depends on this interface, so integrators
    can replace the implementation via a Services.yaml alias.

-   **class Processor**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Processor`

    Default implementation. Parses the requested URL (see
    [Variant URL format](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:configuration-url-format@2.6)) to extract path,
    dimensions, quality, and mode. Loads the original via
    the `ImageReaderInterface` adapter (see
    [ImageManager abstraction](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer-image-manager@2.6)), generates the primary
    variant plus optional
    WebP / AVIF sidecars, and streams a PSR-7 response back
    with long-lived cache headers.

    Key behaviors:

    -   Rejects path-traversal (`..`) sequences at URL
        parse time, returning HTTP 400.
    -   Clamps `w` / `h` to 1--8192 and `q` to 1--100.
    -   Uses TYPO3's `LockFactory` to serialize concurrent
        requests for the same variant; a 503 is returned if
        the lock can't be acquired within  1 second.
    -   Serves the first non-empty file on disk: the AVIF
        sidecar, then the WebP sidecar, then the original
        format. The `Accept` header is not inspected; the
        `skipWebP` and `skipAvif` query parameters only
        suppress sidecar generation.
    -   Dispatches [\\Netresearch\\NrImageOptimize\\Event\\ImageProcessedEvent](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-event-imageprocessedevent@2.6)
        after a new variant is written and
        [\\Netresearch\\NrImageOptimize\\Event\\VariantServedEvent](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-event-variantservedevent@2.6)
        before the response body is sent.

## PSR-14 events {#developer-events}

Integrators can subscribe to the processor's lifecycle to
collect metrics, trigger cache warming, or apply additional
post-processing.

-   **class ImageProcessedEvent**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Event\ImageProcessedEvent`

    Dispatched after the processor has written a new variant
    to disk. Properties (all `public readonly`):

    -   **`pathOriginal` (`string`)**

        Absolute path of the source image.

    -   **`pathVariant` (`string`)**

        Absolute path of the variant -- the actual served
        file may be a `.webp` or `.avif` sidecar of this
        path.

    -   **`extension` (`string`)**

        Lowercased extension of the variant (`jpg`,
        `png`, `webp`, `avif` ...).

    -   **`targetWidth` / `targetHeight` (`?int`)**

        Final clamped dimensions in pixels, or `null` if
        the URL omitted the value.

    -   **`targetQuality` (`int`)**

        Output quality (1--100).

    -   **`processingMode` (`int`)**

        `0` = cover, `1` = scale / fit.

    -   **`webpGenerated` / `avifGenerated` (`bool`)**

        Whether the respective sidecar was written alongside.

-   **class VariantServedEvent**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Event\VariantServedEvent`

    Dispatched immediately before the response body is
    streamed to the client -- both for fresh variants and for
    cache hits. Properties:

    -   **`pathVariant` (`string`)**

        Absolute base path of the variant being served.

    -   **`extension` (`string`)**

        Lowercased source extension.

    -   **`responseStatusCode` (`int`)**

        HTTP status code (always 200 in practice).

    -   **`fromCache` (`bool`)**

        `true` when the response reuses a previously
        generated file on disk; `false` when the variant
        was produced in this request.

Register listeners via `Configuration/Services.yaml` or the
per-class `#[AsEventListener]` attribute:

**Configuration/Services.yaml**

```yaml
services:
  Acme\Example\Listener\MetricsListener:
    tags:
      - name: event.listener
        identifier: 'acme.metrics.variant_served'
        event: Netresearch\NrImageOptimize\Event\VariantServedEvent
```

## Upload event listener {#developer-event-listener}

-   **class OptimizeOnUploadListener**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\EventListener\OptimizeOnUploadListener`

    PSR-14 listener registered for `AfterFileAddedEvent`
    and `AfterFileReplacedEvent`. Normalizes the file
    extension, short-circuits unsupported extensions and
    offline storages, and delegates to
    [\\Netresearch\\NrImageOptimize\\Service\\ImageOptimizer](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-service-imageoptimizer@2.6).

    Re-entrancy guard: keyed by
    `storageUid . ':' . identifier` so two storages sharing
    an identifier (`/image.jpg` in different driver roots)
    don't block each other, while still catching the
    `replaceFile()` loop that the optimizer's own write
    would otherwise trigger.

    Storage state: `setEvaluatePermissions(false)` is
    applied before delegation and restored to its previous
    value in a `finally` block so long-running CLI runs
    don't leak permission state across iterations.

## ImageOptimizer service {#developer-image-optimizer}

-   **class ImageOptimizer**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Service\ImageOptimizer`

    Shared backend used by both the event listener and the
    CLI commands. Resolves optimizer binaries via `$PATH`,
    with env overrides (`OPTIPNG_BIN`, `GIFSICLE_BIN`,
    `JPEGOPTIM_BIN`). A set-but-invalid override is
    authoritative -- the tool is reported unavailable rather
    than silently falling back to `$PATH`. `$PATH`
    lookups also verify `is_executable()`.

    Public API:

    -   **`optimize(FileInterface $file, bool $stripMetadata = false, ?int $jpegQuality = null, bool $dryRun = false): array`**

        Run the appropriate tool in place and replace the
        FAL file when the result shrinks.

    -   **`analyze(FileInterface $file, bool $stripMetadata = false, ?int $jpegQuality = null): array`**

        Same pipeline, but never writes back.

    -   **`analyzeHeuristic(FileInterface $file, int $maxWidth = 2560, int $maxHeight = 1440, int $minSize = 512000): array`**

        Fast estimation from size and resolution. No binary
        is invoked.

    -   **`resolveToolFor(string $extension): ?array`**

        Tool discovery for callers that want to pre-check.

    -   **`hasAnyTool(): bool`**

        Quick check whether at least one optimizer is
        installed.

    -   **`supportedExtensions(): list<string>`**

        Returns `['png', 'gif', 'jpg', 'jpeg']`.

## ImageManager abstraction {#developer-image-manager}

-   **class ImageManagerFactory**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Service\ImageManagerFactory`

    Selects the best available Intervention Image driver
    (Imagick over GD). See
    [Image driver selection](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:configuration-image-driver@2.6) for behavior and
    failure modes.

-   **class ImageReaderInterface**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Service\ImageReaderInterface`

    Version-agnostic abstraction around Intervention Image's
    loading API. v3 uses `ImageManager::read()`, v4 uses
    `ImageManager::decode()`; this interface hides that
    difference so consumers and static analysis see a single
    stable contract.

-   **class ImageManagerAdapter**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Service\ImageManagerAdapter`

    Default implementation. Dispatches to whichever method is
    present on the installed `ImageManager`, letting the
    extension support Intervention Image `^3 || ^4`
    simultaneously without version-conditional code paths.

## SystemRequirementsService {#developer-system-requirements}

-   **class SystemRequirementsService**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Service\SystemRequirementsService`

    Produces the data shown on the
    [System requirements](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:maintenance-system-requirements@2.6)
    tab of the backend module. Inspects:

    -   PHP version and loaded extensions (`imagick`,
        `gd`, `fileinfo`).
    -   ImageMagick / GraphicsMagick capabilities as
        reported by Imagick (WebP, AVIF support).
    -   Intervention Image installed version and the active
        driver (Imagick or GD).
    -   TYPO3 version.
    -   Availability of optional CLI tools (`magick`,
        `convert`, `identify`, `gm`).

    Returns a structured array the controller renders into a
    Fluid template.

## CLI commands {#developer-commands}

-   **class AbstractImageCommand**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Command\AbstractImageCommand`

    Base class for the image CLI commands. Exposes
    `parseStorageUidsOption`, `getIntOption`,
    `extractUid`, `countImages`, `iterateViaIndex`
    (Generator over `sys_file` rows -- constant memory),
    `createProgress`, `buildLabel`, `shortenLabel`,
    and `formatMbGb`.

-   **class OptimizeImagesCommand**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Command\OptimizeImagesCommand`

    Implements `nr:image:optimize`. Delegates to
    [\\Netresearch\\NrImageOptimize\\Service\\ImageOptimizer](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-service-imageoptimizer@2.6)
    per file and renders a Symfony progress bar with
    cumulative savings. See [Bulk optimize images](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:usage-cli-optimize@2.6).

-   **class AnalyzeImagesCommand**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Command\AnalyzeImagesCommand`

    Implements `nr:image:analyze`. Uses
    `ImageOptimizer::analyzeHeuristic()` -- no external
    tool is invoked. See [Analyze optimization potential](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:usage-cli-analyze@2.6).

## SourceSetViewHelper {#developer-viewhelper}

-   **class SourceSetViewHelper**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\ViewHelpers\SourceSetViewHelper`

    Fluid ViewHelper that generates `<img>` tags with
    `srcset` attributes for responsive image delivery.
    Supports both density-based (2x) and width-based
    responsive srcset modes. See [Usage](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:usage@2.6) for examples
    and [SourceSetViewHelper](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:configuration-viewhelper@2.6) for the parameter
    reference.

## MaintenanceController {#developer-controller}

-   **class MaintenanceController**

    -   *Fully qualified name:* `\Netresearch\NrImageOptimize\Controller\MaintenanceController`

    Extbase controller powering the backend module. Three
    actions:

    -   **`indexAction`**

        Overview of the processed-images directory: file
        count, total size, largest files, type distribution.

    -   **`systemRequirementsAction`**

        Renders the data produced by
        [\\Netresearch\\NrImageOptimize\\Service\\SystemRequirementsService](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-service-systemrequirementsservice@2.6).

    -   **`clearProcessedImagesAction`**

        Recursively deletes every file under the configured
        processed-images directory. A flash message reports
        the freed space.

    Module registration (access limited to `systemMaintainer`)
    lives in `Configuration/Backend/Modules.php`.

## Testing {#developer-testing}

**Run the full test suite**

```bash
composer ci:test
```

Individual test commands:

**Available test commands**

```bash
composer ci:test:php:cgl      # Code style
composer ci:test:php:lint     # PHP syntax
composer ci:test:php:phpstan  # Static analysis
composer ci:test:php:unit     # PHPUnit tests
composer ci:test:php:rector   # Code quality
```

**Docker-based matrix runner (also used by CI)**

```bash
Build/Scripts/runTests.sh -s unit -p 8.4
Build/Scripts/runTests.sh -s phpstan -p 8.4
```
