diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d87c8a..fee4fcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Latest * [#220](https://github.com/cleverage/process-bundle/issues/220) Fix the stop error strategy: throw a `ProcessFailedException` (with the original exception as `previous`) instead of a `FatalError`, so that the command exits with a non-zero code when a process fails and `ProcessLauncherTask` detects failed sub-processes. Update documentation, add tests. * [#221](https://github.com/cleverage/process-bundle/issues/221) Fix CsvSplitterTask: each produced file contains `max_lines` data lines (instead of `max_lines - 2`), no infinite loop with `max_lines` <= 2 (`max_lines` must now be an integer greater than 0), no header-only file at the end. Update documentation, add tests. * [#223](https://github.com/cleverage/process-bundle/issues/223) Fix CounterTask: the final count is outputted once, as `flush()` may be called several times. Document that `flush()` implementations must be idempotent. Update documentation, add tests. +* [#228](https://github.com/cleverage/process-bundle/issues/228) Add the missing `setAllowedTypes()` on HashTransformer (`raw_output`), SimpleBatchTask (`batch_count`), ConditionTrait (`empty`, `not_empty`), NormalizerTask / SerializerTask / DeserializerTask (`context`) and ObjectUpdaterTask (`property_path`): a wrong type is now reported when the options are resolved. 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. diff --git a/src/Task/ObjectUpdaterTask.php b/src/Task/ObjectUpdaterTask.php index 30a53641..0490a249 100644 --- a/src/Task/ObjectUpdaterTask.php +++ b/src/Task/ObjectUpdaterTask.php @@ -17,6 +17,7 @@ use CleverAge\ProcessBundle\Model\ProcessState; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * Takes an array containing an object and a value updates an object's property with this value, then return the object. @@ -44,5 +45,6 @@ public function execute(ProcessState $state): void protected function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired(['property_path']); + $resolver->setAllowedTypes('property_path', ['string', PropertyPathInterface::class]); } } diff --git a/src/Task/Serialization/DeserializerTask.php b/src/Task/Serialization/DeserializerTask.php index 654a535d..9035d1d9 100644 --- a/src/Task/Serialization/DeserializerTask.php +++ b/src/Task/Serialization/DeserializerTask.php @@ -45,5 +45,6 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'context' => [], ]); + $resolver->setAllowedTypes('context', ['array']); } } diff --git a/src/Task/Serialization/NormalizerTask.php b/src/Task/Serialization/NormalizerTask.php index 8c43ed1b..f7126ac9 100644 --- a/src/Task/Serialization/NormalizerTask.php +++ b/src/Task/Serialization/NormalizerTask.php @@ -51,5 +51,6 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'context' => [], ]); + $resolver->setAllowedTypes('context', ['array']); } } diff --git a/src/Task/Serialization/SerializerTask.php b/src/Task/Serialization/SerializerTask.php index 644447b0..90953452 100644 --- a/src/Task/Serialization/SerializerTask.php +++ b/src/Task/Serialization/SerializerTask.php @@ -39,5 +39,6 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'context' => [], ]); + $resolver->setAllowedTypes('context', ['array']); } } diff --git a/src/Task/SimpleBatchTask.php b/src/Task/SimpleBatchTask.php index b022dd70..6bdb99bf 100644 --- a/src/Task/SimpleBatchTask.php +++ b/src/Task/SimpleBatchTask.php @@ -53,5 +53,6 @@ protected function configureOptions(OptionsResolver $resolver): void $resolver->setDefaults([ 'batch_count' => 10, ]); + $resolver->setAllowedTypes('batch_count', ['int', 'null']); } } diff --git a/src/Transformer/ConditionTrait.php b/src/Transformer/ConditionTrait.php index d91e705d..076cb0e3 100644 --- a/src/Transformer/ConditionTrait.php +++ b/src/Transformer/ConditionTrait.php @@ -99,6 +99,8 @@ protected function configureConditionOptions(OptionsResolver $resolver): void $resolver->setDefault('match_regexp', []); $resolver->setAllowedTypes('not_match', 'array'); $resolver->setAllowedTypes('match', 'array'); + $resolver->setAllowedTypes('not_empty', 'array'); + $resolver->setAllowedTypes('empty', 'array'); $resolver->setAllowedTypes('not_match_regexp', 'array'); $resolver->setAllowedTypes('match_regexp', 'array'); } diff --git a/src/Transformer/String/HashTransformer.php b/src/Transformer/String/HashTransformer.php index 4082b845..2dc58b11 100644 --- a/src/Transformer/String/HashTransformer.php +++ b/src/Transformer/String/HashTransformer.php @@ -29,6 +29,7 @@ public function configureOptions(OptionsResolver $resolver): void $resolver->setDefined('raw_output'); $resolver->setDefault('raw_output', false); + $resolver->setAllowedTypes('raw_output', 'bool'); } public function transform(mixed $value, array $options = []): string diff --git a/tests/OptionAllowedTypesTest.php b/tests/OptionAllowedTypesTest.php new file mode 100644 index 00000000..67d8777f --- /dev/null +++ b/tests/OptionAllowedTypesTest.php @@ -0,0 +1,186 @@ +}> + */ + public static function invalidTransformerOptionsProvider(): iterable + { + yield 'hash raw_output' => [HashTransformer::class, ['algo' => 'md5', 'raw_output' => 'yes']]; + yield 'condition empty' => [ArrayFilterTransformer::class, ['condition' => ['empty' => 'name']]]; + yield 'condition not_empty' => [ArrayFilterTransformer::class, ['condition' => ['not_empty' => 'name']]]; + } + + /** + * @param class-string $class + * @param array $options + */ + #[DataProvider('invalidTransformerOptionsProvider')] + public function testInvalidTransformerOptionIsRejected(string $class, array $options): void + { + $this->expectException(InvalidOptionsException::class); + + $this->resolveTransformerOptions($class, $options); + } + + /** + * @return iterable}> + */ + public static function validTransformerOptionsProvider(): iterable + { + yield 'hash raw_output' => [HashTransformer::class, ['algo' => 'md5', 'raw_output' => true]]; + yield 'condition empty' => [ArrayFilterTransformer::class, ['condition' => ['empty' => ['name' => null]]]]; + yield 'condition not_empty' => [ArrayFilterTransformer::class, ['condition' => ['not_empty' => ['name' => null]]]]; + } + + /** + * @param class-string $class + * @param array $options + */ + #[DataProvider('validTransformerOptionsProvider')] + public function testValidTransformerOptionIsAccepted(string $class, array $options): void + { + self::assertNotEmpty($this->resolveTransformerOptions($class, $options)); + } + + /** + * @return iterable}> + */ + public static function invalidTaskOptionsProvider(): iterable + { + yield 'simple batch batch_count' => [SimpleBatchTask::class, ['batch_count' => '10']]; + yield 'normalizer context' => [NormalizerTask::class, ['format' => 'json', 'context' => 'groups']]; + yield 'serializer context' => [SerializerTask::class, ['format' => 'json', 'context' => 'groups']]; + yield 'deserializer context' => [DeserializerTask::class, ['type' => 'array', 'format' => 'json', 'context' => 'groups']]; + yield 'object updater property_path' => [ObjectUpdaterTask::class, ['property_path' => ['name']]]; + } + + /** + * @param class-string $class + * @param array $options + */ + #[DataProvider('invalidTaskOptionsProvider')] + public function testInvalidTaskOptionIsRejected(string $class, array $options): void + { + $this->expectException(InvalidOptionsException::class); + + $this->initializeTask($class, $options); + } + + /** + * @return iterable}> + */ + public static function validTaskOptionsProvider(): iterable + { + yield 'simple batch batch_count' => [SimpleBatchTask::class, ['batch_count' => 10]]; + yield 'simple batch null batch_count' => [SimpleBatchTask::class, ['batch_count' => null]]; + yield 'normalizer context' => [NormalizerTask::class, ['format' => 'json', 'context' => ['groups' => ['a']]]]; + yield 'serializer context' => [SerializerTask::class, ['format' => 'json', 'context' => []]]; + yield 'deserializer context' => [DeserializerTask::class, ['type' => 'array', 'format' => 'json', 'context' => []]]; + yield 'object updater string property_path' => [ObjectUpdaterTask::class, ['property_path' => 'name']]; + yield 'object updater PropertyPath property_path' => [ObjectUpdaterTask::class, ['property_path' => new PropertyPath('name')]]; + } + + /** + * @param class-string $class + * @param array $options + */ + #[DataProvider('validTaskOptionsProvider')] + public function testValidTaskOptionIsAccepted(string $class, array $options): void + { + $this->expectNotToPerformAssertions(); + + $this->initializeTask($class, $options); + } + + /** + * @param class-string $class + * @param array $options + * + * @return array + */ + private function resolveTransformerOptions(string $class, array $options): array + { + $transformer = ArrayFilterTransformer::class === $class + ? new ArrayFilterTransformer(PropertyAccess::createPropertyAccessor()) + : new $class(); + $resolver = new OptionsResolver(); + $transformer->configureOptions($resolver); + + return $resolver->resolve($options); + } + + /** + * @param class-string $class + * @param array $options + */ + private function initializeTask(string $class, array $options): void + { + $task = match ($class) { + NormalizerTask::class, SerializerTask::class, DeserializerTask::class => new $class(new Serializer()), + ObjectUpdaterTask::class => new ObjectUpdaterTask(PropertyAccess::createPropertyAccessor()), + default => new $class(), + }; + + $processConfiguration = new ProcessConfiguration('test', []); + $state = new ProcessState($processConfiguration, new ProcessHistory($processConfiguration)); + $state->setContextualOptionResolver(new ContextualOptionResolver()); + $state->setContext([]); + $state->setTaskConfiguration(new TaskConfiguration('task', $class, $options)); + $task->initialize($state); + } +}