From acdc5ee05d4fee434b61bd74c7dc99730cf21f3c Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Tue, 20 Oct 2026 14:49:28 +0200 Subject: [PATCH] #310 - Configuration: deprecate an unknown task error_strategy, boolean public, describe the nodes - A task error_strategy other than skip or stop is deprecated: notice when the container is compiled (CleverAgeProcessExtension), it will be rejected in v6.0. It was accepted, and the process failed with "Unknown error strategy" when the task failed. Environment variables are not checked. - The process "public" key is a booleanNode: a non-boolean value is rejected when the container is compiled instead of a TypeError when the ProcessConfiguration is built. - ->info() / ->example() on the nodes of the process, task and generic transformer configurations (config:dump-reference clever_age_process). - Tests, documentation (process and task definition) and CHANGELOG updated. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + docs/reference/01-process_definition.md | 3 +- docs/reference/02-task_definition.md | 4 ++- .../CleverAgeProcessExtension.php | 33 +++++++++++++++++ src/DependencyInjection/Configuration.php | 30 +++++++++++++--- .../DependencyInjection/ConfigurationTest.php | 36 +++++++++++++++++++ 6 files changed, 101 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a24625..766057ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Latest ------ ## Changes +* [#310](https://github.com/cleverage/process-bundle/issues/310) Process configuration tree: a task `error_strategy` other than `skip` or `stop` is deprecated (notice when the container is compiled; it was accepted and the process failed with "Unknown error strategy" when the task failed), it will be rejected in v6.0. The process `public` key is a boolean node: a non-boolean value is rejected when the container is compiled, instead of a `TypeError` when the configuration is built. Describe the nodes of the process, task and generic transformer configurations (`config:dump-reference clever_age_process`). Update documentation, add tests. * [#305](https://github.com/cleverage/process-bundle/issues/305) Bundle best practices, residues: the generic transformers are no longer autowired (explicit arguments). `ProcessHelpCommand` receives the `TaskRegistry` instead of the service container (passing a container is deprecated). Fetching from the service container a task of the application made public by the `TaskInterface` autoconfiguration is deprecated (`container.private` tag, new internal `PublicTaskDeprecationCompilerPass`); the tasks made public by the application itself are not concerned. Trigger the missing deprecation notices: `CheckSerializerCompilerPass`, `errors` task configuration key (use `error_outputs`). `TaskConfiguration::getErrors()` triggers a single notice on PHP 8.4+. `CleverAgeProcessBundle` is marked `@final since 5.4`. Update documentation, add tests. * [#299](https://github.com/cleverage/process-bundle/issues/299) Require `symfony/polyfill-php83`, `symfony/polyfill-php84` and `symfony/polyfill-php85`: the functions of PHP 8.3 to 8.5 can be used while supporting PHP 8.2. Rector targets PHP 8.2 (`withPhpVersion()`, `withPhpSets()` read from `composer.json`) and applies the rules using a newer function when its polyfill is required: `ProcessConfiguration` uses `array_any()` again; `array_first()` replaces `reset()` / `current()` in `ProcessConfiguration::getMainTask()`, `ConstraintLoader` and `ProcessHelpCommand` (no internal array pointer, same results). * [#279](https://github.com/cleverage/process-bundle/issues/279) Define the public API (Symfony BC promise): the classes not meant to be extended are marked `@final since 5.4` (the engine: `ProcessManager`, `ProcessConfigurationRegistry`, `TransformerRegistry`, `ContextualOptionResolver`; the models: `ProcessState`, `ProcessHistory`, `SubprocessInstance`, the configurations, the events, the `Filesystem` files; the concrete tasks and transformers), they will be `final` in v6.0: extending them triggers a deprecation with the Symfony `DebugClassLoader`. The abstract classes, interfaces, traits, exceptions and the file tasks extended by other tasks (`FileReaderTask`, `LineReaderTask`, `FolderBrowserTask`, `CsvReaderTask`, `InputCsvReaderTask`, `Filesystem\CsvResource`) stay open. `TaskRegistry`, new in this version, is `final`. The infrastructure classes are marked `@internal`: `CleverAgeProcessExtension`, `Configuration`, the commands, `DataQueueEventListener`, `PhpFunctionProvider`, the Monolog processors, `ProcessLogger` and `TaskLogger`. Update documentation. diff --git a/docs/reference/01-process_definition.md b/docs/reference/01-process_definition.md index 250ed8b5..6a1fdea7 100644 --- a/docs/reference/01-process_definition.md +++ b/docs/reference/01-process_definition.md @@ -61,7 +61,8 @@ must be in the [main branch](#main-branch) of the process. be in the main branch of the process. **public**: optional boolean (default is true) to mark a process as public or private. Private processes are filtered -from the process list (unless `--all` is used) but execution is still allowed. +from the process list (unless `--all` is used) but execution is still allowed. A value other than a boolean is rejected +when the container is compiled. **options**: optional free array (default is empty), not used by this bundle itself. It is available to other bundles through `ProcessConfiguration::getOptions()`, e.g. [cleverage/ui-process-bundle](https://github.com/cleverage/ui-process-bundle) diff --git a/docs/reference/02-task_definition.md b/docs/reference/02-task_definition.md index 29d1d946..8c5f2ec1 100644 --- a/docs/reference/02-task_definition.md +++ b/docs/reference/02-task_definition.md @@ -58,7 +58,9 @@ an error occurs). It can be a simple string. See [errors and skips](../04-advanc task throws an exception. **error_strategy**: optional, either *skip* or *stop*. Defines if the process continues with the next input or stops -when the task fails. When not defined, the global `default_error_strategy` is used (*stop* by default). +when the task fails. When not defined, the global `default_error_strategy` is used (*stop* by default). Another value +is deprecated since 5.4 (`E_USER_DEPRECATED` error when the container is compiled; the process fails with "Unknown +error strategy" when the task fails), it will be rejected in v6.0. **log_level**: optional [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) severity (emergency, alert, critical, error, warning, notice, info, debug) of the log record written on the `cleverage_process_task` channel when the task diff --git a/src/DependencyInjection/CleverAgeProcessExtension.php b/src/DependencyInjection/CleverAgeProcessExtension.php index 8b62613f..dd854401 100644 --- a/src/DependencyInjection/CleverAgeProcessExtension.php +++ b/src/DependencyInjection/CleverAgeProcessExtension.php @@ -15,6 +15,7 @@ use CleverAge\ProcessBundle\Attribute\AsTask; use CleverAge\ProcessBundle\Attribute\AsTransformer; +use CleverAge\ProcessBundle\Configuration\TaskConfiguration; use CleverAge\ProcessBundle\Model\ProcessStopCheckerInterface; use CleverAge\ProcessBundle\Model\TaskInterface; use CleverAge\ProcessBundle\Transformer\GenericTransformer; @@ -69,6 +70,7 @@ static function (ChildDefinition $definition, AsTransformer $attribute): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); + $this->checkErrorStrategies($container, $config['configurations']); $processConfigurationRegistry = $container->getDefinition('cleverage_process.registry.process_configuration'); $processConfigurationRegistry->replaceArgument(0, $config['configurations']); @@ -90,6 +92,37 @@ static function (ChildDefinition $definition, AsTransformer $attribute): void { } } + /** + * An unknown error strategy only fails when the task fails ("Unknown error strategy"). + * + * @param array}> $configurations + */ + protected function checkErrorStrategies(ContainerBuilder $container, array $configurations): void + { + $strategies = [TaskConfiguration::STRATEGY_SKIP, TaskConfiguration::STRATEGY_STOP]; + foreach ($configurations as $processCode => $processConfiguration) { + foreach ($processConfiguration['tasks'] as $taskCode => $taskConfiguration) { + // Not set (default_error_strategy), or not a string (already a TypeError in TaskConfiguration) + $errorStrategy = $taskConfiguration['error_strategy']; + if (!\is_string($errorStrategy) || \in_array($errorStrategy, $strategies, true)) { + continue; + } + // An environment variable is only known at runtime + if (str_contains((string) $container->resolveEnvPlaceholders($errorStrategy), '%env(')) { + continue; + } + trigger_deprecation( + 'cleverage/process-bundle', + '5.4', + 'The error strategy "%s" of the task "%s" of the process "%s" is unknown, use "skip" or "stop" (or remove it to use the "default_error_strategy"). Unknown error strategies are deprecated, they will be rejected in v6.0.', + $errorStrategy, + $taskCode, + $processCode + ); + } + } + } + /** * Import the service definition files of a directory into the container. */ diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 44144ae2..3793f20d 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -52,7 +52,8 @@ public function getConfigTreeBuilder(): TreeBuilder // Default error strategy $definition->enumNode('default_error_strategy') ->values([TaskConfiguration::STRATEGY_SKIP, TaskConfiguration::STRATEGY_STOP]) - ->defaultValue(TaskConfiguration::STRATEGY_STOP); + ->defaultValue(TaskConfiguration::STRATEGY_STOP) + ->info('Error strategy of the tasks without "error_strategy": "skip" the item or "stop" the process'); // Default log levels of the end of the processes $logsDefinition = $definition->arrayNode('logs') @@ -86,7 +87,7 @@ protected function appendRootTransformersConfigDefinition(NodeBuilder $definitio $transformerListDefinition = $transformersArrayDefinition ->performNoDeepMerging() ->cannotBeOverwritten() - ->info('Unique custom transformer code') + ->info('Unique custom transformer code, usable as a transformer in the processes') ->children(); $this->appendTransformerConfigDefinition($transformerListDefinition); @@ -99,10 +100,14 @@ protected function appendTransformerConfigDefinition(NodeBuilder $definition): v { $definition ->arrayNode('contextual_options') + ->info('Options of the transformer (code: definition with required, default, default_is_null), injected in the "transformers" with "{{ code }}"') + ->example(['delimiter' => ['default' => ',']]) ->variablePrototype() ->end() ->end() ->arrayNode('transformers') + ->info('Transformers applied in sequence, by code, with their options') + ->example(['explode' => ['delimiter' => '{{ separator }}']]) ->variablePrototype() ->end() ->end(); @@ -131,21 +136,27 @@ protected function appendProcessConfigDefinition(NodeBuilder $definition): void { $definition ->scalarNode('entry_point') + ->info('Code of the task receiving the input of the process (the input is ignored if not set)') ->defaultNull() ->end() ->scalarNode('end_point') + ->info('Code of the task whose last output is returned by the process (null if not set)') ->defaultNull() ->end() ->scalarNode('description') + ->info('Description of the process, shown by the cleverage:process:list command') ->defaultValue('') ->end() ->scalarNode('help') + ->info('Help of the process, shown by the cleverage:process:help command') ->defaultValue('') ->end() - ->scalarNode('public') + ->booleanNode('public') + ->info('Whether the process is listed by the cleverage:process:list command') ->defaultTrue() ->end() ->arrayNode('options') + ->info('Free options of the process, read by other bundles (e.g. "ui" for the CleverAge UI process bundle)') ->variablePrototype() ->end() ->end() @@ -197,21 +208,32 @@ protected function appendTaskConfigDefinition(NodeBuilder $definition): void ->info('Code of the task (cleverage.task tag, #[AsTask])') ->defaultNull(); $definition->scalarNode('description') + ->info('Description of the task, shown by the cleverage:process:help command') ->defaultValue(''); $definition->scalarNode('help') + ->info('Help of the task, shown by the cleverage:process:help command') ->defaultValue(''); $definition->arrayNode('options') + ->info('Options of the task, given on the reference page of each task') ->variablePrototype() ->end(); $definition->scalarNode('error_strategy') + ->info('On error, "skip" the item or "stop" the process; "default_error_strategy" if not set') ->defaultNull(); $definition->enumNode('log_level') ->values(self::LOG_LEVELS) + ->info('Level of the log of the errors of the task') ->defaultValue(LogLevel::CRITICAL); foreach (['outputs', 'errors', 'error_outputs'] as $nodeName) { $node = $definition->arrayNode($nodeName); - if ('errors' === $nodeName) { + if ('outputs' === $nodeName) { + $node->info('Codes of the tasks receiving the output of the task') + ->example(['next_task', 'other_task']); + } elseif ('error_outputs' === $nodeName) { + $node->info('Codes of the tasks receiving the error output of the task (by default, its input)') + ->example(['error_task']); + } elseif ('errors' === $nodeName) { $node->info('Deprecated, use "error_outputs"') ->setDeprecated( 'cleverage/process-bundle', diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index 413dd138..2c35894c 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -165,6 +165,42 @@ public function testDefaultLogLevelsArePassedToTheProcessConfigurationRegistry() ); } + public function testUnknownErrorStrategyIsDeprecated(): void + { + $container = new ContainerBuilder(); + $deprecations = []; + set_error_handler(static function (int $errno, string $errstr) use (&$deprecations): bool { + $deprecations[] = $errstr; + + return true; + }, \E_USER_DEPRECATED); + try { + (new CleverAgeProcessExtension())->load([['configurations' => ['test.process' => ['tasks' => [ + 'default' => ['task' => 'transformer'], + 'skip' => ['task' => 'transformer', 'error_strategy' => 'skip'], + 'stop' => ['task' => 'transformer', 'error_strategy' => 'stop'], + // Placeholder of the environment variable, as given to the extension + 'env' => ['task' => 'transformer', 'error_strategy' => $container->getParameterBag()->resolveValue('%env(APP_ERROR_STRATEGY)%')], + 'typo' => ['task' => 'transformer', 'error_strategy' => 'skp'], + ]]]]], $container); + } finally { + restore_error_handler(); + } + + self::assertSame( + ['Since cleverage/process-bundle 5.4: The error strategy "skp" of the task "typo" of the process "test.process" is unknown, use "skip" or "stop" (or remove it to use the "default_error_strategy"). Unknown error strategies are deprecated, they will be rejected in v6.0.'], + $deprecations + ); + } + + public function testPublicMustBeABoolean(): void + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('Invalid type for path "clever_age_process.configurations.test.process.public". Expected "bool", but got "string".'); + + $this->process(['configurations' => ['test.process' => ['public' => 'no', 'tasks' => []]]]); + } + /** * @param array $config *