Breaking: #110702 - Widened FAL processing task interfaces 

See forge#110702

Description 

TYPO3's File Abstraction Layer (FAL) processing pipeline - the TaskInterface / AbstractTask / ProcessorInterface / ProcessorRegistry / FileProcessingService classes that resize, crop and convert images - has so far only ever operated on FAL files ( \TYPO3\CMS\Core\Resource\File and \TYPO3\CMS\Core\Resource\ProcessedFile ). The same pipeline now also processes resources that are not backed by FAL - most commonly a system resource shipped with a package and referenced via an EXT: / PKG: identifier - by routing a ProcessedFile , or the new \TYPO3\CMS\Core\Resource\ProcessedResource , through the same task/ processor machinery (see Feature: #110702 - Process system resource images). Several signatures widened for this:

  • TaskInterface::getSourceFile() now returns \TYPO3\CMS\Core\SystemResource\Type\SystemResourceInterface instead of \TYPO3\CMS\Core\Resource\File .
  • TaskInterface::getTargetFile() now returns the new \TYPO3\CMS\Core\Resource\ProcessedResourceInterface instead of \TYPO3\CMS\Core\Resource\ProcessedFile . ProcessedFile now implements this new interface, alongside the new ProcessedResource for the non-FAL case.
  • AbstractTask::__construct() and TaskTypeRegistry::getTaskForType() now take a ProcessedResourceInterface instead of a ProcessedFile , and the AbstractTask::$sourceFile property is now typed SystemResourceInterface instead of File . A custom task class registered for a task type is constructed with a ProcessedResource when the source is a system resource.
  • ProcessorRegistry no longer offers a task whose source is not a FAL File to a processor unless that processor implements the new, opt-in \TYPO3\CMS\Core\Resource\Processing\ResourceAwareProcessorInterface marker. TYPO3 Core's LocalImageProcessor and SvgImageProcessor now implement it and handle a non-FAL source for the Image.CropScaleMask task; no other Core processor does. As with any interface, a subclass inherits it: a custom processor extending LocalImageProcessor or SvgImageProcessor is offered non-FAL tasks as well.
  • FileProcessingService has a new, last constructor argument \TYPO3\CMS\Core\Resource\ProcessedResourceCache .
  • FileProcessingService::processFile() now accepts File|FileReference|SystemResourceInterface (was File|FileReference ), returns ProcessedFile|ProcessedResource (was ProcessedFile ), and its $driver parameter is now nullable - a non-FAL resource is never fetched through a ResourceStorage driver in the first place, so there is none to pass.
  • BeforeFileProcessingEvent / AfterFileProcessingEvent widen the same way: getDriver() now returns ?DriverInterface , getFile() returns FileInterface|SystemResourceInterface , and getProcessedFile() / setProcessedFile() use ProcessedResourceInterface .
  • \TYPO3\CMS\Core\Imaging\ImageResource::getOriginalFile() now returns File|SystemResourceInterface|null (was ?File ), and ImageResource::getProcessedFile() now returns ?ProcessedResourceInterface (was ?ProcessedFile ). The corresponding with*() methods and the constructor accept the same wider types. An image resource of a processed system resource carries the system resource and its ProcessedResource .

The URLs of EXT: / PKG: images rendered through image processing change as well:

  • A processed variant is no longer stored in typo3temp/assets/_processed_/ as csm_<name>_<hash>.<extension>, but in typo3temp/assets/images/system/<hash>/<hash>-<hash>.<extension>.
  • An image that does not need processing is linked by its own public URL, as generated by the system resource publisher - including its cache busting query string, for example logo.png?1791262813.
  • The link generated by imageLinkWrap for such an image passes the system resource identifier as file parameter to the tx_cms_showpic eID, instead of the uid of a sys_file record.

Impact 

Custom ProcessorInterface / TaskInterface / AbstractTask implementations, or PSR-14 listeners of the two events above, that use getSourceFile() / getTargetFile() / getFile() / getProcessedFile() / getDriver() as a concrete File / ProcessedFile / DriverInterface without a type check - for example calling getUid() , getForLocalProcessing() , getStorage() or any DriverInterface method directly - will fail static analysis against the wider declared type, and can now genuinely receive a system resource, or a null driver, at runtime: a template or content element that references an EXT: / PKG: image is processed through this same pipeline starting with this release.

Custom task classes that type-hint the constructor argument as ProcessedFile , or assign a File to $sourceFile , fail with a PHP fatal error. Code that instantiates FileProcessingService manually, or extends it with its own constructor, fails with an ArgumentCountError . Code relying on ImageResource::getOriginalFile() / getProcessedFile() returning a FAL object fails for an EXT: / PKG: image.

Tests, caches, CDN or proxy rules and other code that relied on the previous URL pattern of processed EXT: / PKG: images no longer match.

Affected installations 

Installations with extensions that implement ProcessorInterface directly, subclass LocalImageProcessor / SvgImageProcessor , AbstractTask or FileProcessingService , listen to BeforeFileProcessingEvent / AfterFileProcessingEvent , or use ImageResource::getOriginalFile() / getProcessedFile() , and rely on the narrower pre-existing types without narrowing themselves.

Installations that depend on the URL of a processed EXT: / PKG: image.

Migration 

Narrow the type before using File / ProcessedFile / DriverInterface -specific members, the same way TYPO3 Core's own processors do now:

EXT:my_extension/Classes/Resource/Processing/MyProcessor.php
use TYPO3\CMS\Core\Resource\File;

public function processTask(TaskInterface $task): void
{
    $sourceFile = $task->getSourceFile();
    if (!$sourceFile instanceof File) {
        throw new \InvalidArgumentException('MyProcessor can only process a FAL File.', 1234567890);
    }
    // $sourceFile is now a concrete File again.
}
Copied!
EXT:my_extension/Classes/EventListener/MyFileProcessingListener.php
public function __invoke(AfterFileProcessingEvent $event): void
{
    $driver = $event->getDriver();
    if ($driver === null) {
        // A non-FAL resource - nothing to do here, or handle it explicitly.
        return;
    }
    // $driver is now a concrete DriverInterface again.
}
Copied!

Custom task classes declare the constructor argument as ProcessedResourceInterface , the same as AbstractTask does.

A subclass of LocalImageProcessor or SvgImageProcessor that should only handle FAL files declines other tasks in canProcessTask() :

EXT:my_extension/Classes/Resource/Processing/MyImageProcessor.php
use TYPO3\CMS\Core\Resource\File;

public function canProcessTask(TaskInterface $task): bool
{
    return $task->getSourceFile() instanceof File
        && parent::canProcessTask($task);
}
Copied!

Code that instantiates FileProcessingService itself retrieves it through dependency injection instead.

Check ImageResource::getOriginalFile() / getProcessedFile() for File / ProcessedFile before using FAL-specific methods.

Do not rely on the file name or location of a processed image.