diff --git a/composer.json b/composer.json index 17f54a5..c147485 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.1", "ext-json": "*", - "68publishers/file-storage": "^1.3.0", + "68publishers/file-storage": "^1.4.0", "tg666/image": "^2.7.3", "psr/http-message": "^2.0" }, diff --git a/src/FileInfo.php b/src/FileInfo.php index de39d14..a191921 100644 --- a/src/FileInfo.php +++ b/src/FileInfo.php @@ -20,11 +20,15 @@ public function __construct(ImageLinkGeneratorInterface $linkGenerator, PathInfo parent::__construct($linkGenerator, $pathInfo, $imageStorageName); } - public function srcSet(DescriptorInterface $descriptor): SrcSet + public function srcSet(DescriptorInterface $descriptor, bool $absolute = true): SrcSet { assert($this->linkGenerator instanceof ImageLinkGeneratorInterface); - return $this->linkGenerator->srcSet($this, $descriptor); + return $this->linkGenerator->srcSet( + info: $this, + descriptor: $descriptor, + absolute: $absolute, + ); } public function getModifiers(): string|array|null diff --git a/src/FileInfoInterface.php b/src/FileInfoInterface.php index d7a38ae..b9d9f93 100644 --- a/src/FileInfoInterface.php +++ b/src/FileInfoInterface.php @@ -10,5 +10,5 @@ interface FileInfoInterface extends BaseFileInfoInterface, PathInfoInterface { - public function srcSet(DescriptorInterface $descriptor): SrcSet; + public function srcSet(DescriptorInterface $descriptor, bool $absolute = true): SrcSet; } diff --git a/src/ImageStorage.php b/src/ImageStorage.php index f1a73f3..b2406bf 100644 --- a/src/ImageStorage.php +++ b/src/ImageStorage.php @@ -90,11 +90,15 @@ public function resolveNoImage(string $path): ImagePathInfoInterface return $this->noImageResolver->resolveNoImage($path); } - public function srcSet(ImagePathInfoInterface $info, DescriptorInterface $descriptor): SrcSet + public function srcSet(ImagePathInfoInterface $info, DescriptorInterface $descriptor, bool $absolute = true): SrcSet { assert($this->linkGenerator instanceof ImageLinkGeneratorInterface); - return $this->linkGenerator->srcSet($info, $descriptor); + return $this->linkGenerator->srcSet( + info: $info, + descriptor: $descriptor, + absolute: $absolute, + ); } public function getSignatureStrategy(): ?SignatureStrategyInterface diff --git a/src/LinkGenerator/LinkGenerator.php b/src/LinkGenerator/LinkGenerator.php index 620a00f..4e1df0a 100644 --- a/src/LinkGenerator/LinkGenerator.php +++ b/src/LinkGenerator/LinkGenerator.php @@ -33,7 +33,7 @@ public function __construct( parent::__construct($this->config); } - public function link(FilePathInfoInterface $pathInfo): string + public function link(FilePathInfoInterface $pathInfo, bool $absolute = true): string { if (!$pathInfo instanceof ImagePathInfoInterface) { throw new InvalidArgumentException(sprintf( @@ -47,16 +47,23 @@ public function link(FilePathInfoInterface $pathInfo): string $pathInfo = $pathInfo->withModifiers(['original' => true]); } - return parent::link($pathInfo); + return parent::link( + pathInfo: $pathInfo, + absolute: $absolute, + ); } - public function srcSet(ImagePathInfoInterface $info, DescriptorInterface $descriptor): SrcSet + public function srcSet(ImagePathInfoInterface $info, DescriptorInterface $descriptor, bool $absolute = true): SrcSet { if (null === $this->srcSetGenerator) { $this->srcSetGenerator = $this->srcSetGeneratorFactory->create($this, $this->modifierFacade); } - return $this->srcSetGenerator->generate($descriptor, $info); + return $this->srcSetGenerator->generate( + descriptor: $descriptor, + pathInfo: $info, + absolute: $absolute, + ); } public function getSignatureStrategy(): ?SignatureStrategyInterface diff --git a/src/LinkGenerator/LinkGeneratorInterface.php b/src/LinkGenerator/LinkGeneratorInterface.php index db788e9..badedbe 100644 --- a/src/LinkGenerator/LinkGeneratorInterface.php +++ b/src/LinkGenerator/LinkGeneratorInterface.php @@ -12,7 +12,7 @@ interface LinkGeneratorInterface extends BaseLinkGeneratorInterface { - public function srcSet(PathInfoInterface $info, DescriptorInterface $descriptor): SrcSet; + public function srcSet(PathInfoInterface $info, DescriptorInterface $descriptor, bool $absolute = true): SrcSet; public function getSignatureStrategy(): ?SignatureStrategyInterface; } diff --git a/src/Responsive/Descriptor/ArgsFacade.php b/src/Responsive/Descriptor/ArgsFacade.php index dc3540f..92fd5f8 100644 --- a/src/Responsive/Descriptor/ArgsFacade.php +++ b/src/Responsive/Descriptor/ArgsFacade.php @@ -21,6 +21,7 @@ public function __construct( private readonly LinkGeneratorInterface $linkGenerator, private readonly ModifierFacadeInterface $modifierFacade, private readonly PathInfoInterface $pathInfo, + private readonly bool $absolute, ) { $modifiers = $this->pathInfo->getModifiers(); @@ -42,7 +43,10 @@ public function getDefaultModifiers(): ?array */ public function createLink(array $modifiers): string { - return $this->linkGenerator->link($this->pathInfo->withModifiers($modifiers)); + return $this->linkGenerator->link( + pathInfo: $this->pathInfo->withModifiers($modifiers), + absolute: $this->absolute, + ); } public function getModifierAlias(string $modifierClassName): ?string diff --git a/src/Responsive/SrcSetGenerator.php b/src/Responsive/SrcSetGenerator.php index 8a7e0f6..7a8b325 100644 --- a/src/Responsive/SrcSetGenerator.php +++ b/src/Responsive/SrcSetGenerator.php @@ -21,18 +21,19 @@ public function __construct( private readonly ModifierFacadeInterface $modifierFacade, ) {} - public function generate(DescriptorInterface $descriptor, PathInfoInterface $pathInfo): SrcSet + public function generate(DescriptorInterface $descriptor, PathInfoInterface $pathInfo, bool $absolute): SrcSet { - $key = $descriptor . '::' . (empty($pathInfo->getModifiers()) ? $pathInfo->withModifiers(['original' => true]) : $pathInfo); + $key = $descriptor . '::' . ($absolute ? 'abs' : 'rel') . '::' . (empty($pathInfo->getModifiers()) ? $pathInfo->withModifiers(['original' => true]) : $pathInfo); if (array_key_exists($key, $this->results)) { return $this->results[$key]; } return $this->results[$key] = $descriptor->createSrcSet(new ArgsFacade( - $this->linkGenerator, - $this->modifierFacade, - $pathInfo, + linkGenerator: $this->linkGenerator, + modifierFacade: $this->modifierFacade, + pathInfo: $pathInfo, + absolute: $absolute, )); } } diff --git a/tests/FileInfoTest.phpt b/tests/FileInfoTest.phpt index 8272f1e..4dc730f 100644 --- a/tests/FileInfoTest.phpt +++ b/tests/FileInfoTest.phpt @@ -54,12 +54,35 @@ final class FileInfoTest extends TestCase $linkGenerator->shouldReceive('srcSet') ->once() - ->with($fileInfo, $descriptor) + ->with($fileInfo, $descriptor, true) ->andReturn($srcSet); Assert::same($srcSet, $fileInfo->srcSet($descriptor)); } + public function testRelativeSrcSetShouldBeReturned(): void + { + $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); + $pathInfo = Mockery::mock(ImagePathInfoInterface::class); + $descriptor = Mockery::mock(DescriptorInterface::class); + $fileInfo = new FileInfo($linkGenerator, $pathInfo, 'default'); + $srcSet = new SrcSet( + descriptor: 'w', + links: [ + 100 => 'var/www/h:100,w:100/file.png', + 200 => 'var/www/h:100,w:200/file.png', + ], + value: 'var/www/h:100,w:100/file.png 100w, var/www/h:100,w:200/file.png 200w', + ); + + $linkGenerator->shouldReceive('srcSet') + ->once() + ->with($fileInfo, $descriptor, false) + ->andReturn($srcSet); + + Assert::same($srcSet, $fileInfo->srcSet($descriptor, false)); + } + public function testModifiersShouldBeNullIfFilePathInfoPassed(): void { $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); diff --git a/tests/ImageStorageTest.phpt b/tests/ImageStorageTest.phpt index 6605687..50ac713 100644 --- a/tests/ImageStorageTest.phpt +++ b/tests/ImageStorageTest.phpt @@ -249,7 +249,7 @@ final class ImageStorageTest extends TestCase $linkGenerator->shouldReceive('srcSet') ->once() - ->with($pathInfo, $descriptor) + ->with($pathInfo, $descriptor, true) ->andReturn($srcSet); $imageStorage = $this->createImageStorage(linkGenerator: $linkGenerator); @@ -257,6 +257,30 @@ final class ImageStorageTest extends TestCase Assert::same($srcSet, $imageStorage->srcSet($pathInfo, $descriptor)); } + public function testRelativeSrcSetShouldBeReturned(): void + { + $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); + $pathInfo = Mockery::mock(ImagePathInfoInterface::class); + $descriptor = Mockery::mock(DescriptorInterface::class); + $srcSet = new SrcSet( + descriptor: 'w', + links: [ + 100 => 'var/www/h:100,w:100/file.png', + 200 => 'var/www/h:100,w:200/file.png', + ], + value: 'var/www/h:100,w:100/file.png 100w, var/www/h:100,w:200/file.png 200w', + ); + + $linkGenerator->shouldReceive('srcSet') + ->once() + ->with($pathInfo, $descriptor, false) + ->andReturn($srcSet); + + $imageStorage = $this->createImageStorage(linkGenerator: $linkGenerator); + + Assert::same($srcSet, $imageStorage->srcSet($pathInfo, $descriptor, false)); + } + public function testSignatureStrategyShouldBeReturned(): void { $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); diff --git a/tests/LinkGenerator/LinkGeneratorTest.phpt b/tests/LinkGenerator/LinkGeneratorTest.phpt index 2792ea5..f6f668e 100644 --- a/tests/LinkGenerator/LinkGeneratorTest.phpt +++ b/tests/LinkGenerator/LinkGeneratorTest.phpt @@ -149,13 +149,43 @@ final class LinkGeneratorTest extends TestCase $srcSetGenerator->shouldReceive('generate') ->times(2) - ->with($descriptor, $pathInfo) + ->with($descriptor, $pathInfo, true) ->andReturn($srcSet); Assert::same($srcSet, $linkGenerator->srcSet($pathInfo, $descriptor)); Assert::same($srcSet, $linkGenerator->srcSet($pathInfo, $descriptor)); } + public function testRelativeSrcSetShouldBeCreated(): void + { + $modifierFacade = Mockery::mock(ModifierFacadeInterface::class); + $srcSetGeneratorFactory = Mockery::mock(SrcSetGeneratorFactoryInterface::class); + $srcSetGenerator = Mockery::mock(SrcSetGenerator::class); + $pathInfo = Mockery::mock(ImagePathInfoInterface::class); + $descriptor = Mockery::mock(DescriptorInterface::class); + $linkGenerator = new LinkGenerator(new Config([]), $modifierFacade, $srcSetGeneratorFactory); + $srcSet = new SrcSet( + descriptor: 'test', + links: [ + 1 => 'srcset', + ], + value: 'srcset', + ); + + $srcSetGeneratorFactory->shouldReceive('create') + ->once() + ->with($linkGenerator, $modifierFacade) + ->andReturn($srcSetGenerator); + + $srcSetGenerator->shouldReceive('generate') + ->times(2) + ->with($descriptor, $pathInfo, false) + ->andReturn($srcSet); + + Assert::same($srcSet, $linkGenerator->srcSet($pathInfo, $descriptor, false)); + Assert::same($srcSet, $linkGenerator->srcSet($pathInfo, $descriptor, false)); + } + public function tearDown(): void { Mockery::close(); diff --git a/tests/Responsive/Descriptor/ArgsFacadeTest.phpt b/tests/Responsive/Descriptor/ArgsFacadeTest.phpt index 67c60ba..a1cce49 100644 --- a/tests/Responsive/Descriptor/ArgsFacadeTest.phpt +++ b/tests/Responsive/Descriptor/ArgsFacadeTest.phpt @@ -32,7 +32,7 @@ final class ArgsFacadeTest extends TestCase ->withNoArgs() ->andReturn(null); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::null($facade->getDefaultModifiers()); } @@ -48,7 +48,7 @@ final class ArgsFacadeTest extends TestCase ->withNoArgs() ->andReturn(['w' => 150]); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::same(['w' => 150], $facade->getDefaultModifiers()); } @@ -79,7 +79,7 @@ final class ArgsFacadeTest extends TestCase return ['w' => 150]; }); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::same(['w' => 150], $facade->getDefaultModifiers()); } @@ -103,14 +103,41 @@ final class ArgsFacadeTest extends TestCase $linkGenerator->shouldReceive('link') ->once() - ->with($modifiedPathInfo) + ->with($modifiedPathInfo, true) ->andReturn('/var/www/h:100/file.png'); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::same('/var/www/h:100/file.png', $facade->createLink(['h' => 100])); } + public function testRelativeLinkShouldBeCreated(): void + { + $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); + $modifierFacade = Mockery::mock(ModifierFacadeInterface::class); + $pathInfo = Mockery::mock(PathInfoInterface::class); + $modifiedPathInfo = Mockery::mock(PathInfoInterface::class); + + $pathInfo->shouldReceive('getModifiers') + ->once() + ->withNoArgs() + ->andReturn(null); + + $pathInfo->shouldReceive('withModifiers') + ->once() + ->with(['h' => 100]) + ->andReturn($modifiedPathInfo); + + $linkGenerator->shouldReceive('link') + ->once() + ->with($modifiedPathInfo, false) + ->andReturn('var/www/h:100/file.png'); + + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, false); + + Assert::same('var/www/h:100/file.png', $facade->createLink(['h' => 100])); + } + public function testErrorShouldBeTriggeredIfModifierNotFound(): void { $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); @@ -133,7 +160,7 @@ final class ArgsFacadeTest extends TestCase ->with(Width::class) ->andThrows(new InvalidArgumentException('Missing modifier.')); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::error( static fn () => $facade->getModifierAlias(Width::class), @@ -164,7 +191,7 @@ final class ArgsFacadeTest extends TestCase ->with(Width::class) ->andReturn(new Width()); - $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo); + $facade = new ArgsFacade($linkGenerator, $modifierFacade, $pathInfo, true); Assert::same('w', $facade->getModifierAlias(Width::class)); } diff --git a/tests/Responsive/SrcSetGeneratorTest.phpt b/tests/Responsive/SrcSetGeneratorTest.phpt index dbed00a..6e6deff 100644 --- a/tests/Responsive/SrcSetGeneratorTest.phpt +++ b/tests/Responsive/SrcSetGeneratorTest.phpt @@ -69,16 +69,16 @@ final class SrcSetGeneratorTest extends TestCase $this->assertCache($generator, []); - Assert::same($srcSet, $generator->generate($descriptor, $pathInfo)); + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, true)); $this->assertCache($generator, [ - 'TEST()::var/www/original/file.png' => $srcSet, + 'TEST()::abs::var/www/original/file.png' => $srcSet, ]); - Assert::same($srcSet, $generator->generate($descriptor, $pathInfo)); + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, true)); $this->assertCache($generator, [ - 'TEST()::var/www/original/file.png' => $srcSet, + 'TEST()::abs::var/www/original/file.png' => $srcSet, ]); } @@ -124,16 +124,71 @@ final class SrcSetGeneratorTest extends TestCase $this->assertCache($generator, []); - Assert::same($srcSet, $generator->generate($descriptor, $pathInfo)); + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, true)); $this->assertCache($generator, [ - 'TEST()::var/www/h:100/file.png' => $srcSet, + 'TEST()::abs::var/www/h:100/file.png' => $srcSet, ]); - Assert::same($srcSet, $generator->generate($descriptor, $pathInfo)); + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, true)); $this->assertCache($generator, [ - 'TEST()::var/www/h:100/file.png' => $srcSet, + 'TEST()::abs::var/www/h:100/file.png' => $srcSet, + ]); + } + + public function testRelativeSrcSetShouldBeGeneratedAndCached(): void + { + $linkGenerator = Mockery::mock(LinkGeneratorInterface::class); + $modifierFacade = Mockery::mock(ModifierFacadeInterface::class); + $descriptor = Mockery::mock(DescriptorInterface::class); + $pathInfo = Mockery::mock(PathInfoInterface::class); + $srcSet = new SrcSet( + descriptor: 'test', + links: [ + 1 => 'srcset', + ], + value: 'srcset', + ); + + $descriptor->shouldReceive('__toString') + ->times(2) + ->withNoArgs() + ->andReturn('TEST()'); + + $pathInfo->shouldReceive('getModifiers') + ->times(3) # 2x in SrcSetGenerator, 1x in ArgsFacade + ->withNoArgs() + ->andReturn(['h' => 100]); + + $pathInfo->shouldReceive('__toString') + ->times(2) + ->withNoArgs() + ->andReturn('var/www/h:100/file.png'); + + $descriptor->shouldReceive('createSrcSet') + ->times(1) + ->with(Mockery::type(ArgsFacade::class)) + ->andReturnUsing(function (ArgsFacade $facade) use ($linkGenerator, $modifierFacade, $pathInfo, $srcSet): SrcSet { + $this->assertFacadePropertiesRelative($facade, $linkGenerator, $modifierFacade, $pathInfo); + + return $srcSet; + }); + + $generator = new SrcSetGenerator($linkGenerator, $modifierFacade); + + $this->assertCache($generator, []); + + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, false)); + + $this->assertCache($generator, [ + 'TEST()::rel::var/www/h:100/file.png' => $srcSet, + ]); + + Assert::same($srcSet, $generator->generate($descriptor, $pathInfo, false)); + + $this->assertCache($generator, [ + 'TEST()::rel::var/www/h:100/file.png' => $srcSet, ]); } @@ -149,6 +204,21 @@ final class SrcSetGeneratorTest extends TestCase Assert::same($facade->linkGenerator, $linkGenerator); Assert::same($facade->modifierFacade, $modifierFacade); Assert::same($facade->pathInfo, $pathInfo); + Assert::true($facade->absolute); + }, + null, + ArgsFacade::class, + )); + } + + private function assertFacadePropertiesRelative(ArgsFacade $facade, LinkGeneratorInterface $linkGenerator, ModifierFacadeInterface $modifierFacade, PathInfoInterface $pathInfo): void + { + call_user_func(Closure::bind( + static function () use ($facade, $linkGenerator, $modifierFacade, $pathInfo): void { + Assert::same($facade->linkGenerator, $linkGenerator); + Assert::same($facade->modifierFacade, $modifierFacade); + Assert::same($facade->pathInfo, $pathInfo); + Assert::false($facade->absolute); }, null, ArgsFacade::class,