Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Filter out parameter that do not match target encoder #1333

Merged
merged 12 commits into from Apr 23, 2024
43 changes: 32 additions & 11 deletions src/Format.php
Expand Up @@ -16,6 +16,7 @@
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Exceptions\NotSupportedException;
use Intervention\Image\Interfaces\EncoderInterface;
use ReflectionClass;

enum Format
{
Expand Down Expand Up @@ -88,23 +89,43 @@ public function fileExtensions(): array
}

/**
* Create an encoder instance that matches the format
* Create an encoder instance with given options that matches the format
*
* @param mixed $options
* @return EncoderInterface
*/
public function encoder(mixed ...$options): EncoderInterface
{
return match ($this) {
self::AVIF => new AvifEncoder(...$options),
self::BMP => new BmpEncoder(...$options),
self::GIF => new GifEncoder(...$options),
self::HEIC => new HeicEncoder(...$options),
self::JP2 => new Jpeg2000Encoder(...$options),
self::JPEG => new JpegEncoder(...$options),
self::PNG => new PngEncoder(...$options),
self::TIFF => new TiffEncoder(...$options),
self::WEBP => new WebpEncoder(...$options),
// get classname of target encoder from current format
$classname = match ($this) {
self::AVIF => AvifEncoder::class,
self::BMP => BmpEncoder::class,
self::GIF => GifEncoder::class,
self::HEIC => HeicEncoder::class,
self::JP2 => Jpeg2000Encoder::class,
self::JPEG => JpegEncoder::class,
self::PNG => PngEncoder::class,
self::TIFF => TiffEncoder::class,
self::WEBP => WebpEncoder::class,
};

// get parameters of target encoder
$parameters = [];
$reflectionClass = new ReflectionClass($classname);
if ($constructor = $reflectionClass->getConstructor()) {
$parameters = array_map(
fn ($parameter) => $parameter->getName(),
$constructor->getParameters(),
);
}

// filter out unavailable options of target encoder
$options = array_filter(
$options,
fn ($key) => in_array($key, $parameters),
ARRAY_FILTER_USE_KEY,
);

return new $classname(...$options);
}
}
138 changes: 50 additions & 88 deletions tests/Unit/Encoders/FileExtensionEncoderTest.php
Expand Up @@ -18,13 +18,19 @@
use Intervention\Image\FileExtension;
use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\Tests\BaseTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

