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 @@ -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).
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/01-process_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/02-task_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions src/DependencyInjection/CleverAgeProcessExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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']);
Expand All @@ -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<string, array{tasks: array<string, array{error_strategy: mixed}>}> $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.
*/
Expand Down
30 changes: 26 additions & 4 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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',
Expand Down
36 changes: 36 additions & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, mixed> $config
*
Expand Down
Loading