diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php index d0624a6..23cd7b7 100644 --- a/core/lib/Drupal/Core/Form/FormState.php +++ b/core/lib/Drupal/Core/Form/FormState.php @@ -210,6 +210,20 @@ class FormState implements FormStateInterface { protected $no_cache; /** + * An associative array of values submitted to the form. + * + * The validation functions and submit functions use this array for nearly all + * their decision making. (Note that #tree determines whether the values are a + * flat array or an array whose structure parallels the $form array. See + * \Drupal\Core\Render\Element\FormElement for more information.) + * + * This property is uncacheable. + * + * @var array + */ + protected $values = array(); + + /** * An associative array of form value keys to be removed by cleanValues(). * * Any values that are temporary but must still be displayed as values in @@ -966,6 +980,13 @@ public function setUserInput(array $user_input) { /** * {@inheritdoc} */ + public function &getValues() { + return $this->values; + } + + /** + * {@inheritdoc} + */ public function setResponse(Response $response) { $this->response = $response; return $this; @@ -1237,11 +1258,4 @@ protected function moduleLoadInclude($module, $type, $name = NULL) { return \Drupal::moduleHandler()->loadInclude($module, $type, $name); } - /** - * {@inheritdoc} - */ - public function getSubFormState(array $element) { - return SubFormState::createForSubform($element, $this); - } - } diff --git a/core/lib/Drupal/Core/Form/FormStateDecoratorBase.php b/core/lib/Drupal/Core/Form/FormStateDecoratorBase.php index 6f7c0e3..da24cb1 100644 --- a/core/lib/Drupal/Core/Form/FormStateDecoratorBase.php +++ b/core/lib/Drupal/Core/Form/FormStateDecoratorBase.php @@ -588,7 +588,7 @@ public function setError(array &$element, $message = '') { * {@inheritdoc} */ public function clearErrors() { - return $this->decoratedFormState->clearErrors(); + $this->decoratedFormState->clearErrors(); } /** diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php index 7d9d80d..798d12c 100644 --- a/core/lib/Drupal/Core/Form/FormStateInterface.php +++ b/core/lib/Drupal/Core/Form/FormStateInterface.php @@ -1115,14 +1115,4 @@ public function addCleanValueKey($key); */ public function cleanValues(); - /** - * Gets the form state for a subform. - * - * @param mixed[] $subform - * The subform for which to get the form state. - * - * @return \Drupal\Core\Form\FormStateInterface - */ - public function getSubFormState(array $subform); - } diff --git a/core/lib/Drupal/Core/Form/FormStateValuesTrait.php b/core/lib/Drupal/Core/Form/FormStateValuesTrait.php index 1aa22cd..6626beb 100644 --- a/core/lib/Drupal/Core/Form/FormStateValuesTrait.php +++ b/core/lib/Drupal/Core/Form/FormStateValuesTrait.php @@ -19,25 +19,9 @@ trait FormStateValuesTrait { /** - * An associative array of values submitted to the form. - * - * The validation functions and submit functions use this array for nearly all - * their decision making. (Note that #tree determines whether the values are a - * flat array or an array whose structure parallels the $form array. See - * \Drupal\Core\Render\Element\FormElement for more information.) - * - * This property is uncacheable. - * - * @var array - */ - protected $values = array(); - - /** * @see \Drupal\Core\Form\FormStateInterface::getValues() */ - public function &getValues() { - return $this->values; - } + abstract public function &getValues(); /** * @see \Drupal\Core\Form\FormStateInterface::getValue() diff --git a/core/lib/Drupal/Core/Form/SubFormState.php b/core/lib/Drupal/Core/Form/SubformState.php similarity index 82% rename from core/lib/Drupal/Core/Form/SubFormState.php rename to core/lib/Drupal/Core/Form/SubformState.php index 9fe3df6..e54ac21 100644 --- a/core/lib/Drupal/Core/Form/SubFormState.php +++ b/core/lib/Drupal/Core/Form/SubformState.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Core\Form\SubFormState. + * Contains \Drupal\Core\Form\SubformState. */ namespace Drupal\Core\Form; @@ -13,7 +13,7 @@ /** * Stores information about the state of a subform. */ -class SubFormState extends FormStateDecoratorBase { +class SubformState extends FormStateDecoratorBase implements SubformStateInterface { use FormStateValuesTrait; @@ -22,7 +22,7 @@ class SubFormState extends FormStateDecoratorBase { * * @var mixed[] */ - protected $subform = []; + protected $subform; /** * Constructs a new instance. @@ -62,7 +62,7 @@ public static function createForSubform(array &$subform, FormStateInterface $par * @throws \InvalidArgumentException * Thrown when the requested property does not exist. */ - protected function getSubFormProperty($property) { + protected function getSubformProperty($property) { if (!array_key_exists($property, $this->subform)) { throw new \InvalidArgumentException(sprintf('The subform must contain the %s property. Try calling this method from a #process callback instead.', $property)); } @@ -77,7 +77,7 @@ protected function getSubFormProperty($property) { * The parent keys (#array_parents). */ protected function getArrayParents() { - return $this->getSubFormProperty('#array_parents'); + return $this->getSubformProperty('#array_parents'); } /** @@ -87,7 +87,7 @@ protected function getArrayParents() { * The parent keys (#parents). */ protected function getParents() { - return $this->getSubFormProperty('#parents'); + return $this->getSubformProperty('#parents'); } /** @@ -103,15 +103,14 @@ public function &getValues() { throw new \UnexpectedValueException('The form state values do not belong to the subform.'); } - return $values; } /** * {@inheritdoc} */ - public function getSubFormState(array $element) { - return static::createForSubform($element, $this); + public function getCompleteFormState() { + return $this->decoratedFormState instanceof SubformStateInterface ? $this->decoratedFormState->getCompleteFormState() : $this->decoratedFormState; } } diff --git a/core/lib/Drupal/Core/Form/SubformStateInterface.php b/core/lib/Drupal/Core/Form/SubformStateInterface.php new file mode 100644 index 0000000..f837a21 --- /dev/null +++ b/core/lib/Drupal/Core/Form/SubformStateInterface.php @@ -0,0 +1,27 @@ +getSubFormState($form['settings']); + $subform_state = SubformState::createForSubform($form['settings'], $form_state); $form['settings'] = $entity->getPlugin()->buildConfigurationForm($form['settings'], $subform_state); $form['visibility'] = $this->buildVisibilityInterface([], $form_state); @@ -287,7 +288,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // The Block Entity form puts all block plugin form elements in the // settings form element, so just pass that to the block for validation. - $this->entity->getPlugin()->validateConfigurationForm($form['settings'], $form_state->getSubFormState($form['settings'])); + $this->entity->getPlugin()->validateConfigurationForm($form['settings'], SubformState::createForSubform($form['settings'], $form_state)); $this->validateVisibility($form, $form_state); } @@ -311,7 +312,7 @@ protected function validateVisibility(array $form, FormStateInterface $form_stat // Allow the condition to validate the form. $condition = $form_state->get(['conditions', $condition_id]); - $condition->validateConfigurationForm($form['visibility'][$condition_id], $form_state->getSubFormState($form['visibility'][$condition_id])); + $condition->validateConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form_state)); } } @@ -324,7 +325,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $entity = $this->entity; // The Block Entity form puts all block plugin form elements in the // settings form element, so just pass that to the block for submission. - $sub_form_state = $form_state->getSubFormState($form['settings']); + $sub_form_state = SubformState::createForSubform($form['settings'], $form_state); // Call the plugin submit handler. $entity->getPlugin()->submitConfigurationForm($form['settings'], $sub_form_state); $block = $entity->getPlugin(); @@ -338,7 +339,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { foreach ($form_state->getValue('visibility') as $condition_id => $values) { // Allow the condition to submit the form. $condition = $form_state->get(['conditions', $condition_id]); - $condition->submitConfigurationForm($form['visibility'][$condition_id], $form_state->getSubFormState($form['visibility'][$condition_id])); + $condition->submitConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form_state)); if ($condition instanceof ContextAwarePluginInterface) { $context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : []; diff --git a/core/modules/image/src/Form/ImageEffectFormBase.php b/core/modules/image/src/Form/ImageEffectFormBase.php index def903f..24542c4 100644 --- a/core/modules/image/src/Form/ImageEffectFormBase.php +++ b/core/modules/image/src/Form/ImageEffectFormBase.php @@ -9,7 +9,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Form\SubFormState; +use Drupal\Core\Form\SubformState; use Drupal\image\ConfigurableImageEffectInterface; use Drupal\image\ImageStyleInterface; use Drupal\Component\Plugin\Exception\PluginNotFoundException; @@ -79,7 +79,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ImageStyl ); $form['data'] = []; - $subform_state = $form_state->getSubFormState($form['data']); + $subform_state = SubformState::createForSubform($form['data'], $form_state); $form['data'] = $this->imageEffect->buildConfigurationForm($form['data'], $subform_state); $form['data']['#tree'] = TRUE; @@ -109,7 +109,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ImageStyl public function validateForm(array &$form, FormStateInterface $form_state) { // The image effect configuration is stored in the 'data' key in the form, // pass that through for validation. - $this->imageEffect->validateConfigurationForm($form['data'], $form_state->getSubFormState($form['data'])); + $this->imageEffect->validateConfigurationForm($form['data'], SubformState::createForSubform($form['data'], $form_state)); } /** @@ -120,7 +120,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // The image effect configuration is stored in the 'data' key in the form, // pass that through for submission. - $this->imageEffect->submitConfigurationForm($form['data'], $form_state->getSubFormState($form['data'])); + $this->imageEffect->submitConfigurationForm($form['data'], SubformState::createForSubform($form['data'], $form_state)); $this->imageEffect->setWeight($form_state->getValue('weight')); if (!$this->imageEffect->getUuid()) { diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php index d707661..ff69778 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormStateDecoratorBaseTest.php @@ -1058,10 +1058,14 @@ public function testGetError() { * @covers ::getErrors */ public function testGetErrors() { + $errors = [ + 'foo' => 'bar', + ]; $this->decoratedFormState->expects($this->once()) - ->method('getErrors'); + ->method('getErrors') + ->willReturn($errors); - $this->sut->getErrors(); + $this->assertSame($errors, $this->sut->getErrors()); } /** @@ -1223,4 +1227,4 @@ public function testCleanValues() { $this->assertSame($this->sut, $this->sut->cleanValues()); } -} \ No newline at end of file +} diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php index 71d1097..51b1a91 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php @@ -420,19 +420,16 @@ public function testCleanValues($form_state) { } /** - * @covers ::getSubFormState + * @covers ::setValues + * @covers ::getValues */ - public function testGetSubFormState() { - $form_state = new FormState(); - $element = [ - '#parents' => ['foo'], - '#array_parents' => ['foo'], - 'bar' => [], + public function testGetValues() { + $values = [ + 'foo' => 'bar', ]; - - $sub_form_state = $form_state->getSubFormState($element); - $this->assertNotSame($form_state, $sub_form_state); - $this->assertInstanceOf(FormStateInterface::class, $sub_form_state); + $form_state = new FormState(); + $form_state->setValues($values); + $this->assertSame($values, $form_state->getValues()); } } diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateValuesTraitTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateValuesTraitTest.php index 822bafb..acc3507 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormStateValuesTraitTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormStateValuesTraitTest.php @@ -23,54 +23,63 @@ class FormStateValuesTraitTest extends UnitTestCase { * @covers ::setValueForElement */ public function testSetValueForElement() { - $element = array( - '#parents' => array( + $element = [ + '#parents' => [ 'foo', 'bar', - ), - ); + ], + ]; $value = $this->randomMachineName(); $form_state = new FormStateValuesTraitStub(); $form_state->setValueForElement($element, $value); - $expected = array( - 'foo' => array( + $expected = [ + 'foo' => [ 'bar' => $value, - ), - ); + ], + ]; $this->assertSame($expected, $form_state->getValues()); } /** * @covers ::getValue * - * @dataProvider providerTestGetValue + * @dataProvider providerGetValue */ public function testGetValue($key, $expected, $default = NULL) { $form_state = (new FormStateValuesTraitStub())->setValues([ 'foo' => 'one', - 'bar' => array( + 'bar' => [ 'baz' => 'two', - ), + ], ]); $this->assertSame($expected, $form_state->getValue($key, $default)); } - public function providerTestGetValue() { - $data = array(); - $data[] = array( + /** + * Provides data to self::testGetValue(). + * + * @return array[] + * Items are arrays of two items: + * - The key for which to get the value (string) + * - The expected value (mixed). + * - The default value (mixed). + */ + public function providerGetValue() { + $data = []; + $data[] = [ 'foo', 'one', - ); - $data[] = array( - array('bar', 'baz'), 'two', - ); - $data[] = array( - array('foo', 'bar', 'baz'), NULL, - ); - $data[] = array( + ]; + $data[] = [ + ['bar', 'baz'], 'two', + ]; + $data[] = [ + ['foo', 'bar', 'baz'], NULL, + ]; + $data[] = [ 'baz', 'baz', 'baz', - ); - $data[] = array( + ]; + $data[] = [ NULL, [ 'foo' => 'one', @@ -78,7 +87,7 @@ public function providerTestGetValue() { 'baz' => 'two', ], ], - ); + ]; return $data; } @@ -105,7 +114,7 @@ public function testGetValueModifyReturn() { /** * @covers ::setValue * - * @dataProvider providerTestSetValue + * @dataProvider providerSetValue */ public function testSetValue($key, $value, $expected) { $form_state = (new FormStateValuesTraitStub())->setValues([ @@ -115,31 +124,40 @@ public function testSetValue($key, $value, $expected) { $this->assertSame($expected, $form_state->getValues()); } - public function providerTestSetValue() { - $data = array(); - $data[] = array( - 'foo', 'one', array('bar' => 'wrong', 'foo' => 'one'), - ); - $data[] = array( - array('bar', 'baz'), 'two', array('bar' => array('baz' => 'two')), - ); - $data[] = array( - array('foo', 'bar', 'baz'), NULL, array('bar' => 'wrong', 'foo' => array('bar' => array('baz' => NULL))), - ); + /** + * Provides data to self::testSetValue(). + * + * @return array[] + * Items are arrays of two items: + * - The key for which to set a new value (string) + * - The new value to set (mixed). + * - The expected form state values after setting the new value (mixed[]). + */ + public function providerSetValue() { + $data = []; + $data[] = [ + 'foo', 'one', ['bar' => 'wrong', 'foo' => 'one'], + ]; + $data[] = [ + ['bar', 'baz'], 'two', ['bar' => ['baz' => 'two']], + ]; + $data[] = [ + ['foo', 'bar', 'baz'], NULL, ['bar' => 'wrong', 'foo' => ['bar' => ['baz' => NULL]]], + ]; return $data; } /** * @covers ::hasValue * - * @dataProvider providerTestHasValue + * @dataProvider providerHasValue */ public function testHasValue($key, $expected) { $form_state = (new FormStateValuesTraitStub())->setValues([ 'foo' => 'one', - 'bar' => array( + 'bar' => [ 'baz' => 'two', - ), + ], 'true' => TRUE, 'false' => FALSE, 'null' => NULL, @@ -147,40 +165,48 @@ public function testHasValue($key, $expected) { $this->assertSame($expected, $form_state->hasValue($key)); } - public function providerTestHasValue() { - $data = array(); - $data[] = array( + /** + * Provides data to self::testHasValue(). + * + * @return array[] + * Items are arrays of two items: + * - The key to check for in the form state (string) + * - Whether the form state has an item with that key (bool). + */ + public function providerHasValue() { + $data = []; + $data[] = [ 'foo', TRUE, - ); - $data[] = array( - array('bar', 'baz'), TRUE, - ); - $data[] = array( - array('foo', 'bar', 'baz'), FALSE, - ); - $data[] = array( + ]; + $data[] = [ + ['bar', 'baz'], TRUE, + ]; + $data[] = [ + ['foo', 'bar', 'baz'], FALSE, + ]; + $data[] = [ 'true', TRUE, - ); - $data[] = array( + ]; + $data[] = [ 'false', TRUE, - ); - $data[] = array( + ]; + $data[] = [ 'null', FALSE, - ); + ]; return $data; } /** * @covers ::isValueEmpty * - * @dataProvider providerTestIsValueEmpty + * @dataProvider providerIsValueEmpty */ public function testIsValueEmpty($key, $expected) { $form_state = (new FormStateValuesTraitStub())->setValues([ 'foo' => 'one', - 'bar' => array( + 'bar' => [ 'baz' => 'two', - ), + ], 'true' => TRUE, 'false' => FALSE, 'null' => NULL, @@ -188,31 +214,54 @@ public function testIsValueEmpty($key, $expected) { $this->assertSame($expected, $form_state->isValueEmpty($key)); } - public function providerTestIsValueEmpty() { - $data = array(); - $data[] = array( + /** + * Provides data to self::testIsValueEmpty(). + * + * @return array[] + * Items are arrays of two items: + * - The key to check for in the form state (string) + * - Whether the value is empty or not (bool). + */ + public function providerIsValueEmpty() { + $data = []; + $data[] = [ 'foo', FALSE, - ); - $data[] = array( - array('bar', 'baz'), FALSE, - ); - $data[] = array( - array('foo', 'bar', 'baz'), TRUE, - ); - $data[] = array( + ]; + $data[] = [ + ['bar', 'baz'], FALSE, + ]; + $data[] = [ + ['foo', 'bar', 'baz'], TRUE, + ]; + $data[] = [ 'true', FALSE, - ); - $data[] = array( + ]; + $data[] = [ 'false', TRUE, - ); - $data[] = array( + ]; + $data[] = [ 'null', TRUE, - ); + ]; return $data; } } class FormStateValuesTraitStub { + use FormStateValuesTrait; + + /** + * The submitted form values. + * + * @var mixed[] + */ + protected $values = []; + + /** + * {@inheritdoc} + */ + public function &getValues() { + return $this->values; + } } diff --git a/core/tests/Drupal/Tests/Core/Form/SubFormStateTest.php b/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php similarity index 73% rename from core/tests/Drupal/Tests/Core/Form/SubFormStateTest.php rename to core/tests/Drupal/Tests/Core/Form/SubformStateTest.php index d516d9a..3ec3619 100644 --- a/core/tests/Drupal/Tests/Core/Form/SubFormStateTest.php +++ b/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Tests\Core\Form\SubFormStateTest. + * Contains \Drupal\Tests\Core\Form\SubformStateTest. */ namespace Drupal\Tests\Core\Form; @@ -10,14 +10,16 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformState; +use Drupal\Core\Form\SubformStateInterface; use Drupal\Tests\UnitTestCase; /** - * @coversDefaultClass \Drupal\Core\Form\SubFormState + * @coversDefaultClass \Drupal\Core\Form\SubformState * * @group Form */ -class SubFormStateTest extends UnitTestCase { +class SubformStateTest extends UnitTestCase { /** * Test fixture. @@ -51,25 +53,9 @@ class SubFormStateTest extends UnitTestCase { ]; /** - * @covers ::getSubFormState - */ - public function testGetSubFormState() { - $form_state = new FormState(); - $element = [ - '#parents' => ['foo'], - '#array_parents' => ['foo'], - 'bar' => [], - ]; - - $sub_form_state = $form_state->getSubFormState($element); - $this->assertNotSame($form_state, $sub_form_state); - $this->assertInstanceOf(FormStateInterface::class, $sub_form_state); - } - - /** * @covers ::getValues * @covers ::getParents - * @covers ::getSubFormProperty + * @covers ::getSubformProperty * * @dataProvider providerTestGetValues * @@ -81,8 +67,8 @@ public function testGetValues(array $parents, $expected) { $form_state->setValues($this->initialValues); $element = NestedArray::getValue($this->element, $parents); - $sub_form_state = $form_state->getSubFormState($element); - $sub_values = $sub_form_state->getValues(); + $subform_state = SubformState::createForSubform($element, $form_state); + $sub_values = $subform_state->getValues(); $this->assertSame($expected, $sub_values); } @@ -102,7 +88,7 @@ public function providerTestGetValues() { /** * @covers ::getValues * @covers ::getParents - * @covers ::getSubFormProperty + * @covers ::getSubformProperty * * @dataProvider providerTestGetValuesBroken * @@ -142,8 +128,8 @@ public function testGetValue($parents, $key, $expected, $default = NULL) { $form_state->setValues($this->initialValues); $element = NestedArray::getValue($this->element, $parents); - $sub_form_state = $form_state->getSubFormState($element); - $sub_values = $sub_form_state->getValue($key, $default); + $subform_state = SubformState::createForSubform($element, $form_state); + $sub_values = $subform_state->getValue($key, $default); $this->assertSame($expected, $sub_values); } @@ -196,8 +182,8 @@ public function testSetValues($parents, $new_values, $expected) { $form_state->setValues($this->initialValues); $element = NestedArray::getValue($this->element, $parents); - $sub_form_state = $form_state->getSubFormState($element); - $sub_form_state->setValues($new_values); + $subform_state = SubformState::createForSubform($element, $form_state); + $subform_state->setValues($new_values); $this->assertSame($expected, $form_state->getValues()); } @@ -244,4 +230,28 @@ public function providerTestSetValuesBroken() { return $data; } + /** + * @covers ::getCompleteFormState + */ + public function testGetCompleteFormStateWithParentCompleteForm() { + $decorated_form_state = $this->getMock(FormStateInterface::class); + $subform = []; + $subform_state = SubformState::createForSubform($subform, $decorated_form_state); + $this->assertSame($decorated_form_state, $subform_state->getCompleteFormState()); + } + + /** + * @covers ::getCompleteFormState + */ + public function testGetCompleteFormStateWithParentSubform() { + $form_state = $this->getMock(FormStateInterface::class); + $decorated_form_state = $this->getMock(SubformStateInterface::class); + $decorated_form_state->expects($this->atLeastOnce()) + ->method('getCompleteFormState') + ->willReturn($form_state); + $subform = []; + $subform_state = SubformState::createForSubform($subform, $decorated_form_state); + $this->assertSame($form_state, $subform_state->getCompleteFormState()); + } + }