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