Skip to content
Snippets Groups Projects
Verified Commit 030a9cef authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #2937542 by alexpott, dawehner, Mile23, catch, mikelutz: Not setting the...

Issue #2937542 by alexpott, dawehner, Mile23, catch, mikelutz: Not setting the strict option of the Choice constraint to true is deprecated since Symfony 3.4 and will throw an exception in 4.0
parent 57f1f1a4
No related branches found
No related tags found
No related merge requests found
......@@ -16,6 +16,7 @@
*/
class AllowedValuesConstraint extends Choice {
public $strict = TRUE;
public $minMessage = 'You must select at least %limit choice.|You must select at least %limit choices.';
public $maxMessage = 'You must select at most %limit choice.|You must select at most %limit choices.';
......
......@@ -6,10 +6,12 @@
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\ChoiceValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Validates the AllowedValues constraint.
......@@ -47,9 +49,24 @@ public function __construct(AccountInterface $current_user) {
*/
public function validate($value, Constraint $constraint) {
$typed_data = $this->getTypedData();
if ($typed_data instanceof OptionsProviderInterface) {
// In a better world where typed data just returns typed values, we could
// set a constraint callback to use the OptionsProviderInterface.
// This is not possible right now though because we do the typecasting
// further down.
if ($constraint->callback) {
if (!\is_callable($choices = [$this->context->getObject(), $constraint->callback])
&& !\is_callable($choices = [$this->context->getClassName(), $constraint->callback])
&& !\is_callable($choices = $constraint->callback)
) {
throw new ConstraintDefinitionException('The AllowedValuesConstraint constraint expects a valid callback');
}
$allowed_values = \call_user_func($choices);
// parent::validate() does not need to invoke the callback again.
$constraint->callback = NULL;
}
elseif ($typed_data instanceof OptionsProviderInterface) {
$allowed_values = $typed_data->getSettableValues($this->currentUser);
$constraint->choices = $allowed_values;
// If the data is complex, we have to validate its main property.
if ($typed_data instanceof ComplexDataInterface) {
......@@ -69,6 +86,22 @@ public function validate($value, Constraint $constraint) {
return;
}
if (isset($allowed_values)) {
$constraint->choices = $allowed_values;
// Make the types match for strict checking. We can't use typed data here
// because types are not enforced everywhere. For example,
// \Drupal\Core\Field\Plugin\Field\FieldType\BooleanItem::getSettableValues()
// uses 0 and 1 to represent possible Boolean values whilst
// \Drupal\Core\TypedData\Plugin\DataType\BooleanData::getCastedValue()
// will return a proper typed Boolean. Therefore assume that
// $allowed_values contains values of the same type and cast $value to
// match.
settype($value, gettype(reset($allowed_values)));
}
elseif ($typed_data instanceof PrimitiveInterface) {
$value = $typed_data->getCastedValue();
}
parent::validate($value, $constraint);
}
......
......@@ -4,6 +4,7 @@
use Drupal\Core\TypedData\DataDefinition;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Tests AllowedValues validation constraint with both valid and invalid values.
......@@ -49,6 +50,62 @@ public function testValidation() {
$this->assertEqual($violation->getMessage(), t('The value you selected is not a valid choice.'), 'The message for invalid value is correct.');
$this->assertEqual($violation->getRoot(), $typed_data, 'Violation root is correct.');
$this->assertEqual($violation->getInvalidValue(), 4, 'The invalid value is set correctly in the violation.');
// Test the validation when a value of an incorrect type is passed.
$typed_data = $this->typedData->create($definition, '1');
$violations = $typed_data->validate();
$this->assertEquals(0, $violations->count(), 'Value is coerced to the correct type and is valid.');
}
/**
* Tests the AllowedValuesConstraintValidator with callbacks.
*/
public function testValidationCallback() {
// Create a definition that specifies some AllowedValues and a callback.
// This tests that callbacks have a higher priority than a supplied list of
// values and can be used to coerce the value to the correct type.
$definition = DataDefinition::create('string')
->addConstraint('AllowedValues', ['choices' => [1, 2, 3], 'callback' => [static::class, 'allowedValueCallback']]);
$typed_data = $this->typedData->create($definition, 'a');
$violations = $typed_data->validate();
$this->assertEquals(0, $violations->count(), 'Validation passed for correct value.');
$typed_data = $this->typedData->create($definition, 1);
$violations = $typed_data->validate();
$this->assertEquals(0, $violations->count(), 'Validation passed for value that will be cast to the correct type.');
$typed_data = $this->typedData->create($definition, 2);
$violations = $typed_data->validate();
$this->assertEquals(1, $violations->count(), 'Validation failed for incorrect value.');
$typed_data = $this->typedData->create($definition, 'd');
$violations = $typed_data->validate();
$this->assertEquals(1, $violations->count(), 'Validation failed for incorrect value.');
}
/**
* An AllowedValueConstraint callback.
*
* @return string[]
* A list of allowed values.
*/
public static function allowedValueCallback() {
return ['a', 'b', 'c', '1'];
}
/**
* Tests the AllowedValuesConstraintValidator with an invalid callback.
*/
public function testValidationCallbackException() {
// Create a definition that specifies some AllowedValues and a callback.
// This tests that callbacks have a higher priority than a supplied list of
// values and can be used to coerce the value to the correct type.
$definition = DataDefinition::create('string')
->addConstraint('AllowedValues', ['choices' => [1, 2, 3], 'callback' => [static::class, 'doesNotExist']]);
$typed_data = $this->typedData->create($definition, 1);
$this->setExpectedException(ConstraintDefinitionException::class, 'The AllowedValuesConstraint constraint expects a valid callback');
$typed_data->validate();
}
}
......@@ -126,7 +126,6 @@ public static function getSkippedDeprecations() {
// is a Windows only deprecation. Remove when core no longer uses
// WinCacheClassLoader in \Drupal\Core\DrupalKernel::initializeSettings().
'The Symfony\Component\ClassLoader\WinCacheClassLoader class is deprecated since Symfony 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.',
'Not setting the strict option of the Choice constraint to true is deprecated since Symfony 3.4 and will throw an exception in 4.0.',
];
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment