diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba97445..1d7d467a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Latest * [#189](https://github.com/cleverage/process-bundle/issues/189) Fix EventDispatcherTask: dispatch the event under the `event_name` option (regression since v4.0). `event_name` is now optional: when `null` (default), the event is dispatched under its class name. Update documentation, add tests. * [#198](https://github.com/cleverage/process-bundle/issues/198) Fix DateParserTransformer: a `\DateTimeImmutable` input is converted to a `\DateTime` instead of throwing a `TypeError`. Update documentation, add tests. * [#199](https://github.com/cleverage/process-bundle/issues/199) Fix PregFilterTransformer: an array `replacement` is no longer cast to the string `"Array"`, and requires an array `pattern`. Update documentation, add tests. +* [#200](https://github.com/cleverage/process-bundle/issues/200) Fix GenericTransformer: a contextual option declared with `required: false` and no default can now be used; its placeholders are replaced by `null` when omitted. Update documentation, add tests. ## Deprecated * [#189](https://github.com/cleverage/process-bundle/issues/189) EventDispatcherTask: when `event_name` is set, listening to `CleverAge\ProcessBundle\Event\EventDispatcherTaskEvent` is deprecated (the event is still dispatched under its class name, with an `E_USER_DEPRECATED` error, if it has listeners). Listen to the configured `event_name` instead: the BC layer will be removed in v6.0. diff --git a/docs/reference/03-generic_transformers_definition.md b/docs/reference/03-generic_transformers_definition.md index 1883a819..013a8f0d 100644 --- a/docs/reference/03-generic_transformers_definition.md +++ b/docs/reference/03-generic_transformers_definition.md @@ -33,7 +33,8 @@ For each contextual option, you can define | `default_is_null` | `bool` | | `false` | If you need `null` to be the default value, use this option | Note that an option with a default value is still required by default, which has no effect since the default is used: -set `required: false` for an option without default value that may be omitted. +set `required: false` for an option without default value that may be omitted: when omitted, its placeholders are +replaced by `null`. The `transformers` list uses the same syntax as any other transformer using a sub-list of transformers (see [TransformerTrait](traits/transformer_trait.md)). diff --git a/docs/reference/transformers/generic_transformer.md b/docs/reference/transformers/generic_transformer.md index 135967d7..a8b9ec0a 100644 --- a/docs/reference/transformers/generic_transformer.md +++ b/docs/reference/transformers/generic_transformer.md @@ -102,5 +102,5 @@ Notes ----- * Contextual options are transformer options, unrelated to the process context (`-c key:value`). -* An option declared with `required: false` and no default is neither required nor defined, so it cannot be used: - always give such an option a `default` or `default_is_null: true`. +* An option declared with `required: false` and no default may be omitted: its placeholders are then replaced by + `null`. diff --git a/src/Transformer/GenericTransformer.php b/src/Transformer/GenericTransformer.php index f1863aa4..bf53d955 100644 --- a/src/Transformer/GenericTransformer.php +++ b/src/Transformer/GenericTransformer.php @@ -79,6 +79,7 @@ public function configureInitialOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void { foreach ($this->contextualOptions as $option => $optionConfig) { + $resolver->setDefined($option); if (null !== $optionConfig['default'] || $optionConfig['default_is_null']) { $resolver->setDefault($option, $optionConfig['default']); } @@ -118,7 +119,10 @@ public function normalizeTransformerOptions(Options $options, array $transformer { $contextualizedOptionValues = []; foreach ($this->contextualOptions as $contextualOption => $contextualOptionConfig) { - $contextualizedOptionValues[$contextualOption] = $options[$contextualOption]; + // An optional contextual option without default value is replaced by null when not provided + $contextualizedOptionValues[$contextualOption] = $options->offsetExists($contextualOption) + ? $options[$contextualOption] + : null; } return $this->contextualOptionResolver->contextualizeOptions($transformerOptions, $contextualizedOptionValues); diff --git a/tests/Transformer/GenericTransformerTest.php b/tests/Transformer/GenericTransformerTest.php new file mode 100644 index 00000000..001a9183 --- /dev/null +++ b/tests/Transformer/GenericTransformerTest.php @@ -0,0 +1,110 @@ +createSubstrTransformer(['offset' => ['required' => true]]); + + $this->expectException(MissingOptionsException::class); + + $this->resolveOptions($transformer, []); + } + + public function testOptionWithDefault(): void + { + $transformer = $this->createSubstrTransformer([ + 'offset' => ['default' => 2], + 'length' => ['default_is_null' => true], + ]); + + $this->assertSame('llo', $transformer->transform('hello', $this->resolveOptions($transformer, []))); + $this->assertSame('ll', $transformer->transform('hello', $this->resolveOptions($transformer, ['length' => 2]))); + } + + public function testOptionalOptionWithoutDefaultCanBeProvided(): void + { + $transformer = $this->createSubstrTransformer([ + 'offset' => ['required' => true], + 'length' => ['required' => false], + ]); + + $options = $this->resolveOptions($transformer, ['offset' => 1, 'length' => 3]); + + $this->assertSame('ell', $transformer->transform('hello', $options)); + } + + public function testOptionalOptionWithoutDefaultIsNullWhenOmitted(): void + { + $transformer = $this->createSubstrTransformer([ + 'offset' => ['required' => true], + 'length' => ['required' => false], + ]); + + $options = $this->resolveOptions($transformer, ['offset' => 1]); + + $this->assertSame('ello', $transformer->transform('hello', $options)); + } + + /** + * Generic transformer applying substr($value, {{ offset }}, {{ length }}). + * + * @param array> $contextualOptions + */ + private function createSubstrTransformer(array $contextualOptions): GenericTransformer + { + $registry = new TransformerRegistry(); + $registry->addTransformer(new CallbackTransformer()); + + $transformer = new GenericTransformer(new ContextualOptionResolver(), $registry); + $transformer->initialize('substr', [ + 'contextual_options' => $contextualOptions, + 'transformers' => [ + 'callback' => [ + 'callback' => 'substr', + 'right_parameters' => ['{{ offset }}', '{{ length }}'], + ], + ], + ]); + + return $transformer; + } + + /** + * @param array $options + * + * @return array + */ + private function resolveOptions(GenericTransformer $transformer, array $options): array + { + $resolver = new OptionsResolver(); + $transformer->configureOptions($resolver); + + return $resolver->resolve($options); + } +}