diff --git a/Classes/DigiComp/SettingValidator/Package.php b/Classes/DigiComp/SettingValidator/Package.php deleted file mode 100644 index b7e328c..0000000 --- a/Classes/DigiComp/SettingValidator/Package.php +++ /dev/null @@ -1,33 +0,0 @@ -getSignalSlotDispatcher(); - $dispatcher->connect( - 'TYPO3\Flow\Configuration\ConfigurationManager', - 'configurationManagerReady', - function (ConfigurationManager $configurationManager) { - $configurationManager->registerConfigurationType( - 'Validation', - ConfigurationManager::CONFIGURATION_PROCESSING_TYPE_DEFAULT, - true - ); - } - ); - } -} diff --git a/Classes/DigiComp/SettingValidator/Validation/Validator/SettingsValidator.php b/Classes/DigiComp/SettingValidator/Validation/Validator/SettingsValidator.php deleted file mode 100644 index f29f969..0000000 --- a/Classes/DigiComp/SettingValidator/Validation/Validator/SettingsValidator.php +++ /dev/null @@ -1,96 +0,0 @@ -configurationManager = $configurationManager; - $this->validations = $this->configurationManager->getConfiguration('Validation'); - } - - /** - * @var ReflectionService - * @Flow\Inject - */ - protected $reflectionService; - - protected $supportedOptions = array( - 'name' => array('', 'Set the name of the setting-array to use', 'string', false) - ); - - /** - * Check if $value is valid. If it is not valid, needs to add an error - * to Result. - * - * @param mixed $value - * - * @throws InvalidValidationOptionsException - * @throws InvalidValidationConfigurationException - */ - protected function isValid($value) - { - $name = $this->options['name'] ? $this->options['name'] : $this->reflectionService->getClassNameByObject( - $value - ); - if (!isset($this->validations[$name])) { - throw new InvalidValidationOptionsException( - 'The name ' . $name . ' has not been defined in Validation.yaml!', - 1397821438 - ); - } - $config = &$this->validations[$name]; - foreach ($config as $validatorConfig) { - $validator = $this->validatorResolver->createValidator( - $validatorConfig['validator'], - $validatorConfig['options'] - ); - if (!$validator) { - throw new InvalidValidationConfigurationException( - 'Validator could not be resolved: ' . - $validatorConfig['validator'] . '. Check your Validation.yaml', - 1402326139 - ); - } - if (isset($validatorConfig['property'])) { - $this->result->forProperty($validatorConfig['property'])->merge( - $validator->validate(ObjectAccess::getPropertyPath($value, $validatorConfig['property'])) - ); - } else { - $this->result->merge($validator->validate($value)); - } - } - } -} diff --git a/Classes/Package.php b/Classes/Package.php new file mode 100644 index 0000000..06276a7 --- /dev/null +++ b/Classes/Package.php @@ -0,0 +1,44 @@ +getSignalSlotDispatcher(); + $dispatcher->connect(ConfigurationManager::class, 'configurationManagerReady', + function ($configurationManager) { + /** @var ConfigurationManager $configurationManager */ + $configurationManager->registerConfigurationType( + static::CONFIGURATION_TYPE_VALIDATION, + ConfigurationManager::CONFIGURATION_PROCESSING_TYPE_DEFAULT, + true + ); + } + ); + } +} diff --git a/Classes/Validation/Validator/SettingsValidator.php b/Classes/Validation/Validator/SettingsValidator.php new file mode 100644 index 0000000..1db1d4c --- /dev/null +++ b/Classes/Validation/Validator/SettingsValidator.php @@ -0,0 +1,194 @@ + ['', 'Set the name of the setting-array to use', 'string', false], + 'validationGroups' => [ + ['Default'], + 'Same as "Validation Groups" of Flow Framework. Defines the groups to execute.', + 'array', + false + ], + ]; + + /** + * @var array + */ + protected $validations; + + /** + * @param ConfigurationManager $configurationManager + */ + public function injectConfigurationManager(ConfigurationManager $configurationManager) + { + $this->configurationManager = $configurationManager; + $this->validations = $this->configurationManager->getConfiguration('Validation'); + } + + /** + * Check if $value is valid. If it is not valid, needs to add an error + * to Result. + * + * @param mixed $value + * + * @throws InvalidValidationOptionsException + * @throws InvalidValidationConfigurationException + */ + protected function isValid($value) + { + $name = $this->options['name'] ? $this->options['name'] : TypeHandling::getTypeForValue($value); + if (! isset($this->validations[$name])) { + throw new InvalidValidationOptionsException( + 'The name ' . $name . ' has not been defined in Validation.yaml!', + 1397821438 + ); + } + + $config = $this->getConfigForName($name); + + foreach ($config as $validatorConfig) { + if (! $this->doesValidationGroupsMatch($validatorConfig)) { + continue; + } + + $this->handleValidationGroups($validatorConfig); + + $validator = $this->validatorResolver->createValidator( + $validatorConfig['validator'], + $validatorConfig['options'] + ); + + if (! $validator) { + throw new InvalidValidationConfigurationException( + sprintf( + 'Validator could not be resolved: "%s" Check your Validation.yaml', + $validatorConfig['validator'] + ), + 1402326139 + ); + } + + if (isset($validatorConfig['property'])) { + $this->result->forProperty($validatorConfig['property'])->merge( + $validator->validate(ObjectAccess::getPropertyPath($value, $validatorConfig['property'])) + ); + } else { + $this->result->merge($validator->validate($value)); + } + } + } + + /** + * @param $name + * + * @return array + */ + protected function getConfigForName($name): array + { + $config = []; + if (isset($this->validations[$name]['self'])) { + foreach ($this->validations[$name]['self'] as $validator => &$validation) { + if (is_null($validation)) { + continue; + } + $newValidation['options'] = $validation; + $newValidation['validator'] = $validator; + $config[] = $newValidation; + } + } + if (isset($this->validations[$name]['properties'])) { + foreach ($this->validations[$name]['properties'] as $propertyName => &$validation) { + foreach ($validation as $validator => &$options) { + if (is_null($options)) { + continue; + } + $newValidation['property'] = $propertyName; + $newValidation['validator'] = $validator; + $newValidation['options'] = $options; + $config[] = $newValidation; + } + } + } + return $config; + } + + /** + * Check whether at least one configured group does match, if any is configured. + * + * @param array $validatorConfig + * @return bool + */ + protected function doesValidationGroupsMatch(array &$validatorConfig) + { + if (isset($validatorConfig['options']['validationGroups']) + && count(array_intersect($validatorConfig['options']['validationGroups'], $this->options['validationGroups'])) === 0 + ) { + return false; + } + + return true; + } + + /** + * Add validation groups for recursion if necessary. + * + * @param array $validatorConfig + */ + protected function handleValidationGroups(array &$validatorConfig) + { + if (isset($validatorConfig['options']['validationGroups']) + && $validatorConfig['validator'] !== 'DigiComp.SettingValidator:Settings' + ) { + unset($validatorConfig['options']['validationGroups']); + } elseif ($validatorConfig['validator'] === 'DigiComp.SettingValidator:Settings') { + $validatorConfig['options']['validationGroups'] = $this->options['validationGroups']; + } + } +} diff --git a/Configuration/Testing/Validation.yaml b/Configuration/Testing/Validation.yaml new file mode 100644 index 0000000..e61cfb0 --- /dev/null +++ b/Configuration/Testing/Validation.yaml @@ -0,0 +1,38 @@ +DigiComp\SettingValidator\Tests\Functional\Fixtures\TestObject: + properties: + shouldBeTrue: + BooleanValue: + expectedValue: true + shouldBeFalse: + BooleanValue: + expectedValue: false + Grumble: ~ + +TrueValidator: + self: + BooleanValue: + expectedValue: true + +DigiComp\SettingValidator\Tests\Functional\Fixtures\TestValidationGroupsCustomObject: + self: + DigiComp.SettingValidator:Settings: + name: GroupValidatorCustom + +DigiComp\SettingValidator\Tests\Functional\Fixtures\TestValidationGroupsDefaultObject: + self: + DigiComp.SettingValidator:Settings: + name: GroupValidatorDefault + +GroupValidatorDefault: + properties: + shouldBeTrue: + BooleanValue: + expectedValue: true + +GroupValidatorCustom: + properties: + shouldBeFalse: + BooleanValue: + expectedValue: false + validationGroups: + - Custom diff --git a/Migrations/Code/Version20170603120900.php b/Migrations/Code/Version20170603120900.php new file mode 100644 index 0000000..333c1a5 --- /dev/null +++ b/Migrations/Code/Version20170603120900.php @@ -0,0 +1,55 @@ +processConfiguration(Package::CONFIGURATION_TYPE_VALIDATION, + function (&$configuration) { + foreach ($configuration as $validatorName => &$validators) { + $newConfiguration = ['properties' => [], 'self' => []]; + + foreach ($validators as $key => &$validator) { + if (!isset($validator['validator']) || !isset($validator['options'])) { + $this->showWarning('The Validation.yaml files contained no validator or options for ' . + 'validation: "' . $validatorName . '.' . $key . '". It was not be migrated.'); + continue; + } + if (isset($validator['property'])) { + $newConfiguration['properties'][$validator['property']][$validator['validator']] + = $validator['options']; + } else { + $newConfiguration['self'][$validator['validator']] = $validator['options']; + } + } + $validators = $newConfiguration; + } + }, + true + ); + } +} diff --git a/Resources/Private/Schema/Validation.schema.yaml b/Resources/Private/Schema/Validation.schema.yaml index 088e51b..3f96d9d 100644 --- a/Resources/Private/Schema/Validation.schema.yaml +++ b/Resources/Private/Schema/Validation.schema.yaml @@ -1,11 +1,11 @@ type: dictionary additionalProperties: - type: array - items: + type: dictionary + additionalProperties: false + properties: type: dictionary additionalProperties: false - # This validation sadly only hits the first level of validation, and options are not coverable with current validator properties: - validator: {type: string, required: TRUE} - options: {type: dictionary, required: TRUE} - property: {type: string, required: FALSE} + type: dictionary + self: + type: dictionary diff --git a/Tests/Functional/Fixtures/TestObject.php b/Tests/Functional/Fixtures/TestObject.php new file mode 100644 index 0000000..ddd62c5 --- /dev/null +++ b/Tests/Functional/Fixtures/TestObject.php @@ -0,0 +1,47 @@ +shouldBeTrue; + } + + /** + * @return bool + */ + public function isShouldBeFalse(): bool + { + return $this->shouldBeFalse; + } + + /** + * @return bool + */ + public function isShouldBeTrueAndValidatedByAnnotation(): bool + { + return $this->shouldBeTrueAndValidatedByAnnotation; + } +} diff --git a/Tests/Functional/Fixtures/TestValidationGroupsCustomObject.php b/Tests/Functional/Fixtures/TestValidationGroupsCustomObject.php new file mode 100644 index 0000000..29df333 --- /dev/null +++ b/Tests/Functional/Fixtures/TestValidationGroupsCustomObject.php @@ -0,0 +1,33 @@ +shouldBeTrue; + } + + /** + * @return bool + */ + public function isShouldBeFalse(): bool + { + return $this->shouldBeFalse; + } +} diff --git a/Tests/Functional/Fixtures/TestValidationGroupsDefaultObject.php b/Tests/Functional/Fixtures/TestValidationGroupsDefaultObject.php new file mode 100644 index 0000000..4d4197c --- /dev/null +++ b/Tests/Functional/Fixtures/TestValidationGroupsDefaultObject.php @@ -0,0 +1,33 @@ +shouldBeTrue; + } + + /** + * @return bool + */ + public function isShouldBeFalse(): bool + { + return $this->shouldBeFalse; + } +} diff --git a/Tests/Functional/SettingsValidatorTest.php b/Tests/Functional/SettingsValidatorTest.php new file mode 100644 index 0000000..813c171 --- /dev/null +++ b/Tests/Functional/SettingsValidatorTest.php @@ -0,0 +1,60 @@ +objectManager->get(SettingsValidator::class); + $result = $validator->validate(new TestObject()); + $this->assertTrue($result->hasErrors()); + $this->assertCount(1, $result->getFlattenedErrors()); + $this->assertCount(1, $result->forProperty('shouldBeFalse')->getErrors()); + } + + /** + * @test + */ + public function conjunctionValidationWorksAsExpected() + { + $validatorResolver = $this->objectManager->get(ValidatorResolver::class); + $validator = $validatorResolver->getBaseValidatorConjunction(TestObject::class); + $result = $validator->validate(new TestObject()); + $this->assertTrue($result->hasErrors()); + $this->assertCount(1, $result->getFlattenedErrors()); + } + + /** + * @test + */ + public function defaultValidationGroupWorks() + { + $validator = $this->objectManager->get(SettingsValidator::class, ['validationGroups' => ['Default']]); + $result = $validator->validate(new TestValidationGroupsDefaultObject()); + $this->assertTrue($result->hasErrors(), 'No Errors for validation group "Default"'); + $this->assertCount(1, $result->getFlattenedErrors(), 'Got a non expected number of errors for group "Default"'); + $this->assertCount(1, $result->forProperty('shouldBeTrue')->getErrors(), 'Got no error for property'); + } + + /** + * @test + */ + public function customValidationGroupWorks() + { + $validator = $this->objectManager->get(SettingsValidator::class, ['validationGroups' => ['Custom']]); + $result = $validator->validate(new TestValidationGroupsCustomObject()); + $this->assertTrue($result->hasErrors(), 'No Errors for validation group "Custom"'); + $this->assertCount(1, $result->getFlattenedErrors(), 'Got a non expected number of errors for group "Custom"'); + $this->assertCount(1, $result->forProperty('shouldBeFalse')->getErrors(), 'Got no error for property'); + } +} diff --git a/composer.json b/composer.json index 4143c8e..65020c7 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "digicomp/settingvalidator", - "type": "typo3-flow-package", - "description": "Just a TYPO3\\Flow Validator resolving other Validators with Configuration/Validation.yaml", + "type": "neos-package", + "description": "Just a Neos\\Flow Validator resolving other Validators with Configuration/Validation.yaml", "authors": [ { "name": "Ferdinand Kuhl", @@ -13,21 +13,27 @@ "license": "MIT", "homepage": "https://github.com/fcool/DigiComp.SettingValidator", "keywords": [ + "Neos", "Flow", "validation" ], "require": { - "typo3/flow": "~2.0|~3.0", - "php": ">=5.3.0" + "neos/flow": "~4.1" }, "autoload": { - "psr-0": { - "DigiComp\\SettingValidator": "Classes" + "psr-4": { + "DigiComp\\SettingValidator\\": "Classes" + } + }, + "autoload-dev": { + "psr-4": { + "DigiComp\\SettingValidator\\Tests\\": "Tests" } }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.0.x-dev", + "dev-feature/neos-flow4": "2.0.x-dev" }, "applied-flow-migrations": [ "Inwebs.Basket-201409170938", @@ -44,7 +50,23 @@ "TYPO3.Fluid-20141113120800", "TYPO3.Flow-20141113121400", "TYPO3.Fluid-20141121091700", - "TYPO3.Fluid-20150214130800" + "TYPO3.Fluid-20150214130800", + "TYPO3.Flow-20151113161300", + "TYPO3.Flow-20161115140400", + "TYPO3.Flow-20161115140430", + "Neos.Flow-20161124204700", + "Neos.Flow-20161124204701", + "Neos.Flow-20161124224015", + "Neos.Eel-20161124230101", + "Neos.Imagine-20161124231742", + "Neos.Media-20161124233100", + "Neos.Flow-20161125124112", + "Neos.SwiftMailer-20161130105617", + "TYPO3.FluidAdaptor-20161130112935", + "Neos.Media-20161219094126", + "Neos.Flow-20170125103800", + "Neos.Flow-20170127183102", + "DigiComp.SettingValidator-20170603120900" ] } } \ No newline at end of file