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"),
36 class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayInterface {
41 protected $displayContext = 'form';
44 * Returns the entity_form_display object used to build an entity form.
46 * Depending on the configuration of the form mode for the entity bundle, this
47 * can be either the display object associated with the form mode, or the
50 * This method should only be used internally when rendering an entity form.
51 * When assigning suggested display options for a component in a given form
52 * mode, entity_get_form_display() should be used instead, in order to avoid
53 * inadvertently modifying the output of other form modes that might happen to
54 * use the 'default' display too. Those options will then be effectively
55 * applied only if the form mode is configured to use them.
57 * hook_entity_form_display_alter() is invoked on each display, allowing 3rd
58 * party code to alter the display options held in the display before they are
59 * used to generate render arrays.
61 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
62 * The entity for which the form is being built.
63 * @param string $form_mode
66 * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
67 * The display object that should be used to build the entity form.
69 * @see entity_get_form_display()
70 * @see hook_entity_form_display_alter()
72 public static function collectRenderDisplay(FieldableEntityInterface $entity, $form_mode) {
73 $entity_type = $entity->getEntityTypeId();
74 $bundle = $entity->bundle();
76 // Check the existence and status of:
77 // - the display for the form mode,
78 // - the 'default' display.
79 if ($form_mode != 'default') {
80 $candidate_ids[] = $entity_type . '.' . $bundle . '.' . $form_mode;
82 $candidate_ids[] = $entity_type . '.' . $bundle . '.default';
83 $results = \Drupal::entityQuery('entity_form_display')
84 ->condition('id', $candidate_ids)
85 ->condition('status', TRUE)
88 // Load the first valid candidate display, if any.
89 $storage = \Drupal::entityManager()->getStorage('entity_form_display');
90 foreach ($candidate_ids as $candidate_id) {
91 if (isset($results[$candidate_id])) {
92 $display = $storage->load($candidate_id);
96 // Else create a fresh runtime object.
97 if (empty($display)) {
98 $display = $storage->create([
99 'targetEntityType' => $entity_type,
101 'mode' => $form_mode,
106 // Let the display know which form mode was originally requested.
107 $display->originalMode = $form_mode;
109 // Let modules alter the display.
111 'entity_type' => $entity_type,
113 'form_mode' => $form_mode,
115 \Drupal::moduleHandler()->alter('entity_form_display', $display, $display_context);
123 public function __construct(array $values, $entity_type) {
124 $this->pluginManager = \Drupal::service('plugin.manager.field.widget');
126 parent::__construct($values, $entity_type);
132 public function getRenderer($field_name) {
133 if (isset($this->plugins[$field_name])) {
134 return $this->plugins[$field_name];
137 // Instantiate the widget object from the stored display properties.
138 if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
139 $widget = $this->pluginManager->getInstance([
140 'field_definition' => $definition,
141 'form_mode' => $this->originalMode,
142 // No need to prepare, defaults have been merged in setComponent().
144 'configuration' => $configuration
151 // Persist the widget object.
152 $this->plugins[$field_name] = $widget;
159 public function buildForm(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
160 // Set #parents to 'top-level' by default.
161 $form += ['#parents' => []];
163 // Let each widget generate the form elements.
164 foreach ($this->getComponents() as $name => $options) {
165 if ($widget = $this->getRenderer($name)) {
166 $items = $entity->get($name);
167 $items->filterEmptyItems();
168 $form[$name] = $widget->form($items, $form, $form_state);
169 $form[$name]['#access'] = $items->access('edit');
171 // Assign the correct weight. This duplicates the reordering done in
172 // processForm(), but is needed for other forms calling this method
174 $form[$name]['#weight'] = $options['weight'];
176 // Associate the cache tags for the field definition & field storage
178 $field_definition = $this->getFieldDefinition($name);
179 $this->renderer->addCacheableDependency($form[$name], $field_definition);
180 $this->renderer->addCacheableDependency($form[$name], $field_definition->getFieldStorageDefinition());
184 // Associate the cache tags for the form display.
185 $this->renderer->addCacheableDependency($form, $this);
187 // Add a process callback so we can assign weights and hide extra fields.
188 $form['#process'][] = [$this, 'processForm'];
192 * Process callback: assigns weights and hides extra fields.
194 * @see \Drupal\Core\Entity\Entity\EntityFormDisplay::buildForm()
196 public function processForm($element, FormStateInterface $form_state, $form) {
197 // Assign the weights configured in the form display.
198 foreach ($this->getComponents() as $name => $options) {
199 if (isset($element[$name])) {
200 $element[$name]['#weight'] = $options['weight'];
204 // Hide extra fields.
205 $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
206 $extra_fields = isset($extra_fields['form']) ? $extra_fields['form'] : [];
207 foreach ($extra_fields as $extra_field => $info) {
208 if (!$this->getComponent($extra_field)) {
209 $element[$extra_field]['#access'] = FALSE;
218 public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
220 foreach ($entity as $name => $items) {
221 if ($widget = $this->getRenderer($name)) {
222 $widget->extractFormValues($items, $form, $form_state);
223 $extracted[$name] = $name;
232 public function validateFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) {
233 $violations = $entity->validate();
234 $violations->filterByFieldAccess();
236 // Flag entity level violations.
237 foreach ($violations->getEntityViolations() as $violation) {
238 /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
239 $form_state->setError($form, $violation->getMessage());
242 $this->flagWidgetsErrorsFromViolations($violations, $form, $form_state);
248 public function flagWidgetsErrorsFromViolations(EntityConstraintViolationListInterface $violations, array &$form, FormStateInterface $form_state) {
249 $entity = $violations->getEntity();
250 foreach ($violations->getFieldNames() as $field_name) {
251 // Only show violations for fields that actually appear in the form, and
252 // let the widget assign the violations to the correct form elements.
253 if ($widget = $this->getRenderer($field_name)) {
254 $field_violations = $this->movePropertyPathViolationsRelativeToField($field_name, $violations->getByField($field_name));
255 $widget->flagErrors($entity->get($field_name), $field_violations, $form, $form_state);
261 * Moves the property path to be relative to field level.
263 * @param string $field_name
265 * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
268 * @return \Symfony\Component\Validator\ConstraintViolationList
269 * A new constraint violation list with the changed property path.
271 protected function movePropertyPathViolationsRelativeToField($field_name, ConstraintViolationListInterface $violations) {
272 $new_violations = new ConstraintViolationList();
273 foreach ($violations as $violation) {
274 // All the logic below is necessary to change the property path of the
275 // violations to be relative to the item list, so like title.0.value gets
276 // changed to 0.value. Sadly constraints in Symfony don't have setters so
277 // we have to create new objects.
278 /** @var \Symfony\Component\Validator\ConstraintViolationInterface $violation */
279 // Create a new violation object with just a different property path.
280 $violation_path = $violation->getPropertyPath();
281 $path_parts = explode('.', $violation_path);
282 if ($path_parts[0] === $field_name) {
283 unset($path_parts[0]);
285 $new_path = implode('.', $path_parts);
291 if ($violation instanceof ConstraintViolation) {
292 $constraint = $violation->getConstraint();
293 $cause = $violation->getCause();
294 $parameters = $violation->getParameters();
295 $plural = $violation->getPlural();
298 $new_violation = new ConstraintViolation(
299 $violation->getMessage(),
300 $violation->getMessageTemplate(),
302 $violation->getRoot(),
304 $violation->getInvalidValue(),
306 $violation->getCode(),
310 $new_violations->add($new_violation);
312 return $new_violations;
318 public function getPluginCollections() {
319 $configurations = [];
320 foreach ($this->getComponents() as $field_name => $configuration) {
321 if (!empty($configuration['type']) && ($field_definition = $this->getFieldDefinition($field_name))) {
322 $configurations[$configuration['type']] = $configuration + [
323 'field_definition' => $field_definition,
324 'form_mode' => $this->mode,
330 'widgets' => new EntityDisplayPluginCollection($this->pluginManager, $configurations)