diff --git a/features/setup/symfonyCache.feature b/features/setup/symfonyCache.feature index 2164b2c..986933d 100644 --- a/features/setup/symfonyCache.feature +++ b/features/setup/symfonyCache.feature @@ -19,20 +19,20 @@ index 9982c21..03ac40a 100644 +++ b/public/index.php @@ -1,9 +1,14 @@ , Prophecy\\Argument\\Token\\AnyValueToken given\.$#' - identifier: argument.type - count: 1 - path: spec/ResponseTagger/Value/ContentInfoTaggerSpec.php - - - - message: '#^Call to an undefined method FOS\\HttpCache\\ResponseTagger\:\:shouldHaveBeenCalled\(\)\.$#' - identifier: method.notFound - count: 3 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - - - message: '#^Call to an undefined method FOS\\HttpCache\\ResponseTagger\:\:shouldNotHaveBeenCalled\(\)\.$#' - identifier: method.notFound - count: 1 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - - - message: '#^Call to an undefined method FOS\\HttpCache\\ResponseTagger\:\:willReturn\(\)\.$#' - identifier: method.notFound - count: 1 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - - - message: '#^Call to an undefined method spec\\Ibexa\\HttpCache\\ResponseTagger\\Value\\LocationTaggerSpec\:\:tag\(\)\.$#' - identifier: method.notFound - count: 4 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - - - message: '#^Class spec\\Ibexa\\HttpCache\\ResponseTagger\\Value\\LocationTaggerSpec extends generic class PhpSpec\\ObjectBehavior but does not specify its types\: TKey, TValue$#' - identifier: missingType.generics - count: 1 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - - - message: '#^Parameter \#1 \$tags of method FOS\\HttpCache\\ResponseTagger\:\:addTags\(\) expects array\, Prophecy\\Argument\\Token\\AnyValueToken given\.$#' - identifier: argument.type - count: 2 - path: spec/ResponseTagger/Value/LocationTaggerSpec.php - - message: '#^Call to an undefined method Symfony\\Component\\HttpKernel\\HttpKernelInterface\:\:isDebug\(\)\.$#' identifier: method.notFound @@ -1122,24 +996,6 @@ parameters: count: 1 path: src/lib/ResponseTagger/Delegator/ContentValueViewTagger.php - - - message: '#^Method Ibexa\\HttpCache\\ResponseTagger\\Delegator\\DispatcherTagger\:\:__construct\(\) has parameter \$taggers with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/lib/ResponseTagger/Delegator/DispatcherTagger.php - - - - message: '#^PHPDoc tag @var for property Ibexa\\HttpCache\\ResponseTagger\\Delegator\\DispatcherTagger\:\:\$taggers with type Ibexa\\Contracts\\HttpCache\\ResponseTagger\\ResponseTagger is incompatible with native type array\.$#' - identifier: property.phpDocType - count: 1 - path: src/lib/ResponseTagger/Delegator/DispatcherTagger.php - - - - message: '#^Property Ibexa\\HttpCache\\ResponseTagger\\Delegator\\DispatcherTagger\:\:\$taggers type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/lib/ResponseTagger/Delegator/DispatcherTagger.php - - message: '#^Method Ibexa\\HttpCache\\ResponseTagger\\Delegator\\LocationValueViewTagger\:\:tag\(\) has no return type specified\.$#' identifier: missingType.return diff --git a/spec/ResponseTagger/Delegator/ContentValueViewTaggerSpec.php b/spec/ResponseTagger/Delegator/ContentValueViewTaggerSpec.php deleted file mode 100644 index ce6bc8a..0000000 --- a/spec/ResponseTagger/Delegator/ContentValueViewTaggerSpec.php +++ /dev/null @@ -1,42 +0,0 @@ -beConstructedWith($contentInfoTagger); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(ContentValueViewTagger::class); - } - - public function it_delegates_tagging_of_the_content_info( - ResponseTagger $contentInfoTagger, - ContentValueView $view - ): void { - $contentInfo = new ContentInfo(); - $content = new Content(['versionInfo' => new VersionInfo(['contentInfo' => $contentInfo])]); - $view->getContent()->willReturn($content); - - $this->tag($view); - - $contentInfoTagger->tag($contentInfo)->shouldHaveBeenCalled(); - } -} diff --git a/spec/ResponseTagger/Delegator/DispatcherTaggerSpec.php b/spec/ResponseTagger/Delegator/DispatcherTaggerSpec.php deleted file mode 100644 index 452af93..0000000 --- a/spec/ResponseTagger/Delegator/DispatcherTaggerSpec.php +++ /dev/null @@ -1,37 +0,0 @@ -beConstructedWith([$taggerOne, $taggerTwo]); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(DispatcherTagger::class); - } - - public function it_calls_tag_on_every_tagger( - ResponseTagger $taggerOne, - ResponseTagger $taggerTwo, - ValueObject $value - ): void { - $this->tag($value); - - $taggerOne->tag($value)->shouldHaveBeenCalled(); - $taggerTwo->tag($value)->shouldHaveBeenCalled(); - } -} diff --git a/spec/ResponseTagger/Delegator/LocationValueViewTaggerSpec.php b/spec/ResponseTagger/Delegator/LocationValueViewTaggerSpec.php deleted file mode 100644 index c750bb5..0000000 --- a/spec/ResponseTagger/Delegator/LocationValueViewTaggerSpec.php +++ /dev/null @@ -1,38 +0,0 @@ -beConstructedWith($locationTagger); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(LocationValueViewTagger::class); - } - - public function it_delegates_tagging_of_the_location( - ResponseTagger $locationTagger, - LocationValueView $view - ): void { - $location = new Location(); - $view->getLocation()->willReturn($location); - $this->tag($view); - - $locationTagger->tag($location)->shouldHaveBeenCalled(); - } -} diff --git a/spec/ResponseTagger/Value/ContentInfoTaggerSpec.php b/spec/ResponseTagger/Value/ContentInfoTaggerSpec.php deleted file mode 100644 index 021e0be..0000000 --- a/spec/ResponseTagger/Value/ContentInfoTaggerSpec.php +++ /dev/null @@ -1,62 +0,0 @@ -beConstructedWith($tagHandler); - - $tagHandler->addTags(Argument::any())->willReturn($tagHandler); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(ContentInfoTagger::class); - } - - public function it_ignores_non_content_info(ResponseTagger $tagHandler): void - { - $this->tag(null); - - $tagHandler->addTags()->shouldNotHaveBeenCalled(); - } - - public function it_tags_with_content_and_content_type_id(ResponseTagger $tagHandler): void - { - $value = new ContentInfo([ - 'id' => 123, - 'mainLocationId' => 456, - 'contentTypeId' => 987, - ]); - - $this->tag($value); - - $tagHandler->addTags(['c123', 'ct987'])->shouldHaveBeenCalled(); - } - - public function it_tags_with_location_id_if_one_is_set(ResponseTagger $tagHandler): void - { - $value = new ContentInfo([ - 'id' => 123, - 'mainLocationId' => 456, - 'contentTypeId' => 987, - ]); - - $this->tag($value); - - $tagHandler->addTags(['l456'])->shouldHaveBeenCalled(); - } -} diff --git a/spec/ResponseTagger/Value/LocationTaggerSpec.php b/spec/ResponseTagger/Value/LocationTaggerSpec.php deleted file mode 100644 index 7acfb0c..0000000 --- a/spec/ResponseTagger/Value/LocationTaggerSpec.php +++ /dev/null @@ -1,77 +0,0 @@ -beConstructedWith($tagHandler); - - $tagHandler->addTags(Argument::any())->willReturn($tagHandler); - } - - public function it_is_initializable(): void - { - $this->shouldHaveType(LocationTagger::class); - } - - public function it_ignores_non_location(ResponseTagger $tagHandler): void - { - $this->tag(null); - - $tagHandler->addTags(Argument::any())->shouldNotHaveBeenCalled(); - } - - public function it_tags_with_location_id_if_not_main_location(ResponseTagger $tagHandler): void - { - $value = new Location([ - 'id' => 123, - 'contentInfo' => new ContentInfo(['mainLocationId' => 321]), - 'parentLocationId' => 456, - ]); - - $this->tag($value); - - $tagHandler->addTags(['l123'])->shouldHaveBeenCalled(); - } - - public function it_tags_with_parent_location_id(ResponseTagger $tagHandler): void - { - $value = new Location([ - 'id' => 8, - 'parentLocationId' => 123, - 'contentInfo' => new ContentInfo(), - ]); - - $this->tag($value); - - $tagHandler->addTags(['pl123'])->shouldHaveBeenCalled(); - } - - public function it_tags_with_path_items(ResponseTagger $tagHandler): void - { - $value = new Location([ - 'id' => 4, - 'parentLocationId' => 123, - 'pathString' => '/1/2/123', - 'contentInfo' => new ContentInfo(), - ]); - - $this->tag($value); - - $tagHandler->addTags(['p1', 'p2', 'p123'])->shouldHaveBeenCalled(); - } -} diff --git a/src/bundle/DependencyInjection/Compiler/ResponseTaggersPass.php b/src/bundle/DependencyInjection/Compiler/ResponseTaggersPass.php index c85e8a3..86336ca 100644 --- a/src/bundle/DependencyInjection/Compiler/ResponseTaggersPass.php +++ b/src/bundle/DependencyInjection/Compiler/ResponseTaggersPass.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Bundle\HttpCache\DependencyInjection\Compiler; @@ -15,7 +16,7 @@ /** * Injects services tagged as "ibexa.cache.http.response.tagger" into the dispatcher. */ -class ResponseTaggersPass implements CompilerPassInterface +final readonly class ResponseTaggersPass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { diff --git a/src/contracts/ResponseTagger/ResponseTagger.php b/src/contracts/ResponseTagger/ResponseTagger.php index 9e251cc..dcda090 100644 --- a/src/contracts/ResponseTagger/ResponseTagger.php +++ b/src/contracts/ResponseTagger/ResponseTagger.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Contracts\HttpCache\ResponseTagger; diff --git a/src/lib/ResponseTagger/Delegator/ContentValueViewTagger.php b/src/lib/ResponseTagger/Delegator/ContentValueViewTagger.php index a749b3b..5461eb1 100644 --- a/src/lib/ResponseTagger/Delegator/ContentValueViewTagger.php +++ b/src/lib/ResponseTagger/Delegator/ContentValueViewTagger.php @@ -4,29 +4,32 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\HttpCache\ResponseTagger\Delegator; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger; use Ibexa\Core\MVC\Symfony\View\ContentValueView; +use Ibexa\HttpCache\ResponseTagger\Value\AbstractValueTagger; -class ContentValueViewTagger implements ResponseTagger +class ContentValueViewTagger extends AbstractValueTagger { - private ResponseTagger $contentInfoTagger; + public function __construct(private readonly ResponseTagger $contentInfoTagger) + { + } - public function __construct(ResponseTagger $contentInfoTagger) + public function supports(mixed $value): bool { - $this->contentInfoTagger = $contentInfoTagger; + return $value instanceof ContentValueView && $value->getContent() instanceof Content; } - public function tag($view) + public function tag(mixed $value) { - if (!$view instanceof ContentValueView || !($content = $view->getContent()) instanceof Content) { - return $this; - } + assert($value instanceof ContentValueView); - $contentInfo = $content->getVersionInfo()->getContentInfo(); - $this->contentInfoTagger->tag($contentInfo); + $this->contentInfoTagger->tag( + $value->getContent()->getVersionInfo()->getContentInfo() + ); } } diff --git a/src/lib/ResponseTagger/Delegator/DispatcherTagger.php b/src/lib/ResponseTagger/Delegator/DispatcherTagger.php index 292d685..39acba5 100644 --- a/src/lib/ResponseTagger/Delegator/DispatcherTagger.php +++ b/src/lib/ResponseTagger/Delegator/DispatcherTagger.php @@ -8,26 +8,49 @@ namespace Ibexa\HttpCache\ResponseTagger\Delegator; use Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger; +use function Ibexa\PolyfillPhp82\iterator_to_array; /** * Dispatches a value to all registered ResponseTaggers. */ -class DispatcherTagger implements ResponseTagger +readonly class DispatcherTagger implements ResponseTagger { /** - * @var \Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger + * @param iterable<\Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger> $taggers */ - private array $taggers; - - public function __construct(array $taggers = []) + public function __construct(private iterable $taggers = []) { - $this->taggers = $taggers; } - public function tag($value): void + public function tag(mixed $value): void { foreach ($this->taggers as $tagger) { - $tagger->tag($value); + if (method_exists($tagger, 'supports')) { + if ($tagger->supports($value)) { + $tagger->tag($value); + } + } else { + trigger_deprecation( + 'ibexa/http-cache', + '5.0.7', + '%s does not implement supports(). This will be required in 6.0, supports() will be a part of ResponseTagger interface', + get_debug_type($tagger), + ); + $tagger->tag($value); + } } } + + public function __toString(): string + { + $taggers = implode( + ', ', + array_map( + static fn (ResponseTagger $tagger): string => get_debug_type($tagger), + iterator_to_array($this->taggers) + ) + ); + + return sprintf('Available response taggers are: %s', $taggers); + } } diff --git a/src/lib/ResponseTagger/Delegator/LocationValueViewTagger.php b/src/lib/ResponseTagger/Delegator/LocationValueViewTagger.php index 7481bd1..0ef8b3b 100644 --- a/src/lib/ResponseTagger/Delegator/LocationValueViewTagger.php +++ b/src/lib/ResponseTagger/Delegator/LocationValueViewTagger.php @@ -4,28 +4,30 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\HttpCache\ResponseTagger\Delegator; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\HttpCache\ResponseTagger\ResponseTagger; use Ibexa\Core\MVC\Symfony\View\LocationValueView; +use Ibexa\HttpCache\ResponseTagger\Value\AbstractValueTagger; -class LocationValueViewTagger implements ResponseTagger +class LocationValueViewTagger extends AbstractValueTagger { - private ResponseTagger $locationTagger; + public function __construct(private readonly ResponseTagger $locationTagger) + { + } - public function __construct(ResponseTagger $locationTagger) + public function supports(mixed $value): bool { - $this->locationTagger = $locationTagger; + return $value instanceof LocationValueView && $value->getLocation() instanceof Location; } - public function tag($view) + public function tag(mixed $value) { - if (!$view instanceof LocationValueView || !($location = $view->getLocation()) instanceof Location) { - return $this; - } + assert($value instanceof LocationValueView); - $this->locationTagger->tag($location); + $this->locationTagger->tag($value->getLocation()); } } diff --git a/src/lib/ResponseTagger/Value/AbstractValueTagger.php b/src/lib/ResponseTagger/Value/AbstractValueTagger.php index e84eb80..89b722b 100644 --- a/src/lib/ResponseTagger/Value/AbstractValueTagger.php +++ b/src/lib/ResponseTagger/Value/AbstractValueTagger.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\HttpCache\ResponseTagger\Value; @@ -12,10 +13,9 @@ abstract class AbstractValueTagger implements ResponseTagger { - protected FosResponseTagger $responseTagger; - - public function __construct(FosResponseTagger $responseTagger) + public function __construct(protected FosResponseTagger $responseTagger) { - $this->responseTagger = $responseTagger; } + + abstract public function supports(mixed $value): bool; } diff --git a/src/lib/ResponseTagger/Value/ContentInfoTagger.php b/src/lib/ResponseTagger/Value/ContentInfoTagger.php index ba1846d..194ecee 100644 --- a/src/lib/ResponseTagger/Value/ContentInfoTagger.php +++ b/src/lib/ResponseTagger/Value/ContentInfoTagger.php @@ -4,27 +4,34 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\HttpCache\ResponseTagger\Value; use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo; use Ibexa\Contracts\HttpCache\Handler\ContentTagInterface; +/** + * @final + */ class ContentInfoTagger extends AbstractValueTagger { - public function tag($value) + public function supports(mixed $value): bool { - if (!$value instanceof ContentInfo) { - return $this; - } + return $value instanceof ContentInfo; + } + public function tag(mixed $value) + { $this->responseTagger->addTags([ - ContentTagInterface::CONTENT_PREFIX . $value->id, + ContentTagInterface::CONTENT_PREFIX . $value->getId(), ContentTagInterface::CONTENT_TYPE_PREFIX . $value->contentTypeId, ]); if ($value->mainLocationId) { - $this->responseTagger->addTags([ContentTagInterface::LOCATION_PREFIX . $value->mainLocationId]); + $this->responseTagger->addTags([ + ContentTagInterface::LOCATION_PREFIX . $value->getMainLocationId(), + ]); } } } diff --git a/src/lib/ResponseTagger/Value/LocationTagger.php b/src/lib/ResponseTagger/Value/LocationTagger.php index 5ab85e5..6ccaa21 100644 --- a/src/lib/ResponseTagger/Value/LocationTagger.php +++ b/src/lib/ResponseTagger/Value/LocationTagger.php @@ -4,22 +4,27 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\HttpCache\ResponseTagger\Value; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\HttpCache\Handler\ContentTagInterface; +/** + * @final + */ class LocationTagger extends AbstractValueTagger { - public function tag($value) + public function supports(mixed $value): bool { - if (!$value instanceof Location) { - return $this; - } + return $value instanceof Location; + } - if ($value->id !== $value->contentInfo->mainLocationId) { - $this->responseTagger->addTags([ContentTagInterface::LOCATION_PREFIX . $value->id]); + public function tag(mixed $value) + { + if ($value->id !== $value->getContentInfo()->getMainLocationId()) { + $this->responseTagger->addTags([ContentTagInterface::LOCATION_PREFIX . $value->getId()]); } $this->responseTagger->addTags([ContentTagInterface::PARENT_LOCATION_PREFIX . $value->parentLocationId]); @@ -28,7 +33,7 @@ public function tag($value) static function (string $pathItem): string { return ContentTagInterface::PATH_PREFIX . $pathItem; }, - $value->path + $value->getPath() ) ); } diff --git a/tests/lib/ResponseTagger/Delegator/ContentValueViewTaggerTest.php b/tests/lib/ResponseTagger/Delegator/ContentValueViewTaggerTest.php new file mode 100644 index 0000000..a204099 --- /dev/null +++ b/tests/lib/ResponseTagger/Delegator/ContentValueViewTaggerTest.php @@ -0,0 +1,61 @@ +contentInfoTagger = $this->createMock(ResponseTagger::class); + $this->tagger = new ContentValueViewTagger($this->contentInfoTagger); + } + + public function testSupportsContentValueViewWithContent(): void + { + $view = $this->createMock(ContentValueView::class); + $content = new Content(['versionInfo' => new VersionInfo(['contentInfo' => new ContentInfo()])]); + $view->method('getContent')->willReturn($content); + + self::assertTrue($this->tagger->supports($view)); + } + + public function testDoesNotSupportNonContentValueView(): void + { + self::assertFalse($this->tagger->supports(new Location())); + } + + public function testDelegatesTaggingOfContentInfo(): void + { + $contentInfo = new ContentInfo(['id' => 42]); + $content = new Content(['versionInfo' => new VersionInfo(['contentInfo' => $contentInfo])]); + + $view = $this->createMock(ContentValueView::class); + $view->method('getContent')->willReturn($content); + + $this->contentInfoTagger + ->expects(self::once()) + ->method('tag') + ->with($contentInfo); + + $this->tagger->tag($view); + } +} diff --git a/tests/lib/ResponseTagger/Delegator/DispatcherTaggerTest.php b/tests/lib/ResponseTagger/Delegator/DispatcherTaggerTest.php new file mode 100644 index 0000000..81e20fc --- /dev/null +++ b/tests/lib/ResponseTagger/Delegator/DispatcherTaggerTest.php @@ -0,0 +1,154 @@ + 1, 'contentTypeId' => 2]); + + $contentInfoTagger = $this->createMock(ContentInfoTagger::class); + $locationTagger = $this->createMock(LocationTagger::class); + + $contentInfoTagger + ->method('supports') + ->with($contentInfo) + ->willReturn(true); + + $locationTagger + ->expects(self::once()) + ->method('supports') + ->with($contentInfo) + ->willReturn(false); + + $contentInfoTagger + ->expects(self::once()) + ->method('tag') + ->with($contentInfo); + + $locationTagger + ->expects(self::never()) + ->method('tag'); + + $dispatcher = new DispatcherTagger([$contentInfoTagger, $locationTagger]); + $dispatcher->tag($contentInfo); + } + + public function testDoesNotCallTagWhenNoTaggerSupportsTheValue(): void + { + $location = new Location(['id' => 1]); + + $contentInfoTagger = $this->createMock(ContentInfoTagger::class); + $locationTagger = $this->createMock(LocationTagger::class); + + $contentInfoTagger + ->expects(self::once()) + ->method('supports') + ->with($location) + ->willReturn(false); + + $locationTagger + ->expects(self::once()) + ->method('supports') + ->with($location) + ->willReturn(false); + + $contentInfoTagger + ->expects(self::never()) + ->method('tag'); + + $locationTagger + ->expects(self::never()) + ->method('tag'); + + $dispatcher = new DispatcherTagger([$contentInfoTagger, $locationTagger]); + $dispatcher->tag($location); + } + + public function testCustomResponseTaggerImplementationLackingSupportsMethodShouldTag(): void + { + $foo = new stdClass(); + + $contentInfoTagger = $this->createMock(ContentInfoTagger::class); + $contentInfoTagger + ->expects(self::once()) + ->method('supports') + ->with($foo) + ->willReturn(false); + + $contentInfoTagger + ->expects(self::never()) + ->method('tag'); + + $wasCalled = false; + $customTagger = new class($wasCalled) implements ResponseTagger { + public function __construct(private bool &$wasCalled) + { + } + + public function tag(mixed $value): void + { + $this->wasCalled = true; + } + }; + + $dispatcher = new DispatcherTagger([$contentInfoTagger, $customTagger]); + + $deprecation = null; + set_error_handler(static function (int $errorCode, string $errorString) use (&$deprecation): bool { + if ($errorCode === E_USER_DEPRECATED) { + $deprecation = $errorString; + } + + return true; + }); + + try { + $dispatcher->tag($foo); + } finally { + restore_error_handler(); + } + + self::assertTrue($wasCalled, 'Custom ResponseTagger::tag() was not called by the dispatcher.'); + self::assertStringContainsString('does not implement supports()', $deprecation); + } + + public function testToStringWithNoTaggers(): void + { + $dispatcher = new DispatcherTagger(); + + self::assertSame('Available response taggers are: ', (string)$dispatcher); + } + + public function testToStringListsRegisteredTaggerTypes(): void + { + $fosResponseTagger = $this->createMock(FosResponseTagger::class); + + $dispatcher = new DispatcherTagger([ + new ContentInfoTagger($fosResponseTagger), + new LocationTagger($fosResponseTagger), + ]); + + self::assertSame( + 'Available response taggers are: Ibexa\HttpCache\ResponseTagger\Value\ContentInfoTagger, Ibexa\HttpCache\ResponseTagger\Value\LocationTagger', + (string)$dispatcher + ); + } +} diff --git a/tests/lib/ResponseTagger/Delegator/LocationValueViewTaggerTest.php b/tests/lib/ResponseTagger/Delegator/LocationValueViewTaggerTest.php new file mode 100644 index 0000000..afc1aae --- /dev/null +++ b/tests/lib/ResponseTagger/Delegator/LocationValueViewTaggerTest.php @@ -0,0 +1,49 @@ +locationTagger = $this->createMock(ResponseTagger::class); + $this->tagger = new LocationValueViewTagger($this->locationTagger); + } + + public function testDoesNotSupportNonLocationValueView(): void + { + self::assertFalse($this->tagger->supports(new ContentInfo())); + } + + public function testDelegatesTaggingOfLocation(): void + { + $location = new Location(['id' => 55]); + + $view = $this->createMock(LocationValueView::class); + $view->method('getLocation')->willReturn($location); + + $this->locationTagger + ->expects(self::once()) + ->method('tag') + ->with($location); + + $this->tagger->tag($view); + } +} diff --git a/tests/lib/ResponseTagger/Value/ContentInfoTaggerTest.php b/tests/lib/ResponseTagger/Value/ContentInfoTaggerTest.php new file mode 100644 index 0000000..61ec151 --- /dev/null +++ b/tests/lib/ResponseTagger/Value/ContentInfoTaggerTest.php @@ -0,0 +1,99 @@ +responseTagger = $this->createMock(ResponseTagger::class); + $this->tagger = new ContentInfoTagger($this->responseTagger); + } + + public function testSupportsContentInfo(): void + { + self::assertTrue($this->tagger->supports(new ContentInfo())); + } + + public function testDoesNotSupportNonContentInfo(): void + { + self::assertFalse($this->tagger->supports(new Location())); + } + + public function testTagsWithContentAndContentTypeId(): void + { + $value = new ContentInfo(['id' => 123, 'contentTypeId' => 987, 'mainLocationId' => null]); + + $this->responseTagger + ->expects(self::once()) + ->method('addTags') + ->with([ + ContentTagInterface::CONTENT_PREFIX . '123', + ContentTagInterface::CONTENT_TYPE_PREFIX . '987', + ]); + + $this->tagger->tag($value); + } + + public function testTagsWithLocationIdWhenMainLocationIsSet(): void + { + $value = new ContentInfo(['id' => 1, 'contentTypeId' => 2, 'mainLocationId' => 456]); + $matcher = self::exactly(2); + + $this->responseTagger + ->expects($matcher) + ->method('addTags') + ->willReturnCallback(function (array $tags) use ($matcher): ResponseTagger { + if ($matcher->getInvocationCount() === 1) { + self::assertSame( + [ + ContentTagInterface::CONTENT_PREFIX . '1', + ContentTagInterface::CONTENT_TYPE_PREFIX . '2', + ], + $tags + ); + } + + if ($matcher->getInvocationCount() === 2) { + self::assertSame( + [ + ContentTagInterface::LOCATION_PREFIX . '456', + ], + $tags + ); + } + + return $this->responseTagger; + }); + + $this->tagger->tag($value); + } + + public function testDoesNotTagLocationWhenMainLocationIsNull(): void + { + $value = new ContentInfo(['id' => 1, 'contentTypeId' => 2, 'mainLocationId' => null]); + + $this->responseTagger + ->expects(self::once()) + ->method('addTags'); + + $this->tagger->tag($value); + } +} diff --git a/tests/lib/ResponseTagger/Value/LocationTaggerTest.php b/tests/lib/ResponseTagger/Value/LocationTaggerTest.php new file mode 100644 index 0000000..63981cf --- /dev/null +++ b/tests/lib/ResponseTagger/Value/LocationTaggerTest.php @@ -0,0 +1,161 @@ +responseTagger = $this->createMock(ResponseTagger::class); + $this->tagger = new LocationTagger($this->responseTagger); + } + + public function testSupportsLocation(): void + { + self::assertTrue($this->tagger->supports(new Location())); + } + + public function testDoesNotSupportNonLocation(): void + { + self::assertFalse($this->tagger->supports(new ContentInfo())); + } + + public function testTagsWithLocationIdWhenNotMainLocation(): void + { + $location = new Location([ + 'id' => 123, + 'parentLocationId' => 2, + 'pathString' => '/1/2/123/', + 'contentInfo' => new ContentInfo(['mainLocationId' => 321]), + ]); + + $matcher = self::exactly(3); + $this->responseTagger + ->expects($matcher) + ->method('addTags') + ->willReturnCallback(function (array $tags) use ($matcher): ResponseTagger { + if ($matcher->getInvocationCount() === 1) { + self::assertSame([ContentTagInterface::LOCATION_PREFIX . '123'], $tags); + } + + if ($matcher->getInvocationCount() === 2) { + self::assertSame([ContentTagInterface::PARENT_LOCATION_PREFIX . '2'], $tags); + } + + if ($matcher->getInvocationCount() === 3) { + self::assertSame([ + ContentTagInterface::PATH_PREFIX . '1', + ContentTagInterface::PATH_PREFIX . '2', + ContentTagInterface::PATH_PREFIX . '123', + ], $tags); + } + + return $this->responseTagger; + }); + + $this->tagger->tag($location); + } + + public function testDoesNotTagLocationIdWhenItIsMainLocation(): void + { + $location = new Location([ + 'id' => 55, + 'parentLocationId' => 2, + 'pathString' => '/1/2/55/', + 'contentInfo' => new ContentInfo(['mainLocationId' => 55]), + ]); + + $matcher = self::exactly(2); + + $this->responseTagger + ->expects($matcher) + ->method('addTags') + ->willReturnCallback(function (array $tags) use ($matcher): ResponseTagger { + if ($matcher->getInvocationCount() === 1) { + self::assertSame([ContentTagInterface::PARENT_LOCATION_PREFIX . '2'], $tags); + } + + if ($matcher->getInvocationCount() === 2) { + self::assertSame([ + ContentTagInterface::PATH_PREFIX . '1', + ContentTagInterface::PATH_PREFIX . '2', + ContentTagInterface::PATH_PREFIX . '55', + ], $tags); + } + + return $this->responseTagger; + }); + + $this->tagger->tag($location); + } + + public function testTagsWithParentLocationId(): void + { + $location = new Location([ + 'id' => 4, + 'parentLocationId' => 123, + 'pathString' => '/1/123/4/', + 'contentInfo' => new ContentInfo(['mainLocationId' => null]), + ]); + + $matcher = self::atLeastOnce(); + + $this->responseTagger + ->expects($matcher) + ->method('addTags') + ->willReturnCallback(function (array $tags) use ($matcher): ResponseTagger { + if ($matcher->getInvocationCount() === 0) { + self::assertSame([ContentTagInterface::PARENT_LOCATION_PREFIX . '123'], $tags); + } + + return $this->responseTagger; + }); + + $this->tagger->tag($location); + } + + public function testTagsWithPathItems(): void + { + $location = new Location([ + 'id' => 4, + 'parentLocationId' => 2, + 'pathString' => '/1/2/123/', + 'contentInfo' => new ContentInfo(['mainLocationId' => null]), + ]); + + $matcher = self::atLeastOnce(); + $this->responseTagger + ->expects($matcher) + ->method('addTags') + ->willReturnCallback(function (array $tags) use ($matcher): ResponseTagger { + if ($matcher->getInvocationCount() === 0) { + self::assertSame([ + ContentTagInterface::PATH_PREFIX . '1', + ContentTagInterface::PATH_PREFIX . '2', + ContentTagInterface::PATH_PREFIX . '123', + ], $tags); + } + + return $this->responseTagger; + }); + + $this->tagger->tag($location); + } +}