Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/03-generic_transformers_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/transformers/generic_transformer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
6 changes: 5 additions & 1 deletion src/Transformer/GenericTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
Expand Down Expand Up @@ -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);
Expand Down
110 changes: 110 additions & 0 deletions tests/Transformer/GenericTransformerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

/*
* This file is part of the CleverAge/ProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\ProcessBundle\Tests\Transformer;

use CleverAge\ProcessBundle\Context\ContextualOptionResolver;
use CleverAge\ProcessBundle\Registry\TransformerRegistry;
use CleverAge\ProcessBundle\Transformer\CallbackTransformer;
use CleverAge\ProcessBundle\Transformer\GenericTransformer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;

#[\PHPUnit\Framework\Attributes\CoversClass(GenericTransformer::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(ContextualOptionResolver::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(TransformerRegistry::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(CallbackTransformer::class)]
class GenericTransformerTest extends TestCase
{
public function testRequiredOptionMustBeProvided(): void
{
$transformer = $this->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<string, array<string, mixed>> $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<string, mixed> $options
*
* @return array<string, mixed>
*/
private function resolveOptions(GenericTransformer $transformer, array $options): array
{
$resolver = new OptionsResolver();
$transformer->configureOptions($resolver);

return $resolver->resolve($options);
}
}
Loading