3 namespace Drupal\Core\Entity\Entity;
5 use Drupal\Core\Entity\EntityConstraintViolationListInterface;
6 use Drupal\Core\Entity\EntityDisplayPluginCollection;
7 use Drupal\Core\Entity\FieldableEntityInterface;
8 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
9 use Drupal\Core\Entity\EntityDisplayBase;
10 use Drupal\Core\Form\FormStateInterface;
11 use Symfony\Component\Validator\ConstraintViolation;
12 use Symfony\Component\Validator\ConstraintViolationList;
13 use Symfony\Component\Validator\ConstraintViolationListInterface;
16 * Configuration entity that contains widget options for all components of a
17 * entity form in a given form mode.
20 * id = "entity_form_display",
21 * label = @Translation("Entity form display"),
27 * "access" = "\Drupal\Core\Entity\Entity\Access\EntityFormDisplayAccessControlHandler",
39 class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayInterface {
44 protected $displayContext = 'form';
47 * Returns the entity_form_display object used to build an entity form.
49 * Depending on the configuration of the form mode for the entity bundle, this
50 * can be either the display object associated with the form mode, or the
53 * This method should only be used internally when rendering an entity form.
54 * When assigning suggested display options for a component in a given form
55 * mode, entity_get_form_display() should be used instead, in order to avoid
56 * inadvertently modifying the output of other form modes that might happen to
57 * use the 'default' display too. Those options will then be effectively
58 * applied only if the form mode is configured to use them.
60 * hook_entity_form_display_alter() is invoked on each display, allowing 3rd
61 * party code to alter the display options held in the display before they are
62 * used to generate render arrays.
64 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
65 * The entity for which the form is being built.
66 * @param string $form_mode
69 * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
70 * The display object that should be used to build the entity form.
72 * @see entity_get_form_display()
73 * @see hook_entity_form_display_alter()
75 public static function collectRenderDisplay(FieldableEntityInterface $entity, $form_mode) {
76 $entity_type = $entity->getEntityTypeId();
77 $bundle = $entity->bundle();
79 // Check the existence and status of:
80 // - the display for the form mode,
81 // - the 'default' display.
82 if ($form_mode != 'default') {
83 $candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode;
85 $candidate_ids[] = $entity_type . '.' . $bundle . '.default';
86 $results = \Drupal::entityQuery('entity_form_display')
87 ->condition('id', $candidate_ids)
88 ->condition('status', TRUE)
91 // Load the first valid candidate display, if any.
92 $storage = \Drupal::entityManager()->getStorage('entity_form_display');
93 foreach ($candidate_ids as $candidate_id) {
94 if (isset($results[$candidate_id])) {
95 $display = $storage->load($candidate_id);
99 // Else create a fresh runtime object.
100 if (empty($display)) {
101 $display = $storage->create([
102 'targetEntityType' => $entity_type,
104 'mode' => $form_mode,
109 // Let the display know which form mode was originally requested.
110 $display->originalMode = $form_mode;
112 // Let modules alter the display.
114 'entity_type' => $entity_type,
116 'form_mode' => $form_mode,
118 \Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context);
126 public function __construct(array $values, $entity_type) {
127 $this->pluginManager = \Drupal::service('plugin.manager.field.widget');
129 parent::__construct($values, $entity_type);
135 public function getRenderer($field_name) {
136 if (isset($this->plugins[$field_name])) {
137 return $this->plugins[$field_name];
140 // Instantiate the widget object from the stored display properties.
141 if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
142 $widget = $this->pluginManager->getInstance([
143 'field_definition' => $definition,
144 'form_mode' => $this->originalMode,
145 // No need to prepare, defaults have been merged in setComponent().
147 'configuration' => $configuration,
154 // Persist the widget object.
155 $this->plugins[$field_name] = $widget;
162 public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
163 // Set #parents to 'top-level' by default.
164 $form += ['#parents' => []];
166 // Let each widget generate the form elements.
167 foreach ($this->getComponents() as $name => $options) {
168 if ($widget = $this->getRenderer($name)) {
169 $items = $entity->get($name);
170 $items->filterEmptyItems();
171 $form[$name] = $widget->form($items, $form, $form_state);
172 $form[$name]['#access'] = $items->access('edit');
174 // Assign the correct weight. This duplicates the reordering done in
175 // processForm(), but is needed for other forms calling this method
177 $form[$name]['#weight'] = $options['weight'];
179 // Associate the cache tags for the field definition & field storage
181 $field_definition = $this->getFieldDefinition($name);
182 $this->renderer->addCacheableDependency($form[$name], $field_definition);
183 $this->renderer->addCacheableDependency($form[$name], $field_definition->getFieldStorageDefinition());
187 // Associate the cache tags for the form display.
188 $this->renderer->addCacheableDependency($form, $this);
190 // Add a process callback so we can assign weights and hide extra fields.
191 $form['#process'][] = [$this, 'processForm'];
195 * Process callback: assigns weights and hides extra fields.
197 * @see \Drupal\Core\Entity\Entity\EntityFormDisplay::buildForm()
199 public function processForm($element, FormStateInterface $form_state, $form) {
200 // Assign the weights configured in the form display.
201 foreach ($this->getComponents() as $name => $options) {
202 if (isset($element[$name])) {
203 $element[$name]['#weight'] = $options['weight'];
207 // Hide extra fields.
208 $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
209 $extra_fields = isset($extra_fields['form']) ? $extra_fields['form'] : [];
210 foreach ($extra_fields as $extra_field => $info) {
211 if (!$this->getComponent($extra_field)) {
212 $element[$extra_field]['#access'] = FALSE;
221 public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
223 foreach ($entity as $name => $items) {
224 if ($widget = $this->getRenderer($name)) {
225 $widget->extractFormValues($items, $form, $form_state);
226 $extracted[$name] = $name;
235 public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
236 $violations = $entity->validate();
237 $violations->filterByFieldAccess();
239 // Flag entity level violations.
240 foreach ($violations->getEntityViolations() as $violation) {
241 /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
242 $form_state->setError($form, $violation->getMessage());
245 $this->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
251 public function flagWidgetsErrorsFromViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state) {
252 $entity = $violations->getEntity();
253 foreach ($violations->getFieldNames() as $field_name) {
254 // Only show violations for fields that actually appear in the form, and
255 // let the widget assign the violations to the correct form elements.
256 if ($widget = $this->getRenderer($field_name)) {
257 $field_violations = $this->movePropertyPathViolationsRelativeToField($field_name, $violations->getByField($field_name));
258 $widget->flagErrors($entity->get($field_name), $field_violations, $form, $form_state);
264 * Moves the property path to be relative to field level.
266 * @param string $field_name
268 * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
271 * @return \Symfony\Component\Validator\ConstraintViolationList
272 * A new constraint violation list with the changed property path.
274 protected function movePropertyPathViolationsRelativeToField($field_name, ConstraintViolationListInterface $violations) {
275 $new_violations = new ConstraintViolationList();
276 foreach ($violations as $violation) {
277 // All the logic below is necessary to change the property path of the
278 // violations to be relative to the item list, so like title.0.value gets
279 // changed to 0.value. Sadly constraints in Symfony don't have setters so
280 // we have to create new objects.
281 /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
282 // Create a new violation object with just a different property path.
283 $violation_path = $violation->getPropertyPath();
284 $path_parts = explode('.', $violation_path);
285 if ($path_parts[0] === $field_name) {
286 unset($path_parts[0]);
288 $new_path = implode('.', $path_parts);
294 if ($violation instanceof ConstraintViolation) {
295 $constraint = $violation->getConstraint();
296 $cause = $violation->getCause();
297 $parameters = $violation->getParameters();
298 $plural = $violation->getPlural();
301 $new_violation = new ConstraintViolation(
302 $violation->getMessage(),
303 $violation->getMessageTemplate(),
305 $violation->getRoot(),
307 $violation->getInvalidValue(),
309 $violation->getCode(),
313 $new_violations->add($new_violation);
315 return $new_violations;
321 public function getPluginCollections() {
322 $configurations = [];
323 foreach ($this->getComponents() as $field_name => $configuration) {
324 if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
325 $configurations[$configuration['type']] = $configuration + [
326 'field_definition' => $field_definition,
327 'form_mode' => $this->mode,
333 'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations),