diff --git a/composer.json b/composer.json index 0b4ce0f6..608f529a 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "silverstripe/framework": "^6", "silverstripe/vendor-plugin": "^2", "symfony/filesystem": "^6.1", - "intervention/image": "^2.7.2", + "intervention/image": "^3.7", "league/flysystem": "^3.9.0" }, "require-dev": { diff --git a/src/Conversion/InterventionImageFileConverter.php b/src/Conversion/InterventionImageFileConverter.php index 7f75a0a1..1169f27a 100644 --- a/src/Conversion/InterventionImageFileConverter.php +++ b/src/Conversion/InterventionImageFileConverter.php @@ -2,7 +2,6 @@ namespace SilverStripe\Assets\Conversion; -use Imagick; use Intervention\Image\Exception\ImageException; use SilverStripe\Assets\File; use SilverStripe\Assets\Image_Backend; @@ -31,7 +30,9 @@ public function supportsConversion(string $fromExtension, string $toExtension, a if (!is_a($backend, InterventionBackend::class)) { return false; } - return $this->supportedByIntervention($fromExtension, $backend) && $this->supportedByIntervention($toExtension, $backend); + /** @var InterventionBackend $backend */ + $driver = $backend->getImageManager()->driver(); + return $driver->supports($fromExtension) && $driver->supports($toExtension); } public function convert(DBFile|File $from, string $toExtension, array $options = []): DBFile @@ -90,102 +91,4 @@ private function validateOptions(array $options): array } return $problems; } - - private function supportedByIntervention(string $format, InterventionBackend $backend): bool - { - $driver = $backend->getImageManager()->config['driver'] ?? null; - - // Return early for empty values - we obviously can't support that - if ($format === '') { - return false; - } - - $format = strtolower($format); - - // If the driver is somehow not GD or Imagick, we have no way to know what it might support - if ($driver !== 'gd' && $driver !== 'imagick') { - $supported = false; - $this->extend('updateSupportedByIntervention', $supported, $format, $driver); - return $supported; - } - - // GD and Imagick support different things. - // This follows the logic in intervention's AbstractEncoder::process() method - // and the various methods in the Encoder classes for GD and Imagick, - // excluding checking for strings that were obviously mimetypes - switch ($format) { - case 'gif': - // always supported - return true; - case 'png': - // always supported - return true; - case 'jpg': - case 'jpeg': - case 'jfif': - // always supported - return true; - case 'tif': - case 'tiff': - if ($driver === 'gd') { - false; - } - // always supported by imagick - return true; - case 'bmp': - case 'ms-bmp': - case 'x-bitmap': - case 'x-bmp': - case 'x-ms-bmp': - case 'x-win-bitmap': - case 'x-windows-bmp': - case 'x-xbitmap': - if ($driver === 'gd' && !function_exists('imagebmp')) { - return false; - } - // always supported by imagick - return true; - case 'ico': - if ($driver === 'gd') { - return false; - } - // always supported by imagick - return true; - case 'psd': - if ($driver === 'gd') { - return false; - } - // always supported by imagick - return true; - case 'webp': - if ($driver === 'gd' && !function_exists('imagewebp')) { - return false; - } - if ($driver === 'imagick' && !Imagick::queryFormats('WEBP')) { - return false; - } - return true; - case 'avif': - if ($driver === 'gd' && !function_exists('imageavif')) { - return false; - } - if ($driver === 'imagick' && !Imagick::queryFormats('AVIF')) { - return false; - } - return true; - case 'heic': - if ($driver === 'gd') { - return false; - } - if ($driver === 'imagick' && !Imagick::queryFormats('HEIC')) { - return false; - } - return true; - default: - // Anything else is not supported - return false; - } - // This should never be reached, but return false if it is - return false; - } } diff --git a/src/Image_Backend.php b/src/Image_Backend.php index 061bb6a4..63dca6f0 100644 --- a/src/Image_Backend.php +++ b/src/Image_Backend.php @@ -36,42 +36,36 @@ interface Image_Backend public function __construct(AssetContainer $assetContainer = null); /** - * @return int The width of the image + * Get the width of the image */ - public function getWidth(); + public function getWidth(): int; /** - * @return int The height of the image + * Get the height of the image */ - public function getHeight(); + public function getHeight(): int; /** * Populate the backend with a given object * * @param AssetContainer $assetContainer Object to load from */ - public function loadFromContainer(AssetContainer $assetContainer); + public function loadFromContainer(AssetContainer $assetContainer): static; /** * Populate the backend from a local path - * - * @param string $path */ - public function loadFrom($path); + public function loadFrom(string $path): static; /** * Get the currently assigned image resource - * - * @return mixed */ - public function getImageResource(); + public function getImageResource(): mixed; /** * Set the currently assigned image resource - * - * @param mixed $resource */ - public function setImageResource($resource); + public function setImageResource($resource): static; /** * Write to the given asset store @@ -84,7 +78,7 @@ public function setImageResource($resource); * @return array Tuple associative array (Filename, Hash, Variant) Unless storing a variant, the hash * will be calculated from the given data. */ - public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = []); + public function writeToStore(AssetStore $assetStore, string $filename, ?string $hash = null, ?string $variant = null, array $config = []): array; /** * Write the backend to a local path @@ -92,79 +86,53 @@ public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $v * @param string $path * @return bool if the write was successful */ - public function writeTo($path); + public function writeTo(string $path): bool; /** * Set the quality to a value between 0 and 100 - * - * @param int $quality */ - public function setQuality($quality); + public function setQuality(int $quality): static; /** * Resize an image, skewing it as necessary. - * - * @param int $width - * @param int $height - * @return static */ - public function resize($width, $height); + public function resize(int $width, int $height): static; /** - * Resize the image by preserving aspect ratio. By default, it will keep the image inside the maxWidth - * and maxHeight. Passing useAsMinimum will make the smaller dimension equal to the maximum corresponding dimension - * - * @param int $width - * @param int $height - * @param bool $useAsMinimum If true, image will be sized outside of these dimensions. - * If false (default) image will be sized inside these dimensions. - * @return static + * Resize the image by preserving aspect ratio. By default, the image cannot be resized to be larger + * than its current size. + * Passing true to useAsMinimum will allow the image to be scaled up. */ - public function resizeRatio($width, $height, $useAsMinimum = false); + public function resizeRatio(int $width, int $height, bool $useAsMinimum = false): static; /** * Resize an image by width. Preserves aspect ratio. - * - * @param int $width - * @return static */ - public function resizeByWidth($width); + public function resizeByWidth(int $width): static; /** * Resize an image by height. Preserves aspect ratio. - * - * @param int $height - * @return static */ - public function resizeByHeight($height); + public function resizeByHeight(int $height): static; /** - * Return a clone of this image resized, with space filled in with the given colour - * - * @param int $width - * @param int $height - * @param string $backgroundColor - * @param int $transparencyPercent - * @return static + * Return a clone of this image resized, with space filled in with the given colour. */ - public function paddedResize($width, $height, $backgroundColor = "FFFFFF", $transparencyPercent = 0); + public function paddedResize(string $width, string $height, string $backgroundColour = 'FFFFFF', int $transparencyPercent = 0): static; /** * Resize an image to cover the given width/height completely, and crop off any overhanging edges. - * - * @param int $width - * @param int $height - * @return static */ - public function croppedResize($width, $height); + public function croppedResize(int $width, int $height, string $position): static; /** * Crop's part of image. - * @param int $top y position of left upper corner of crop rectangle - * @param int $left x position of left upper corner of crop rectangle + * @param int $top Amount of pixels the cutout will be moved on the y (vertical) axis + * @param int $left Amount of pixels the cutout will be moved on the x (horizontal) axis * @param int $width rectangle width * @param int $height rectangle height - * @return Image_Backend + * @param string $position Postion at which the cutout will be aligned + * @param string $backgroundColour Colour to fill any newly created areas */ - public function crop($top, $left, $width, $height); + public function crop(int $top, int $left, int $width, int $height, string $position, string $backgroundColour = 'FFFFFF'): static; } diff --git a/src/InterventionBackend.php b/src/InterventionBackend.php index a6644560..702729a7 100644 --- a/src/InterventionBackend.php +++ b/src/InterventionBackend.php @@ -3,13 +3,12 @@ namespace SilverStripe\Assets; use BadMethodCallException; -use Intervention\Image\Constraint; -use Intervention\Image\Exception\NotReadableException; -use Intervention\Image\Exception\NotSupportedException; -use Intervention\Image\Exception\NotWritableException; -use Intervention\Image\Image as InterventionImage; +use Intervention\Image\Colors\Rgb\Channels\Alpha; +use Intervention\Image\Colors\Rgb\Color; +use Intervention\Image\Exceptions\DecoderException; +use Intervention\Image\Exceptions\EncoderException; +use Intervention\Image\Interfaces\ImageInterface as InterventionImage; use Intervention\Image\ImageManager; -use Intervention\Image\Size; use InvalidArgumentException; use LogicException; use Psr\Http\Message\StreamInterface; @@ -37,21 +36,17 @@ class InterventionBackend implements Image_Backend, Flushable /** * Is cache flushing enabled? - * - * @config - * @var boolean */ - private static $flush_enabled = true; + private static bool $flush_enabled = true; /** * How long to cache each error type * - * @config - * @var array Map of error type to config. + * Map of error type to config. * each config could be a single int (fixed cache time) * or list of integers (increasing scale) */ - private static $error_cache_ttl = [ + private static array $error_cache_ttl = [ InterventionBackend::FAILED_INVALID => 0, // Invalid file type should probably never be retried InterventionBackend::FAILED_MISSING => '5,10,20,40,80', // Missing files may be eventually available InterventionBackend::FAILED_UNKNOWN => 300, // Unknown (edge case). Maybe system error? Needs a flush? @@ -76,40 +71,20 @@ class InterventionBackend implements Image_Backend, Flushable /** * Configure where cached intervention files will be stored * - * @config - * @var string */ - private static $local_temp_path = TEMP_PATH; + private static string $local_temp_path = TEMP_PATH; - /** - * @var AssetContainer - */ - private $container; + private AssetContainer $container; - /** - * @var InterventionImage - */ - private $image; + private InterventionImage $image; - /** - * @var int - */ - private $quality; + private int $quality; - /** - * @var ImageManager - */ - private $manager; + private ImageManager $manager; - /** - * @var CacheInterface - */ - private $cache; + private CacheInterface $cache; - /** - * @var string - */ - private $tempPath; + private string $tempPath; public function __construct(AssetContainer $assetContainer = null) { @@ -117,28 +92,23 @@ public function __construct(AssetContainer $assetContainer = null) } /** - * @return string The temporary local path for this image + * Get the temporary local path for this image */ - public function getTempPath() + public function getTempPath(): string { return $this->tempPath; } /** - * @param string $path - * - * @return $this + * Set the temporary local path for this image */ - public function setTempPath($path) + public function setTempPath(string $path): static { $this->tempPath = $path; return $this; } - /** - * @return CacheInterface - */ - public function getCache() + public function getCache(): CacheInterface { if (!$this->cache) { $this->setCache(Injector::inst()->get(CacheInterface::class . '.InterventionBackend_Manipulations')); @@ -146,41 +116,25 @@ public function getCache() return $this->cache; } - /** - * @param CacheInterface $cache - * - * @return $this - */ - public function setCache($cache) + public function setCache(CacheInterface $cache): static { $this->cache = $cache; return $this; } - /** - * @return AssetContainer - */ - public function getAssetContainer() + public function getAssetContainer(): AssetContainer { return $this->container; } - /** - * @param AssetContainer $assetContainer - * - * @return $this - */ - public function setAssetContainer($assetContainer) + public function setAssetContainer(AssetContainer $assetContainer): static { $this->setImageResource(null); $this->container = $assetContainer; return $this; } - /** - * @return ImageManager - */ - public function getImageManager() + public function getImageManager(): ImageManager { if (!$this->manager) { $this->setImageManager(Injector::inst()->create(ImageManager::class)); @@ -188,12 +142,7 @@ public function getImageManager() return $this->manager; } - /** - * @param ImageManager $manager - * - * @return $this - */ - public function setImageManager($manager) + public function setImageManager(ImageManager $manager): static { $this->manager = $manager; return $this; @@ -203,9 +152,8 @@ public function setImageManager($manager) * Populate the backend with a given object * * @param AssetContainer $assetContainer Object to load from - * @return $this */ - public function loadFromContainer(AssetContainer $assetContainer) + public function loadFromContainer(AssetContainer $assetContainer): static { return $this->setAssetContainer($assetContainer); } @@ -213,10 +161,8 @@ public function loadFromContainer(AssetContainer $assetContainer) /** * Get the currently assigned image resource, or generates one if not yet assigned. * Note: This method may return null if error - * - * @return InterventionImage */ - public function getImageResource() + public function getImageResource(): ?InterventionImage { // Get existing resource if ($this->image) { @@ -267,17 +213,10 @@ public function getImageResource() $bytesWritten = file_put_contents($path ?? '', $stream); // if we fail to write, then load from stream if ($bytesWritten === false) { - $resource = $this->getImageManager()->make($stream); + $resource = $this->getImageManager()->read($stream); } else { $this->setTempPath($path); - $resource = $this->getImageManager()->make($path); - } - - // Fix image orientation - try { - $resource->orientate(); - } catch (NotSupportedException $e) { - // noop - we can't orientate, don't worry about it + $resource = $this->getImageManager()->read($path); } $this->setImageResource($resource); @@ -285,7 +224,7 @@ public function getImageResource() $this->warmCache($hash, $variant); $error = null; return $resource; - } catch (NotReadableException $ex) { + } catch (DecoderException $ex) { // Handle unsupported image encoding on load (will be marked as failed) // Unsupported exceptions are handled without being raised as exceptions $error = InterventionBackend::FAILED_INVALID; @@ -299,11 +238,8 @@ public function getImageResource() /** * Populate the backend from a local path - * - * @param string $path - * @return $this */ - public function loadFrom($path) + public function loadFrom(string $path): static { // Avoid repeat load of broken images $hash = sha1($path ?? ''); @@ -314,10 +250,10 @@ public function loadFrom($path) // Handle resource $error = InterventionBackend::FAILED_UNKNOWN; try { - $this->setImageResource($this->getImageManager()->make($path)); + $this->setImageResource($this->getImageManager()->read($path)); $this->markSuccess($hash, null); $error = null; - } catch (NotReadableException $ex) { + } catch (DecoderException $ex) { // Handle unsupported image encoding on load (will be marked as failed) // Unsupported exceptions are handled without being raised as exceptions $error = InterventionBackend::FAILED_INVALID; @@ -331,11 +267,15 @@ public function loadFrom($path) } /** + * @inheritDoc + * * @param InterventionImage $image - * @return $this */ - public function setImageResource($image) + public function setImageResource($image): static { + if (!is_a($image, InterventionImage::class)) { + throw new InvalidArgumentException('$image must be an instance of ' . InterventionImage::class); + } $this->image = $image; if ($image === null) { // remove our temp file if it exists @@ -347,18 +287,11 @@ public function setImageResource($image) } /** - * Write to the given asset store + * @inheritDoc * - * @param AssetStore $assetStore - * @param string $filename Name for the resulting file - * @param string $hash Hash of original file, if storing a variant. - * @param string $variant Name of variant, if storing a variant. - * @param array $config Write options. {@see AssetStore} - * @return array Tuple associative array (Filename, Hash, Variant) Unless storing a variant, the hash - * will be calculated from the given data. * @throws BadMethodCallException If image isn't valid */ - public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = []) + public function writeToStore(AssetStore $assetStore, string $filename, ?string $hash = null, ?string $variant = null, array $config = []): array { try { $resource = $this->getImageResource(); @@ -371,7 +304,7 @@ public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $v $extension = pathinfo($url, PATHINFO_EXTENSION); // Save file $result = $assetStore->setFromString( - $resource->encode($extension, $this->getQuality())->getEncoded(), + $resource->encodeByExtension($extension, $this->getQuality())->toString(), $filename, $hash, $variant, @@ -384,19 +317,17 @@ public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $v } return $result; - } catch (NotSupportedException $e) { + } catch (EncoderException $e) { return null; } } /** - * Write the backend to a local path + * @inheritDoc * - * @param string $path - * @return bool If the writing was successful * @throws BadMethodCallException If image isn't valid */ - public function writeTo($path) + public function writeTo(string $path): bool { try { $resource = $this->getImageResource(); @@ -404,7 +335,7 @@ public function writeTo($path) throw new BadMethodCallException("Cannot write corrupt file to store"); } $resource->save($path, $this->getQuality()); - } catch (NotWritableException $e) { + } catch (EncoderException $e) { return false; } return true; @@ -465,51 +396,35 @@ protected function getDimensions() /** * Get dimensions from the given resource - * - * @param InterventionImage $resource - * @return array */ - protected function getResourceDimensions(InterventionImage $resource) + protected function getResourceDimensions(InterventionImage $resource): array { - /** @var Size $size */ - $size = $resource->getSize(); return [ - $size->getWidth(), - $size->getHeight() + $resource->width(), + $resource->height(), ]; } /** * Cache key for recording errors - * - * @param string $hash - * @param string|null $variant - * @return string */ - protected function getErrorCacheKey($hash, $variant = null) + protected function getErrorCacheKey(string $hash, ?string $variant = null): string { return InterventionBackend::CACHE_MARK . sha1($hash . '-' . $variant); } /** * Cache key for dimensions for given container - * - * @param string $hash - * @param string|null $variant - * @return string */ - protected function getDimensionCacheKey($hash, $variant = null) + protected function getDimensionCacheKey(string $hash, ?string $variant = null): string { return InterventionBackend::CACHE_DIMENSIONS . sha1($hash . '-' . $variant); } /** * Warm dimension cache for the given asset - * - * @param string $hash - * @param string|null $variant */ - protected function warmCache($hash, $variant = null) + protected function warmCache(string $hash, ?string $variant = null): void { // Warm dimension cache $key = $this->getDimensionCacheKey($hash, $variant); @@ -521,43 +436,36 @@ protected function warmCache($hash, $variant = null) } /** - * @return int The width of the image + * @inheritDoc */ - public function getWidth() + public function getWidth(): int { list($width) = $this->getDimensions(); return (int)$width; } /** - * @return int The height of the image + * @inheritDoc */ - public function getHeight() + public function getHeight(): int { list(, $height) = $this->getDimensions(); return (int)$height; } /** - * Set the quality to a value between 0 and 100 - * - * @param int $quality - * @return $this + * @inheritDoc */ - public function setQuality($quality) + public function setQuality(int $quality): static { $this->quality = $quality; return $this; } /** - * Resize an image, skewing it as necessary. - * - * @param int $width - * @param int $height - * @return static + * @inheritDoc */ - public function resize($width, $height) + public function resize(int $width, int $height): static { return $this->createCloneWithResource( function (InterventionImage $resource) use ($width, $height) { @@ -567,135 +475,95 @@ function (InterventionImage $resource) use ($width, $height) { } /** - * Resize the image by preserving aspect ratio. By default, it will keep the image inside the maxWidth - * and maxHeight. Passing useAsMinimum will make the smaller dimension equal to the maximum corresponding dimension - * - * @param int $width - * @param int $height - * @param bool $useAsMinimum If true, image will be sized outside of these dimensions. - * If false (default) image will be sized inside these dimensions. - * @return static + * @inheritDoc */ - public function resizeRatio($width, $height, $useAsMinimum = false) + public function resizeRatio(int $width, int $height, bool $useAsMinimum = false): static { return $this->createCloneWithResource( function (InterventionImage $resource) use ($width, $height, $useAsMinimum) { - return $resource->resize( - $width, - $height, - function (Constraint $constraint) use ($useAsMinimum) { - $constraint->aspectRatio(); - if (!$useAsMinimum) { - $constraint->upsize(); - } - } - ); + if ($useAsMinimum) { + return $resource->scale($width, $height); + } + return $resource->scaleDown($width, $height); } ); } /** - * Resize an image by width. Preserves aspect ratio. - * - * @param int $width - * @return static + * @inheritDoc */ - public function resizeByWidth($width) + public function resizeByWidth(int $width): static { return $this->createCloneWithResource( function (InterventionImage $resource) use ($width) { - return $resource->widen($width); + return $resource->scale($width); } ); } /** - * Resize an image by height. Preserves aspect ratio. - * - * @param int $height - * @return static + * @inheritDoc */ - public function resizeByHeight($height) + public function resizeByHeight(int $height): static { return $this->createCloneWithResource( function (InterventionImage $resource) use ($height) { - return $resource->heighten($height); + return $resource->scale(height: $height); } ); } /** - * Return a clone of this image resized, with space filled in with the given colour - * - * @param int $width - * @param int $height - * @param string $backgroundColor - * @param int $transparencyPercent - * @return static + * @inheritDoc */ - public function paddedResize($width, $height, $backgroundColor = "FFFFFF", $transparencyPercent = 0) + public function paddedResize(string $width, string $height, string $backgroundColour = 'FFFFFF', int $transparencyPercent = 0): static { $resource = $this->getImageResource(); if (!$resource) { return null; } - // caclulate the background colour - $background = $resource->getDriver()->parseColor($backgroundColor)->format('array'); - // convert transparancy % to alpha - $background[3] = 1 - round(min(100, max(0, $transparencyPercent)) / 100, 2); + if ($transparencyPercent < 0 || $transparencyPercent > 100) { + throw new InvalidArgumentException('$transparencyPercent must be between 0 and 100. Got ' . $transparencyPercent); + } + + $bgColour = Color::create($backgroundColour); + // The Color class is immutable, so we have to instantiate a new one to set the alpha channel. + // No need to do that if both the $backgroundColor and $transparencyPercent are 0. + if ($bgColour->channel(Alpha::class)->value() !== 0 && $transparencyPercent !== 0) { + $channels = $bgColour->channels(); + $alpha = (int) round(255 * (1 - ($transparencyPercent * 0.01))); + $bgColour = new Color($channels[0]->value(), $channels[1]->value(), $channels[2]->value(), $alpha); + } - // resize the image maintaining the aspect ratio and then pad out the canvas + // resize the image maintaining the aspect ratio and pad out the canvas return $this->createCloneWithResource( - function (InterventionImage $resource) use ($width, $height, $background) { - return $resource - ->resize( - $width, - $height, - function (Constraint $constraint) { - $constraint->aspectRatio(); - } - ) - ->resizeCanvas( - $width, - $height, - 'center', - false, - $background - ); + function (InterventionImage $resource) use ($width, $height, $bgColour) { + return $resource->contain($width, $height, $bgColour, 'center'); } ); } /** - * Resize an image to cover the given width/height completely, and crop off any overhanging edges. - * - * @param int $width - * @param int $height - * @return static + * @inheritDoc */ - public function croppedResize($width, $height) + public function croppedResize(int $width, int $height, string $position = 'center'): static { return $this->createCloneWithResource( - function (InterventionImage $resource) use ($width, $height) { - return $resource->fit($width, $height); + function (InterventionImage $resource) use ($width, $height, $position) { + return $resource->cover($width, $height, $position); } ); } /** - * Crop's part of image. - * @param int $top y position of left upper corner of crop rectangle - * @param int $left x position of left upper corner of crop rectangle - * @param int $width rectangle width - * @param int $height rectangle height - * @return Image_Backend + * @inheritDoc */ - public function crop($top, $left, $width, $height) + public function crop(int $top, int $left, int $width, int $height, string $position = 'top-left', string $backgroundColour = 'FFFFFF'): static { return $this->createCloneWithResource( - function (InterventionImage $resource) use ($top, $left, $height, $width) { - return $resource->crop($width, $height, $left, $top); + function (InterventionImage $resource) use ($top, $left, $height, $width, $position, $backgroundColour) { + return $resource->crop($width, $height, $left, $top, $backgroundColour, $position); } ); } @@ -705,9 +573,8 @@ function (InterventionImage $resource) use ($top, $left, $height, $width) { * * @param InterventionImage|callable $resourceOrTransformation Either the resource to assign to the clone, * or a function which takes the current resource as a parameter - * @return static */ - protected function createCloneWithResource($resourceOrTransformation) + protected function createCloneWithResource($resourceOrTransformation): static { // No clone with no argument if (!$resourceOrTransformation) { @@ -743,11 +610,8 @@ protected function createCloneWithResource($resourceOrTransformation) /** * Clear any cached errors / metadata for this image - * - * @param string $hash - * @param string|null $variant */ - protected function markSuccess($hash, $variant = null) + protected function markSuccess(string $hash, ?string $variant = null): void { $key = $this->getErrorCacheKey($hash, $variant); $this->getCache()->deleteMultiple([ @@ -763,7 +627,7 @@ protected function markSuccess($hash, $variant = null) * @param string|null $variant Variant being loaded * @param string $reason Reason this file is failed */ - protected function markFailed($hash, $variant = null, $reason = InterventionBackend::FAILED_UNKNOWN) + protected function markFailed(string $hash, ?string $variant = null, string $reason = InterventionBackend::FAILED_UNKNOWN): void { $key = $this->getErrorCacheKey($hash, $variant); @@ -796,10 +660,8 @@ protected function markFailed($hash, $variant = null, $reason = InterventionBack * Will return one of the FAILED_* constant values, or null if not failed * * @param string $hash Hash of the original file being manipulated - * @param string|null $variant - * @return string|null */ - protected function hasFailed($hash, $variant = null) + protected function hasFailed(string $hash, ?string $variant = null): ?string { $key = $this->getErrorCacheKey($hash, $variant); return $this->getCache()->get($key.'_reason', null); @@ -810,10 +672,6 @@ protected function hasFailed($hash, $variant = null) */ public function __destruct() { - //skip the `getImageResource` method because we don't want to load the resource just to destroy it - if ($this->image) { - $this->image->destroy(); - } // remove our temp file if it exists if (file_exists($this->getTempPath() ?? '')) { unlink($this->getTempPath() ?? ''); @@ -827,7 +685,7 @@ public function __destruct() * * @see FlushRequestFilter */ - public static function flush() + public static function flush(): void { if (Config::inst()->get(static::class, 'flush_enabled')) { /** @var CacheInterface $cache */ @@ -838,11 +696,8 @@ public static function flush() /** * Validate the stream resource is readable - * - * @param mixed $stream - * @return bool */ - protected function isStreamReadable($stream) + protected function isStreamReadable(mixed $stream): bool { if (empty($stream)) { return false;