1<?php 2 3namespace Drupal\Core\Form; 4 5use Drupal\Component\Utility\NestedArray; 6 7/** 8 * Stores information about the state of a subform. 9 */ 10class SubformState extends FormStateDecoratorBase implements SubformStateInterface { 11 12 use FormStateValuesTrait; 13 14 /** 15 * The parent form. 16 * 17 * @var mixed[] 18 */ 19 protected $parentForm; 20 21 /** 22 * The subform. 23 * 24 * @var mixed[] 25 */ 26 protected $subform; 27 28 /** 29 * Constructs a new instance. 30 * 31 * @param mixed[] $subform 32 * The subform for which to create a form state. 33 * @param mixed[] $parent_form 34 * The subform's parent form. 35 * @param \Drupal\Core\Form\FormStateInterface $parent_form_state 36 * The parent form state. 37 */ 38 protected function __construct(array &$subform, array &$parent_form, FormStateInterface $parent_form_state) { 39 $this->decoratedFormState = $parent_form_state; 40 $this->parentForm = $parent_form; 41 $this->subform = $subform; 42 } 43 44 /** 45 * Creates a new instance for a subform. 46 * 47 * @param mixed[] $subform 48 * The subform for which to create a form state. 49 * @param mixed[] $parent_form 50 * The subform's parent form. 51 * @param \Drupal\Core\Form\FormStateInterface $parent_form_state 52 * The parent form state. 53 * 54 * @return static 55 */ 56 public static function createForSubform(array &$subform, array &$parent_form, FormStateInterface $parent_form_state) { 57 return new static($subform, $parent_form, $parent_form_state); 58 } 59 60 /** 61 * Gets the subform's parents relative to its parent form. 62 * 63 * @param string $property 64 * The property name (#parents or #array_parents). 65 * 66 * @return mixed 67 * 68 * @throws \InvalidArgumentException 69 * Thrown when the requested property does not exist. 70 * @throws \UnexpectedValueException 71 * Thrown when the subform is not contained by the given parent form. 72 */ 73 protected function getParents($property) { 74 foreach ([$this->subform, $this->parentForm] as $form) { 75 if (!isset($form[$property]) || !is_array($form[$property])) { 76 throw new \RuntimeException(sprintf('The subform and parent form must contain the %s property, which must be an array. Try calling this method from a #process callback instead.', $property)); 77 } 78 } 79 80 $relative_subform_parents = $this->subform[$property]; 81 // Remove all of the subform's parents that are also the parent form's 82 // parents, so we are left with the parents relative to the parent form. 83 foreach ($this->parentForm[$property] as $parent_form_parent) { 84 if ($parent_form_parent !== $relative_subform_parents[0]) { 85 // The parent form's parents are the subform's parents as well. If we 86 // find no match, that means the given subform is not contained by the 87 // given parent form. 88 throw new \UnexpectedValueException('The subform is not contained by the given parent form.'); 89 } 90 array_shift($relative_subform_parents); 91 } 92 93 return $relative_subform_parents; 94 } 95 96 /** 97 * {@inheritdoc} 98 */ 99 public function &getValues() { 100 $exists = NULL; 101 $values = &NestedArray::getValue(parent::getValues(), $this->getParents('#parents'), $exists); 102 if (!$exists) { 103 $values = []; 104 } 105 elseif (!is_array($values)) { 106 throw new \UnexpectedValueException('The form state values do not belong to the subform.'); 107 } 108 109 return $values; 110 } 111 112 /** 113 * {@inheritdoc} 114 */ 115 public function getCompleteFormState() { 116 return $this->decoratedFormState instanceof SubformStateInterface ? $this->decoratedFormState->getCompleteFormState() : $this->decoratedFormState; 117 } 118 119 /** 120 * {@inheritdoc} 121 */ 122 public function setLimitValidationErrors($limit_validation_errors) { 123 if (is_array($limit_validation_errors)) { 124 $limit_validation_errors = array_merge($this->getParents('#parents'), $limit_validation_errors); 125 } 126 127 return parent::setLimitValidationErrors($limit_validation_errors); 128 } 129 130 /** 131 * {@inheritdoc} 132 */ 133 public function getLimitValidationErrors() { 134 $limit_validation_errors = parent::getLimitValidationErrors(); 135 if (is_array($limit_validation_errors)) { 136 return array_slice($limit_validation_errors, count($this->getParents('#parents'))); 137 138 } 139 return $limit_validation_errors; 140 } 141 142 /** 143 * {@inheritdoc} 144 */ 145 public function setErrorByName($name, $message = '') { 146 $parents = $this->subform['#array_parents']; 147 $parents[] = $name; 148 $name = implode('][', $parents); 149 parent::setErrorByName($name, $message); 150 151 return $this; 152 } 153 154} 155