diff --git a/README.md b/README.md index cad1c84..44a4f89 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,8 @@ return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRiskyAllowed(true) ->setRules([ - BlockStringFixer::NAME => [ - 'formatters' => [ + BlockStringFixer::NAME => BlockStringFixer::config( + [ // 1️⃣ SimpleLineFormatter // Normalizes indentation of any block not explicitly configured below @@ -186,8 +186,8 @@ return (new PhpCsFixer\Config()) ), ), - ], - ], + ] + ), ]); ``` @@ -288,10 +288,6 @@ Extending this class makes sense in two situations: 2. Or if, for whatever reason, the [`CodecInterface`] concept does not work for you and you want to write something from scratch. -### [AbstractCodecFormatter](./src/Formatter/AbstractCodecFormatter.php) - -_Deprecated in favour of [`AbstractStringFormatter`]._ - ### [AbstractStringFormatter](./src/Formatter/AbstractStringFormatter.php) This formatter base class is aware of string interpolation – it passes content through a codec before and after @@ -324,9 +320,9 @@ final class MyFormatter extends AbstractStringFormatter return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRules([ - BlockStringFixer::NAME => [ + BlockStringFixer::NAME => BlockStringFixer::config([ 'TEXT' => new MyFormatter(), - ], + ]), ]); ``` @@ -341,12 +337,12 @@ Example: return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRules([ - BlockStringFixer::NAME => [ + BlockStringFixer::NAME => BlockStringFixer::config([ 'JSON' => new ChainFormatter( new CliPipeFormatter('v1', ['cmd' => 'some-old-tool']), new SimpleLineFormatter(4, "\t"), ), - ], + ]), ]); ``` @@ -361,7 +357,7 @@ Example: return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRules([ - BlockStringFixer::NAME => [ + BlockStringFixer::NAME => BlockStringFixer::config([ 'J' => new CliPipeFormatter( // Either a version as a string, or the command to get the version (as an array). versionValueOrCommand: '1.0', @@ -372,7 +368,7 @@ return (new PhpCsFixer\Config()) // A normalizer for handling end-of-line characters. lineEndingNormalizer: null ) - ], + ]), ]); ``` @@ -393,7 +389,7 @@ Example: return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRules([ - BlockStringFixer::NAME => [ + BlockStringFixer::NAME => BlockStringFixer::config([ 'JSON' => new DockerPipeFormatter( // The docker image; might contain url, tag or even the digest. image: 'ghcr.io/jqlang/jq', @@ -408,7 +404,7 @@ return (new PhpCsFixer\Config()) // A normalizer for handling end-of-line characters. lineEndingNormalizer: null, ) - ], + ]), ]); ``` @@ -422,7 +418,7 @@ Example: return (new PhpCsFixer\Config()) ->registerCustomFixers([new BlockStringFixer()]) ->setRules([ - BlockStringFixer::NAME => [ + BlockStringFixer::NAME => BlockStringFixer::config([ 'TEXT' => new SimpleLineFormatter( // The number of spaces defining one indentation level in your project. indentSize: 4, @@ -433,7 +429,7 @@ return (new PhpCsFixer\Config()) // A normalizer for handling end-of-line characters. lineEndingNormalizer: null, ) - ], + ]), ]); ``` diff --git a/src/CacheFingerprintableInterface.php b/src/CacheFingerprintableInterface.php new file mode 100644 index 0000000..2c3b34d --- /dev/null +++ b/src/CacheFingerprintableInterface.php @@ -0,0 +1,14 @@ +} + * @phpstan-type TFormatters array<0|non-empty-string, AbstractFormatter> + * @phpstan-type TDeserializedConfig array{formatters: TFormatters} + * @phpstan-type TSerializedConfig array{formatters?: string} * - * @implements ConfigurableFixerInterface + * @implements ConfigurableFixerInterface */ final class BlockStringFixer implements FixerInterface, ConfigurableFixerInterface { @@ -28,9 +30,9 @@ final class BlockStringFixer implements FixerInterface, ConfigurableFixerInterfa private ?FixerConfigurationResolverInterface $configurationDefinition = null; /** - * @var null|TFormatterConfig + * @var TDeserializedConfig */ - private ?array $configuration = null; + private array $configuration; public function isRisky(): bool { @@ -77,16 +79,22 @@ public function getConfigurationDefinition(): FixerConfigurationResolverInterfac public function configure(array $configuration): void { + $formatters = $configuration['formatters'] ?? 'a:0:{}'; + if (($formatters = @unserialize($formatters, ['allowed_classes' => true])) === false) { + throw new InvalidArgumentException( + 'BlockStringFixer configuration is not valid. ' + . 'Did you set it up in your PHP CS Fixer config with `BlockStringFixer::config()`?' + ); + } + // @phpstan-ignore assign.propertyType - $this->configuration = $this->getConfigurationDefinition()->resolve($configuration); + $this->configuration = [ + 'formatters' => $formatters, + ]; } public function fix(SplFileInfo $file, Tokens $tokens): void { - if ($this->configuration === null) { - throw new InvalidArgumentException("Configuration for fixer {$this->getName()} is required."); - } - if (0 < $tokens->count() && $this->isCandidate($tokens) && $this->supports($file)) { $blockStringStream = TokenStream::fromPhpCsFixerTokens($tokens); while (($blockString = $blockStringStream->next()) !== null) { @@ -100,4 +108,15 @@ public function fix(SplFileInfo $file, Tokens $tokens): void } } } + + /** + * @param TFormatters $formatters + * @return TSerializedConfig + */ + public static function config(array $formatters): array + { + return [ + 'formatters' => serialize($formatters), + ]; + } } diff --git a/src/Formatter/AbstractCodecFormatter.php b/src/Formatter/AbstractCodecFormatter.php deleted file mode 100644 index 0daeb86..0000000 --- a/src/Formatter/AbstractCodecFormatter.php +++ /dev/null @@ -1,12 +0,0 @@ -version = $version; + $this->cacheFingerprint = $cacheFingerprint; } /** * Format the provided BlockString accordingly and return a new one. */ abstract public function formatBlock(BlockString $blockString): BlockString; + + /** + * @return mixed + */ + final public function getCacheFingerprint() + { + return $this->cacheFingerprint; + } } diff --git a/src/Formatter/AbstractStringFormatter.php b/src/Formatter/AbstractStringFormatter.php index 6bb02fc..91a08ee 100644 --- a/src/Formatter/AbstractStringFormatter.php +++ b/src/Formatter/AbstractStringFormatter.php @@ -39,9 +39,9 @@ * return (new PhpCsFixer\Config()) * ->registerCustomFixers([new BlockStringFixer()]) * ->setRules([ - * BlockStringFixer::NAME => [ + * BlockStringFixer::NAME => BlockStringFixer::config([ * 'TEXT' => new MyFormatter(), - * ], + * ]), * ]); * ``` */ @@ -66,16 +66,23 @@ abstract class AbstractStringFormatter extends AbstractFormatter */ private NormalizerInterface $lineEndingNormalizer; + /** + * @param mixed $cacheFingerprint {@see AbstractFormatter::__construct()} + */ public function __construct( - string $version, - ?CodecInterface $interpolationCodec=null, - ?NormalizerInterface $lineEndingNormalizer=null + $cacheFingerprint, + ?CodecInterface $interpolationCodec = null, + ?NormalizerInterface $lineEndingNormalizer = null ) { - parent::__construct($version); - $this->objectIndex = self::$objectCounter++; $this->interpolationCodec = $interpolationCodec ?? new PlainStringCodec(); $this->lineEndingNormalizer = $lineEndingNormalizer ?? new DefaultNormalizer(DefaultNormalizer::NO_CHANGE, DefaultNormalizer::NO_CHANGE); + + parent::__construct([ + $cacheFingerprint, + $this->interpolationCodec->getCacheFingerprint(), + $this->lineEndingNormalizer->getCacheFingerprint(), + ]); } final public function formatBlock(BlockString $blockString): BlockString diff --git a/src/Formatter/ChainFormatter.php b/src/Formatter/ChainFormatter.php index d44e80d..8292c95 100644 --- a/src/Formatter/ChainFormatter.php +++ b/src/Formatter/ChainFormatter.php @@ -14,12 +14,12 @@ * return (new PhpCsFixer\Config()) * ->registerCustomFixers([new BlockStringFixer()]) * ->setRules([ - * BlockStringFixer::NAME => [ + * BlockStringFixer::NAME => BlockStringFixer::config([ * 'JSON' => new ChainFormatter( * new CliPipeFormatter('v1', ['cmd' => 'some-old-tool']), * new SimpleLineFormatter(4, "\t"), * ), - * ], + * ]), * ]); * ``` */ @@ -35,9 +35,12 @@ final class ChainFormatter extends AbstractFormatter */ public function __construct(AbstractFormatter ...$formatters) { - parent::__construct('1.0'); - $this->formatters = array_values($formatters); + + parent::__construct([ + self::class, + array_map(static fn(AbstractFormatter $formatter) => $formatter->getCacheFingerprint(), $this->formatters), + ]); } public function formatBlock(BlockString $blockString): BlockString diff --git a/src/Formatter/CliPipeFormatter.php b/src/Formatter/CliPipeFormatter.php index 773ff3c..f26d9db 100644 --- a/src/Formatter/CliPipeFormatter.php +++ b/src/Formatter/CliPipeFormatter.php @@ -17,7 +17,7 @@ * return (new PhpCsFixer\Config()) * ->registerCustomFixers([new BlockStringFixer()]) * ->setRules([ - * BlockStringFixer::NAME => [ + * BlockStringFixer::NAME => BlockStringFixer::config([ * 'J' => new CliPipeFormatter( * // Either a version as a string, or the command to get the version (as an array). * versionValueOrCommand: '1.0', @@ -28,7 +28,7 @@ * // A normalizer for handling end-of-line characters. * lineEndingNormalizer: null * ) - * ], + * ]), * ]); * ``` * @@ -77,9 +77,16 @@ public function __construct( } parent::__construct( - is_string($versionValueOrCommand) - ? $versionValueOrCommand - : $this->exec($versionValueOrCommand, null), + sprintf( + '%s: %s v%s', + static::class, + is_array($this->formatter['cmd']) + ? implode(' ', $this->formatter['cmd']) + : $this->formatter['cmd'], + is_string($versionValueOrCommand) + ? $versionValueOrCommand + : $this->exec($versionValueOrCommand, null) + ), $interpolationCodec, $lineEndingNormalizer ); diff --git a/src/Formatter/DockerPipeFormatter.php b/src/Formatter/DockerPipeFormatter.php index 5b70f64..081fc7f 100644 --- a/src/Formatter/DockerPipeFormatter.php +++ b/src/Formatter/DockerPipeFormatter.php @@ -20,7 +20,7 @@ * return (new PhpCsFixer\Config()) * ->registerCustomFixers([new BlockStringFixer()]) * ->setRules([ - * BlockStringFixer::NAME => [ + * BlockStringFixer::NAME => BlockStringFixer::config([ * 'JSON' => new DockerPipeFormatter( * // The docker image; might contain url, tag or even the digest. * image: 'ghcr.io/jqlang/jq', @@ -35,7 +35,7 @@ * // A normalizer for handling end-of-line characters. * lineEndingNormalizer: null, * ) - * ], + * ]), * ]); * ``` * @@ -106,7 +106,18 @@ public function __construct( } parent::__construct( - "{$this->imageDetails['platform']};{$this->imageDetails['digest']}", + sprintf( + '%s: %s', + static::class, + implode( + ' ', + array_merge( + ["{$this->imageDetails['platform']};{$this->imageDetails['digest']}"], + $this->options, + $this->command + ) + ) + ), $interpolationCodec, $lineEndingNormalizer ); diff --git a/src/Formatter/SimpleLineFormatter.php b/src/Formatter/SimpleLineFormatter.php index f58378b..f7fa093 100644 --- a/src/Formatter/SimpleLineFormatter.php +++ b/src/Formatter/SimpleLineFormatter.php @@ -15,7 +15,7 @@ * return (new PhpCsFixer\Config()) * ->registerCustomFixers([new BlockStringFixer()]) * ->setRules([ - * BlockStringFixer::NAME => [ + * BlockStringFixer::NAME => BlockStringFixer::config([ * 'TEXT' => new SimpleLineFormatter( * // The number of spaces defining one indentation level in your project. * indentSize: 4, @@ -26,7 +26,7 @@ * // A normalizer for handling end-of-line characters. * lineEndingNormalizer: null, * ) - * ], + * ]), * ]); * ``` */ @@ -71,7 +71,7 @@ public function __construct( ); } - parent::__construct('1', $interpolationCodec, $lineEndingNormalizer); + parent::__construct(static::class . ' v1', $interpolationCodec, $lineEndingNormalizer); } protected function formatContent(string $original): string diff --git a/src/InterpolationCodec/CodecInterface.php b/src/InterpolationCodec/CodecInterface.php index bd47191..6e65c77 100644 --- a/src/InterpolationCodec/CodecInterface.php +++ b/src/InterpolationCodec/CodecInterface.php @@ -3,8 +3,9 @@ namespace uuf6429\PhpCsFixerBlockstring\InterpolationCodec; use uuf6429\PhpCsFixerBlockstring\BlockString\SegmentInterface; +use uuf6429\PhpCsFixerBlockstring\CacheFingerprintableInterface; -interface CodecInterface +interface CodecInterface extends CacheFingerprintableInterface { /** * @param list $segments diff --git a/src/InterpolationCodec/GeneratedTokenCodec.php b/src/InterpolationCodec/GeneratedTokenCodec.php index 1a05f1f..68d64ae 100644 --- a/src/InterpolationCodec/GeneratedTokenCodec.php +++ b/src/InterpolationCodec/GeneratedTokenCodec.php @@ -3,8 +3,10 @@ namespace uuf6429\PhpCsFixerBlockstring\InterpolationCodec; use LogicException; +use RuntimeException; use uuf6429\PhpCsFixerBlockstring\BlockString\InterpolationSegment; use uuf6429\PhpCsFixerBlockstring\BlockString\StringSegment; +use uuf6429\PhpCsFixerBlockstring\CacheFingerprintableInterface; final class GeneratedTokenCodec implements CodecInterface { @@ -107,4 +109,28 @@ public function decode(CodecResult $result): array return $segments; } + + public function getCacheFingerprint() + { + return [self::class, $this->tokenPattern, $this->getTokenFactoryCacheFingerprint()]; + } + + /** + * @return mixed + */ + private function getTokenFactoryCacheFingerprint() + { + if (!is_object($this->tokenFactory)) { + return $this->tokenFactory; + } + + if ($this->tokenFactory instanceof CacheFingerprintableInterface) { + return $this->tokenFactory->getCacheFingerprint(); + } + + throw new RuntimeException( + 'Token factory must implement CacheFingerprintableInterface - it cannot be a simple closure object.' + . ' A possibility is to make an anonymous object implementing that interface and an __invoke method.' + ); + } } diff --git a/src/InterpolationCodec/PlainStringCodec.php b/src/InterpolationCodec/PlainStringCodec.php index 1a43daa..3ba40b7 100644 --- a/src/InterpolationCodec/PlainStringCodec.php +++ b/src/InterpolationCodec/PlainStringCodec.php @@ -20,4 +20,9 @@ public function decode(CodecResult $result): array { return [new StringSegment($result->content)]; } + + public function getCacheFingerprint() + { + return [self::class]; + } } diff --git a/src/LineEndingNormalizer/DefaultNormalizer.php b/src/LineEndingNormalizer/DefaultNormalizer.php index 597d8d3..9cf0036 100644 --- a/src/LineEndingNormalizer/DefaultNormalizer.php +++ b/src/LineEndingNormalizer/DefaultNormalizer.php @@ -43,6 +43,11 @@ public function normalize(string $formatted, string $original): string ); } + public function getCacheFingerprint() + { + return [static::class, $this->changeLinesTo, $this->changeFinalLineTo]; + } + private function normalizeLineEnding(string $text, string $original): string { switch ($this->changeLinesTo) { diff --git a/src/LineEndingNormalizer/NormalizerInterface.php b/src/LineEndingNormalizer/NormalizerInterface.php index 7a5fb11..5cbbb49 100644 --- a/src/LineEndingNormalizer/NormalizerInterface.php +++ b/src/LineEndingNormalizer/NormalizerInterface.php @@ -2,7 +2,9 @@ namespace uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer; -interface NormalizerInterface +use uuf6429\PhpCsFixerBlockstring\CacheFingerprintableInterface; + +interface NormalizerInterface extends CacheFingerprintableInterface { public function normalize(string $formatted, string $original): string; } diff --git a/tests/Fixtures/Formatters/JsonCommenter.php b/tests/Fixtures/Formatters/JsonCommenter.php new file mode 100644 index 0000000..7b6b46c --- /dev/null +++ b/tests/Fixtures/Formatters/JsonCommenter.php @@ -0,0 +1,31 @@ +comment = $comment; + } + + public function formatContent(string $original): string + { + return json_encode( + array_merge( + (array)json_decode($original, false, 512, JSON_THROW_ON_ERROR), + ['_comment' => $this->comment] + ), + JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT + ); + } +} diff --git a/tests/Fixtures/Scenarios/caching/config-v1.php b/tests/Fixtures/Scenarios/caching/config-v1.php new file mode 100644 index 0000000..c55e623 --- /dev/null +++ b/tests/Fixtures/Scenarios/caching/config-v1.php @@ -0,0 +1,15 @@ +registerCustomFixers([new BlockStringFixer()]) + ->setRiskyAllowed(true) + ->setRules([ + BlockStringFixer::NAME => BlockStringFixer::config( + [ + 'JSON' => new JsonCommenter('JsonCommenter v1'), + ] + ) + ]); diff --git a/tests/Fixtures/Scenarios/caching/config-v2.php b/tests/Fixtures/Scenarios/caching/config-v2.php new file mode 100644 index 0000000..e3d604e --- /dev/null +++ b/tests/Fixtures/Scenarios/caching/config-v2.php @@ -0,0 +1,15 @@ +registerCustomFixers([new BlockStringFixer()]) + ->setRiskyAllowed(true) + ->setRules([ + BlockStringFixer::NAME => BlockStringFixer::config( + [ + 'JSON' => new JsonCommenter('JsonCommenter v2'), + ] + ) + ]); diff --git a/tests/Fixtures/Scenarios/caching/input.php b/tests/Fixtures/Scenarios/caching/input.php new file mode 100644 index 0000000..64f74cd --- /dev/null +++ b/tests/Fixtures/Scenarios/caching/input.php @@ -0,0 +1,10 @@ +registerCustomFixers([new BlockStringFixer()]) ->setRiskyAllowed(true) ->setRules([ - BlockStringFixer::NAME => [ - 'formatters' => [ + BlockStringFixer::NAME => BlockStringFixer::config( + [ // 1️⃣ SimpleLineFormatter // Normalizes indentation of any block not explicitly configured below @@ -42,6 +42,6 @@ ), ), - ], - ], + ] + ), ]); diff --git a/tests/Integration/CacheTest.php b/tests/Integration/CacheTest.php new file mode 100644 index 0000000..d8b4ea0 --- /dev/null +++ b/tests/Integration/CacheTest.php @@ -0,0 +1,81 @@ + 1, + ] + ); + + $process->mustRun(); + $output = $process->getErrorOutput() . $process->getOutput(); + + $this->assertSame($expectedCacheFileExistence, $cacheFileExistence); + $this->assertFileEquals(__DIR__ . "/../Fixtures/Scenarios/caching/output-{$formatterVersion}.php", self::$inputFile); + $this->assertStringContainsString($expectedProcessOutput, $output); + $this->assertFileExists(self::$cacheFile); + $this->assertStringContainsString("JsonCommenter (JsonCommenter {$formatterVersion})", (string)file_get_contents(self::$cacheFile)); + } +} diff --git a/tests/Unit/Fixer/BlockStringFixerTest.php b/tests/Unit/Fixer/BlockStringFixerTest.php index 8ed7291..cc9a81b 100644 --- a/tests/Unit/Fixer/BlockStringFixerTest.php +++ b/tests/Unit/Fixer/BlockStringFixerTest.php @@ -2,6 +2,7 @@ namespace uuf6429\PhpCsFixerBlockstringTests\Unit\Fixer; +use InvalidArgumentException; use PhpCsFixer\Tokenizer\Tokens; use PHPUnit\Framework\TestCase; use SplFileInfo; @@ -12,7 +13,7 @@ /** * @internal * - * @phpstan-import-type TFormatterConfig from BlockStringFixer + * @phpstan-import-type TSerializedConfig from BlockStringFixer */ final class BlockStringFixerTest extends TestCase { @@ -45,8 +46,20 @@ public function testGetConfigurationDefinition(): void (new BlockStringFixer())->getConfigurationDefinition(); } + public function testConfigureWithInvalidConfiguration(): void + { + $this->expectExceptionObject(new InvalidArgumentException('BlockStringFixer configuration is not valid.')); + + (new BlockStringFixer())->configure(['formatters' => 'invalid']); + } + + public function testConfig(): void + { + $this->assertSame(['formatters' => 'a:0:{}'], BlockStringFixer::config([])); + } + /** - * @param TFormatterConfig $config + * @param TSerializedConfig $config * @dataProvider provideFixCases */ public function testApplyFix(array $config, string $input, string $expected): void @@ -62,12 +75,12 @@ public function testApplyFix(array $config, string $input, string $expected): vo } /** - * @return iterable + * @return iterable */ public static function provideFixCases(): iterable { yield 'nowdoc with unregistered delimiter should be left unchanged' => [ - 'config' => ['formatters' => []], + 'config' => BlockStringFixer::config([]), 'input' => <<<'PHP' [ - 'config' => [ - 'formatters' => [ + 'config' => BlockStringFixer::config( + [ 'HTML' => new fixtures\Formatters\HtmlTagStripper(null), - ], - ], + ] + ), 'input' => <<<'PHP' [ - 'config' => [ - 'formatters' => [ + 'config' => BlockStringFixer::config( + [ new fixtures\Formatters\TagWrapper('def'), 'HTML' => new fixtures\Formatters\TagWrapper('htm'), - ], - ], + ] + ), 'input' => <<<'PHP' [ - 'config' => [ - 'formatters' => [ + 'config' => BlockStringFixer::config( + [ 'HTML' => new fixtures\Formatters\HtmlTagStripper(new GeneratedTokenCodec()), - ], - ], + ] + ), 'input' => <<<'PHP' [ - 'config' => [ - 'formatters' => [ + 'config' => BlockStringFixer::config( + [ 'HTML' => new fixtures\Formatters\HtmlTagStripper(null), - ], - ], + ] + ), 'input' => "Hello world!\r\n HTML;\r\n", 'expected' => "assertSame('fingerprint', $formatter->getCacheFingerprint()); + } +} diff --git a/tests/Unit/InterpolationCodec/GeneratedTokenCodecTest.php b/tests/Unit/InterpolationCodec/GeneratedTokenCodecTest.php index 1e9253b..23a61bc 100644 --- a/tests/Unit/InterpolationCodec/GeneratedTokenCodecTest.php +++ b/tests/Unit/InterpolationCodec/GeneratedTokenCodecTest.php @@ -5,8 +5,10 @@ use LogicException; use PhpCsFixer\Tokenizer\Token; use PHPUnit\Framework\TestCase; +use RuntimeException; use uuf6429\PhpCsFixerBlockstring\BlockString\InterpolationSegment; use uuf6429\PhpCsFixerBlockstring\BlockString\StringSegment; +use uuf6429\PhpCsFixerBlockstring\CacheFingerprintableInterface; use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecResult; use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\GeneratedTokenCodec; @@ -67,4 +69,38 @@ public function testThatDefaultBehaviourTriggeredWhenTokenFactoryReturnsNull(): $result ); } + + public function testThatTokenFactoryCanBeSimpleCallable(): void + { + /** @phpstan-ignore argument.type */ + $codec = new GeneratedTokenCodec('', 'array_map'); + + $this->assertSame([GeneratedTokenCodec::class, '', 'array_map'], $codec->getCacheFingerprint()); + } + + public function testThatTokenFactoryCannotBeBeSimpleClosure(): void + { + $codec = new GeneratedTokenCodec('', static fn() => null); + + $this->expectExceptionObject(new RuntimeException('Token factory must implement CacheFingerprintableInterface')); + + $codec->getCacheFingerprint(); + } + + public function testThatTokenFactoryCanBeCachableInvokable(): void + { + $codec = new GeneratedTokenCodec('', new class implements CacheFingerprintableInterface { + public function __invoke(): string + { + return 'xx'; + } + + public function getCacheFingerprint() + { + return 'fingerprint'; + } + }); + + $this->assertSame([GeneratedTokenCodec::class, '', 'fingerprint'], $codec->getCacheFingerprint()); + } }