final class FileExtensionEncoderTest extends BaseTestCase
{
private function testEncoder(string|FileExtension $extension): EncoderInterface
private function testEncoder(string|FileExtension $extension, array $options = []): EncoderInterface
{
$encoder = new class () extends FileExtensionEncoder
$encoder = new class ($extension, ...$options) extends FileExtensionEncoder
{
public function __construct($mediaType, ...$options)
{
parent::__construct($mediaType, ...$options);
}

public function test($extension)
{
return $this->encoderByFileExtension($extension);
Expand All @@ -34,100 +40,56 @@ public function test($extension)
return $encoder->test($extension);
}

public function testEncoderByFileExtensionString(): void
{
$this->assertInstanceOf(
WebpEncoder::class,
$this->testEncoder('webp')
);

#[DataProvider('targetEncoderProvider')]
public function testEncoderByFileExtensionString(
string|FileExtension $fileExtension,
string $targetEncoderClassname,
): void {
$this->assertInstanceOf(
AvifEncoder::class,
$this->testEncoder('avif')
);

$this->assertInstanceOf(
JpegEncoder::class,
$this->testEncoder('jpeg')
);

$this->assertInstanceOf(
BmpEncoder::class,
$this->testEncoder('bmp')
);

$this->assertInstanceOf(
GifEncoder::class,
$this->testEncoder('gif')
);

$this->assertInstanceOf(
PngEncoder::class,
$this->testEncoder('png')
);

$this->assertInstanceOf(
TiffEncoder::class,
$this->testEncoder('tiff')
);

$this->assertInstanceOf(
Jpeg2000Encoder::class,
$this->testEncoder('jp2')
);

$this->assertInstanceOf(
HeicEncoder::class,
$this->testEncoder('heic')
$targetEncoderClassname,
$this->testEncoder($fileExtension),
);
}

public function testEncoderByFileExtensionEnumMember(): void
public static function targetEncoderProvider(): array
{
$this->assertInstanceOf(
WebpEncoder::class,
$this->testEncoder(FileExtension::WEBP)
);

$this->assertInstanceOf(
AvifEncoder::class,
$this->testEncoder(FileExtension::AVIF)
);

$this->assertInstanceOf(
JpegEncoder::class,
$this->testEncoder(FileExtension::JPG)
);

$this->assertInstanceOf(
BmpEncoder::class,
$this->testEncoder(FileExtension::BMP)
);

$this->assertInstanceOf(
GifEncoder::class,
$this->testEncoder(FileExtension::GIF)
);

$this->assertInstanceOf(
PngEncoder::class,
$this->testEncoder(FileExtension::PNG)
);

$this->assertInstanceOf(
TiffEncoder::class,
$this->testEncoder(FileExtension::TIF)
);
return [
['webp', WebpEncoder::class],
['avif', AvifEncoder::class],
['jpeg', JpegEncoder::class],
['jpg', JpegEncoder::class],
['bmp', BmpEncoder::class],
['gif', GifEncoder::class],
['png', PngEncoder::class],
['tiff', TiffEncoder::class],
['tif', TiffEncoder::class],
['jp2', Jpeg2000Encoder::class],
['heic', HeicEncoder::class],
[FileExtension::WEBP, WebpEncoder::class],
[FileExtension::AVIF, AvifEncoder::class],
[FileExtension::JPG, JpegEncoder::class],
[FileExtension::BMP, BmpEncoder::class],
[FileExtension::GIF, GifEncoder::class],
[FileExtension::PNG, PngEncoder::class],
[FileExtension::TIF, TiffEncoder::class],
[FileExtension::TIFF, TiffEncoder::class],
[FileExtension::JP2, Jpeg2000Encoder::class],
[FileExtension::HEIC, HeicEncoder::class],
];
}

$this->assertInstanceOf(
Jpeg2000Encoder::class,
$this->testEncoder(FileExtension::JPG2)
public function testArgumentsNotSupportedByTargetEncoder(): void
{
$encoder = $this->testEncoder(
'png',
[
'interlaced' => true, // is not ignored
'quality' => 10, // is ignored because png encoder has no quality argument
],
);

$this->assertInstanceOf(
HeicEncoder::class,
$this->testEncoder(FileExtension::HEIC)
);
$this->assertInstanceOf(PngEncoder::class, $encoder);
$this->assertTrue($encoder->interlaced);
}

public function testEncoderByFileExtensionUnknown(): void
Expand Down
99 changes: 99 additions & 0 deletions tests/Unit/Encoders/MediaTypeEncoderTest.php
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Intervention\Image\Tests\Unit\Encoders;

use Intervention\Image\Encoders\AvifEncoder;
use Intervention\Image\Encoders\BmpEncoder;
use Intervention\Image\Encoders\GifEncoder;
use Intervention\Image\Encoders\HeicEncoder;
use Intervention\Image\Encoders\Jpeg2000Encoder;
use Intervention\Image\Encoders\JpegEncoder;
use Intervention\Image\Encoders\MediaTypeEncoder;
use Intervention\Image\Encoders\PngEncoder;
use Intervention\Image\Encoders\TiffEncoder;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Exceptions\EncoderException;
use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\MediaType;
use Intervention\Image\Tests\BaseTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

final class MediaTypeEncoderTest extends BaseTestCase
{
private function testEncoder(string|MediaType $mediaType, array $options = []): EncoderInterface
{
$encoder = new class ($mediaType, ...$options) extends MediaTypeEncoder
{
public function __construct($mediaType, ...$options)
{
parent::__construct($mediaType, ...$options);
}

public function test($mediaType)
{
return $this->encoderByMediaType($mediaType);
}
};

return $encoder->test($mediaType);
}

#[DataProvider('targetEncoderProvider')]
public function testEncoderByMediaType(
string|MediaType $mediaType,
string $targetEncoderClassname,
): void {
$this->assertInstanceOf(
$targetEncoderClassname,
$this->testEncoder($mediaType)
);
}

public static function targetEncoderProvider(): array
{
return [
['image/webp', WebpEncoder::class],
['image/avif', AvifEncoder::class],
['image/jpeg', JpegEncoder::class],
['image/bmp', BmpEncoder::class],
['image/gif', GifEncoder::class],
['image/png', PngEncoder::class],
['image/png', PngEncoder::class],
['image/tiff', TiffEncoder::class],
['image/jp2', Jpeg2000Encoder::class],
['image/heic', HeicEncoder::class],
[MediaType::IMAGE_WEBP, WebpEncoder::class],
[MediaType::IMAGE_AVIF, AvifEncoder::class],
[MediaType::IMAGE_JPEG, JpegEncoder::class],
[MediaType::IMAGE_BMP, BmpEncoder::class],
[MediaType::IMAGE_GIF, GifEncoder::class],
[MediaType::IMAGE_PNG, PngEncoder::class],
[MediaType::IMAGE_TIFF, TiffEncoder::class],
[MediaType::IMAGE_JP2, Jpeg2000Encoder::class],
[MediaType::IMAGE_HEIC, HeicEncoder::class],
[MediaType::IMAGE_HEIF, HeicEncoder::class],
];
}

public function testArgumentsNotSupportedByTargetEncoder(): void
{
$encoder = $this->testEncoder(
'image/png',
[
'interlaced' => true, // is not ignored
'quality' => 10, // is ignored because png encoder has no quality argument
],
);

$this->assertInstanceOf(PngEncoder::class, $encoder);
$this->assertTrue($encoder->interlaced);
}

public function testEncoderByFileExtensionUnknown(): void
{
$this->expectException(EncoderException::class);
$this->testEncoder('test');
}
}