From 49eb7536f9c19165fd6a29c37ff40a4a75315773 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Tue, 29 Sep 2026 17:08:54 +0200 Subject: [PATCH] feat(transformer) #236 TransformerTrait: document that any non-empty #suffix is accepted on transformer codes (callback#1, callback#reverse), and anchor the matching regex (an empty suffix is no longer accepted). Update documentation, add tests. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + docs/reference/traits/transformer_trait.md | 7 +- src/Transformer/TransformerTrait.php | 7 +- tests/Transformer/TransformerTraitTest.php | 110 +++++++++++++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 tests/Transformer/TransformerTraitTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c59e7136..9f9b1da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Latest * [#190](https://github.com/cleverage/process-bundle/issues/190) Update quality stack: use Rector `withComposerBased()` sets (removed `SYMFONY_64` / `PHPUNIT_100` sets), declare used Symfony packages and PHPUnit range in composer.json, apply quality tools fixes * [#145](https://github.com/cleverage/process-bundle/issues/145) Add missing documentations: reference pages for every Task & Transformer, ConditionTrait & GenericTransformer, complete guides and cookbooks. Harmonize and fix existing documentation. * [#229](https://github.com/cleverage/process-bundle/issues/229) StatCounterTask and AdvancedStatCounterTask pass their input to their output on every execution (they used to output `null`, and AdvancedStatCounterTask was skipped when not logging), so they can be placed anywhere in a branch. Update documentation, add tests. +* [#236](https://github.com/cleverage/process-bundle/issues/236) TransformerTrait: document that any non-empty `#suffix` is accepted on transformer codes (`callback#1`, `callback#reverse`), and anchor the matching regex (an empty suffix is no longer accepted). Update documentation, add tests. ## Fixes * [#192](https://github.com/cleverage/process-bundle/issues/192) Fix CommandRunnerTask: only pass the `options` option to `Process::setOptions()`, support string `commandline` through `Process::fromShellCommandline()`, validate option types. Update documentation, add tests. diff --git a/docs/reference/traits/transformer_trait.md b/docs/reference/traits/transformer_trait.md index 2bb02ebc..f488a848 100644 --- a/docs/reference/traits/transformer_trait.md +++ b/docs/reference/traits/transformer_trait.md @@ -16,14 +16,15 @@ Allow to hold a list of sub-transformers, configure their options at initializat - any error thrown by a transformer is wrapped in a `TransformerException` ("Transformation '' have failed: "), the original exception being available as previous exception - as YAML keys must be unique, a suffix starting with `#` can be added to the code to use the same transformer - several times. The convention is `#` followed by digits; the part before the first `#` is used as the transformer - code if it is registered. Example: + several times. Any non-empty suffix is accepted: digits (`callback#1`) or a name describing the step + (`callback#reverse`). The part before the first `#` is used as the transformer code if it is registered (otherwise + the whole code is looked up, and a `MissingTransformerException` is thrown if it is unknown). Example: ```yaml transformers: callback#1: callback: array_filter - callback#2: + callback#reverse: callback: array_reverse ``` diff --git a/src/Transformer/TransformerTrait.php b/src/Transformer/TransformerTrait.php index c63c1d17..e43563e6 100644 --- a/src/Transformer/TransformerTrait.php +++ b/src/Transformer/TransformerTrait.php @@ -72,16 +72,19 @@ protected function applyTransformers(array $transformers, mixed $value): mixed * keys This way you can chain multiple times the same transformer. Without this, it would silently call only the * 1st one. * + * Any non-empty suffix starting with "#" is accepted (digits, or a name describing the step): the part before the + * first "#" is used as the transformer code if it is registered. + * * @example * transformers: * callback#1: * callback: array_filter - * callback#2: + * callback#reverse: * callback: array_reverse */ protected function getCleanedTransfomerCode(string $transformerCode): string { - $match = preg_match('/([^#]+)(#[\d]+)?/', $transformerCode, $parts); + $match = preg_match('/^([^#]+)#.+$/', $transformerCode, $parts); if (1 === $match && $this->transformerRegistry->hasTransformer($parts[1])) { return $parts[1]; diff --git a/tests/Transformer/TransformerTraitTest.php b/tests/Transformer/TransformerTraitTest.php new file mode 100644 index 00000000..bfe6f572 --- /dev/null +++ b/tests/Transformer/TransformerTraitTest.php @@ -0,0 +1,110 @@ + + */ + public static function transformerCodeProvider(): iterable + { + yield 'no suffix' => ['callback', 'callback']; + yield 'numeric suffix' => ['callback#1', 'callback']; + yield 'named suffix' => ['callback#reverse', 'callback']; + yield 'suffix containing #' => ['callback#a#b', 'callback']; + yield 'unknown transformer' => ['unknown#1', 'unknown#1']; + yield 'empty suffix' => ['callback#', 'callback#']; + yield 'suffix only' => ['#1', '#1']; + } + + #[DataProvider('transformerCodeProvider')] + public function testCleanedTransformerCode(string $code, string $expected): void + { + self::assertSame($expected, $this->createHolder()->cleanCode($code)); + } + + public function testSameTransformerCanBeChainedWithSuffixes(): void + { + $holder = $this->createHolder(); + + $transformers = $holder->resolve([ + 'callback#upper' => ['callback' => 'strtoupper'], + 'callback#reverse' => ['callback' => 'strrev'], + 'callback#1' => ['callback' => 'trim'], + ]); + + self::assertSame('CBA', $holder->apply($transformers, ' abc')); + } + + public function testEmptySuffixIsAnUnknownTransformer(): void + { + $this->expectException(MissingTransformerException::class); + + $this->createHolder()->resolve(['callback#' => ['callback' => 'trim']]); + } + + private function createHolder(): object + { + $registry = new TransformerRegistry(); + $registry->addTransformer(new CallbackTransformer()); + + return new class($registry) { + use TransformerTrait; + + public function __construct(TransformerRegistry $transformerRegistry) + { + $this->transformerRegistry = $transformerRegistry; + } + + public function cleanCode(string $code): string + { + return $this->getCleanedTransfomerCode($code); + } + + /** + * @param array $transformers + * + * @return array + */ + public function resolve(array $transformers): array + { + $resolver = new OptionsResolver(); + $this->configureTransformersOptions($resolver); + + return $resolver->resolve(['transformers' => $transformers])['transformers']; + } + + /** + * @param array $transformers + */ + public function apply(array $transformers, mixed $value): mixed + { + return $this->applyTransformers($transformers, $value); + } + }; + } +}