diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a38483..34164066 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. * [#309](https://github.com/cleverage/process-bundle/issues/309) Composer: `ext-intl` is no longer required, it is suggested: it is only needed by the `slugify` transformer (`SlugifyTransformer`), which is replaced by a service failing with an explicit message when the extension is missing (`UnavailableServicesCompilerPass`). 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). 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 *