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 @@ -8,6 +8,7 @@ Latest
* [#483](https://github.com/cleverage/process-bundle/issues/483) Documentation: an exception thrown by `IterableTaskInterface::next()` makes the process fail right away with this exception, the `error_strategy` and the `error_outputs` of the task do not apply.

## Fixes
* [#475](https://github.com/cleverage/process-bundle/issues/475) Generic transformers: restore, deprecated, the former names (with underscores instead of dashes) of their transformer codes and contextual options, which no longer worked since 5.9 ([#438](https://github.com/cleverage/process-bundle/issues/438)): a transformer code with a dash that is only registered with underscores (`array-first` for `array_first`), and a contextual option with a dash given or used by its former name (`the_prefix`, `{{ the_prefix }}` for `the-prefix`). Update documentation, add tests.
* [#491](https://github.com/cleverage/process-bundle/issues/491) `CounterTask`: `flush_every: 0` is rejected when the options are resolved, with a clear message, instead of failing on the first execution with `Modulo by zero`; a negative `flush_every`, which works as its absolute value, is deprecated. Update documentation, add tests.
* [#477](https://github.com/cleverage/process-bundle/issues/477) "Stop the process on the first invalid item" (`error_strategy: skip` and an error branch ending on a `StopTask`) ends as stopped when the failing task is finalizable (`csv_reader`, `xml_stream_reader`, `stat_counter`...): the error of the skipped item, kept by its state, was taken for a finalization failure and the process ended as failed. Add tests.
* [#473](https://github.com/cleverage/process-bundle/issues/473) A task failing with the `stop` error strategy makes the process fail even when its error branch stops the flow (`StopTask`, `ProcessState::stop()`): the failure was swallowed, the process ended as succeeded or stopped (`cleverage_process.end` event, exit code 0) and a parent process went on with a `null` output. Update documentation, add tests.
Expand Down
10 changes: 10 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ A negative `flush_every` option of the `counter` task works as its absolute valu
and will be rejected in 6.0: use a positive integer. `flush_every: 0`, which failed on the first execution (`Modulo by
zero`), is now rejected when the options are resolved.

### Dashes in the generic transformers

Since 5.9, the codes of the transformers and of the contextual options of a generic transformer keep their dashes, as
written. Before 5.9, they were replaced by underscores (unless the code also had an underscore), and only their former
name worked: `array-first` was found as the `array_first` transformer, and a `the-prefix` contextual option was given
and used as `the_prefix` / `{{ the_prefix }}`. These former names work again but are deprecated since 5.10 (`Since
cleverage/process-bundle 5.10: The transformer "array-first" of the generic transformer...`, `Using the contextual option
"the-prefix" of the generic transformer...`), and will not work in 6.0: write the code of the transformer as registered
(`array_first`), and the contextual option as defined in `contextual_options` (`the-prefix`, `{{ the-prefix }}`).

## v5.8

### Unknown task references
Expand Down
5 changes: 4 additions & 1 deletion docs/reference/03-generic_transformers_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,7 @@ clever_age_process:

As any transformer code, generic transformer codes must be unique: use a prefix to avoid conflicts. A dash is kept in
the code (it was replaced by an underscore before 5.7, the former name is deprecated), as in the codes of the
transformers and of the contextual options of a generic transformer.
transformers and of the contextual options of a generic transformer (replaced by an underscore before 5.9, unless the
code also had an underscore: `array-first` was found as the `array_first` transformer, and the `the-prefix` contextual
option was given and used as `the_prefix` / `{{ the_prefix }}`; these former names still work but are deprecated since
5.10, write the code of the transformer and of the contextual option as registered).
4 changes: 3 additions & 1 deletion src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,16 @@ protected function appendTransformerConfigDefinition(NodeBuilder $definition): v
->info('Options of the transformer (code: definition with required, default, default_is_null), injected in the "transformers" with "{{ code }}"')
->example(['delimiter' => ['default' => ',']])
// Keep the dashes of the option codes, as written in the options given to the transformer and in "{{ code }}"
// (their former name, with underscores, is deprecated, see GenericTransformer)
->normalizeKeys(false)
->variablePrototype()
->end()
->end()
->arrayNode('transformers')
->info('Transformers applied in sequence, by code, with their options')
->example(['explode' => ['delimiter' => '{{ separator }}']])
// Keep the dashes of the transformer codes (the former name of a generic transformer is deprecated)
// Keep the dashes of the transformer codes (the former name of a generic transformer is deprecated, as the
// former name of a transformer code that is only registered with underscores, see GenericTransformer)
->normalizeKeys(false)
->variablePrototype()
->end()
Expand Down
178 changes: 177 additions & 1 deletion src/Transformer/GenericTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

namespace CleverAge\ProcessBundle\Transformer;

use CleverAge\ProcessBundle\Configuration\DeprecatedCodeAliases;
use CleverAge\ProcessBundle\Context\ContextualOptionResolver;
use CleverAge\ProcessBundle\Exception\InvalidArgumentException;
use CleverAge\ProcessBundle\Exception\LogicException;
use CleverAge\ProcessBundle\Registry\TransformerRegistry;
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand All @@ -41,6 +43,15 @@ class GenericTransformer implements ConfigurableTransformerInterface
*/
protected ?array $contextualOptions = null;

/**
* Former name (with underscores instead of dashes, before 5.9) of the contextual options whose code has a dash.
*
* Deprecated since 5.10, the former names will not be accepted in v6.0.
*
* @var array<string, string> former name => code
*/
private array $deprecatedContextualOptions = [];

public function __construct(
protected ContextualOptionResolver $contextualOptionResolver,
TransformerRegistry $transformerRegistry,
Expand All @@ -61,7 +72,13 @@ public function initialize(string $code, array $options = []): void

$initialOptions = $resolver->resolve($options);
$this->contextualOptions = $initialOptions['contextual_options'];
$this->preconfiguredTransformerOptions = $initialOptions['transformers'];
$this->deprecatedContextualOptions = DeprecatedCodeAliases::create(array_keys($this->contextualOptions));
$transformers = $initialOptions['transformers'];
if (\is_array($transformers)) {
$transformers = $this->replaceFormerTransformerCodes($transformers);
$this->checkFormerContextualOptionPlaceholders($transformers);
}
$this->preconfiguredTransformerOptions = $transformers;
}

/**
Expand Down Expand Up @@ -91,6 +108,11 @@ public function configureInitialOptions(OptionsResolver $resolver): void
public function configureOptions(OptionsResolver $resolver): void
{
foreach ($this->contextualOptions ?? [] as $option => $optionConfig) {
$formerName = array_search($option, $this->deprecatedContextualOptions, true);
if (false !== $formerName) {
$this->configureFormerContextualOption($resolver, $option, $formerName, $optionConfig);
continue;
}
$resolver->setDefined($option);
if (null !== $optionConfig['default'] || $optionConfig['default_is_null']) {
$resolver->setDefault($option, $optionConfig['default']);
Expand Down Expand Up @@ -148,6 +170,10 @@ public function normalizeTransformerOptions(Options $options, array $transformer
? $options[$contextualOption]
: null;
}
// The former name of a contextual option whose code has a dash, in "{{ former_name }}" (deprecated)
foreach ($this->deprecatedContextualOptions as $formerName => $contextualOption) {
$contextualizedOptionValues[$formerName] = $contextualizedOptionValues[$contextualOption] ?? null;
}

return $this->contextualOptionResolver->contextualizeOptions($transformerOptions, $contextualizedOptionValues);
}
Expand All @@ -165,4 +191,154 @@ public function configureContextualOptions(OptionsResolver $resolver): void
$resolver->setDefault('default_is_null', false);
$resolver->setAllowedTypes('default_is_null', 'bool');
}

/**
* Before 5.9, the dashes of the transformer codes of a generic transformer were replaced by underscores (unless the
* code also had an underscore): "array-first" was found as "array_first". Such a code, when it is not registered
* but its former name is, is replaced by its former name (deprecated).
*
* Deprecated since 5.10, will be removed in v6.0.
*
* @param array<int|string, mixed> $transformers
*
* @return array<int|string, mixed>
*/
private function replaceFormerTransformerCodes(array $transformers): array
{
if (array_is_list($transformers)) {
return $transformers;
}
$replaced = [];
foreach ($transformers as $key => $transformerOptions) {
$key = $this->replaceFormerTransformerCode($key, $transformers);
$replaced[$key] = $transformerOptions;
}

return $replaced;
}

/**
* @param array<int|string, mixed> $transformers
*/
private function replaceFormerTransformerCode(int|string $key, array $transformers): int|string
{
if (!\is_string($key) || !str_contains($key, '-') || str_contains($key, '_')) {
return $key;
}
$formerKey = str_replace('-', '_', $key);
// Both were written: none was renamed
if (\array_key_exists($formerKey, $transformers)) {
return $key;
}
// The code, without the "#" suffix of the key
$code = explode('#', $key, 2)[0];
$formerCode = str_replace('-', '_', $code);
$registry = $this->transformerRegistry;
if (!$registry instanceof TransformerRegistry || $registry->hasTransformer($code) || !$registry->hasTransformer($formerCode)) {
return $key;
}
trigger_deprecation(
'cleverage/process-bundle',
'5.10',
'The transformer "%s" of the generic transformer "%s" is not registered, its former name "%s", with underscores instead of dashes, is used: write "%s". The dashes of the transformer codes of a generic transformer were replaced by underscores before 5.9, the former name will not be used in v6.0.',
$code,
$this->transformerCode,
$formerCode,
$formerCode,
);

return $formerKey;
}

/**
* Before 5.9, a contextual option whose code has a dash was named with underscores, also in "{{ former_name }}".
*
* Deprecated since 5.10, will be removed in v6.0.
*
* @param array<int|string, mixed> $transformers
*/
private function checkFormerContextualOptionPlaceholders(array $transformers): void
{
if ([] === $this->deprecatedContextualOptions) {
return;
}
$formerNames = array_map(static fn (string $formerName): string => preg_quote($formerName, '/'), array_keys($this->deprecatedContextualOptions));
$pattern = \sprintf('/{{[ ]*(%s)[ ]*}}/', implode('|', $formerNames));
$used = [];
foreach (self::getStrings($transformers) as $string) {
if (preg_match_all($pattern, $string, $matches)) {
$used = [...$used, ...$matches[1]];
}
}
foreach (array_unique($used) as $formerName) {
trigger_deprecation(
'cleverage/process-bundle',
'5.10',
'Using the contextual option "%s" of the generic transformer "%s" as "{{ %s }}", with underscores instead of dashes, is deprecated: write "{{ %s }}". The dashes of the contextual option codes were replaced by underscores before 5.9, the former name will not be replaced in v6.0.',
$this->deprecatedContextualOptions[$formerName],
$this->transformerCode,
$formerName,
$this->deprecatedContextualOptions[$formerName],
);
}
}

/**
* A contextual option whose code has a dash can also be given by its former name, with underscores (deprecated).
*
* Deprecated since 5.10, will be removed in v6.0.
*
* @param array{required: bool, default: mixed, default_is_null: bool} $optionConfig
*/
private function configureFormerContextualOption(OptionsResolver $resolver, string $option, string $formerName, array $optionConfig): void
{
$resolver->setDefined($formerName);
$transformerCode = $this->transformerCode;
// Given by its code, by its former name, or the default value
$resolver->setDefault($option, static function (Options $options) use ($option, $formerName, $optionConfig, $transformerCode): mixed {
if ($options->offsetExists($formerName)) {
trigger_deprecation(
'cleverage/process-bundle',
'5.10',
'Using the contextual option "%s" of the generic transformer "%s" by the name "%s", with underscores instead of dashes, is deprecated: use "%s". The dashes of the contextual option codes were replaced by underscores before 5.9, the former name will not be accepted in v6.0.',
$option,
$transformerCode,
$formerName,
$option,
);

return $options[$formerName];
}
if (null !== $optionConfig['default'] || $optionConfig['default_is_null']) {
return $optionConfig['default'];
}
if ($optionConfig['required']) {
throw new MissingOptionsException(\sprintf('The required option "%s" is missing.', $option));
}

return null;
});
}

/**
* The strings of the transformer options, keys and values, where "{{ code }}" is replaced.
*
* @param array<int|string, mixed> $values
*
* @return list<string>
*/
private static function getStrings(array $values): array
{
$strings = [];
foreach ($values as $key => $value) {
$strings[] = (string) $key;
if (\is_array($value)) {
$strings = [...$strings, ...self::getStrings($value)];
} elseif (\is_string($value)) {
$strings[] = $value;
}
}

return $strings;
}
}
Loading
Loading