summaryrefslogtreecommitdiffstatshomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/.phpstan-baseline.php6
-rw-r--r--core/MAINTAINERS.txt3
-rw-r--r--core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php25
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php5
-rw-r--r--core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php22
-rw-r--r--core/lib/Drupal/Core/Field/WidgetBase.php39
-rw-r--r--core/lib/Drupal/Core/Field/WidgetInterface.php51
-rw-r--r--core/lib/Drupal/Core/Form/FormBase.php31
-rw-r--r--core/lib/Drupal/Core/Render/Element/Button.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/Checkbox.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Checkboxes.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Color.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/ComponentElement.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/Container.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Date.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/Details.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/Dropbutton.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/ElementInterface.php113
-rw-r--r--core/lib/Drupal/Core/Render/Element/Email.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/File.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/FormElement.php3
-rw-r--r--core/lib/Drupal/Core/Render/Element/FormElementBase.php141
-rw-r--r--core/lib/Drupal/Core/Render/Element/Generic.php31
-rw-r--r--core/lib/Drupal/Core/Render/Element/Hidden.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/HtmlTag.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/Icon.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/InlineTemplate.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/Link.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/MachineName.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/MoreLink.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Number.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/Page.php2
-rw-r--r--core/lib/Drupal/Core/Render/Element/Pager.php19
-rw-r--r--core/lib/Drupal/Core/Render/Element/Password.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/PasswordConfirm.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Radios.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Range.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/RenderElement.php3
-rw-r--r--core/lib/Drupal/Core/Render/Element/RenderElementBase.php410
-rw-r--r--core/lib/Drupal/Core/Render/Element/Select.php28
-rw-r--r--core/lib/Drupal/Core/Render/Element/Submit.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/Table.php22
-rw-r--r--core/lib/Drupal/Core/Render/Element/Tableselect.php16
-rw-r--r--core/lib/Drupal/Core/Render/Element/Tel.php7
-rw-r--r--core/lib/Drupal/Core/Render/Element/Textarea.php13
-rw-r--r--core/lib/Drupal/Core/Render/Element/Textfield.php18
-rw-r--r--core/lib/Drupal/Core/Render/Element/TitleDisplay.php22
-rw-r--r--core/lib/Drupal/Core/Render/Element/Url.php10
-rw-r--r--core/lib/Drupal/Core/Render/Element/Value.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/VerticalTabs.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Weight.php4
-rw-r--r--core/lib/Drupal/Core/Render/Element/Widget.php26
-rw-r--r--core/lib/Drupal/Core/Render/ElementInfoManager.php137
-rw-r--r--core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php62
-rw-r--r--core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php28
-rw-r--r--core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php8
-rw-r--r--core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php8
-rw-r--r--core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php6
-rw-r--r--core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php45
-rw-r--r--core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php10
-rw-r--r--core/modules/filter/src/Element/TextFormat.php10
-rw-r--r--core/modules/layout_builder/src/Element/LayoutBuilder.php10
-rw-r--r--core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php35
-rw-r--r--core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php8
-rw-r--r--core/modules/node/node.module4
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php3
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php6
-rw-r--r--core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php57
-rw-r--r--core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php26
-rw-r--r--core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php21
-rw-r--r--core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php25
-rw-r--r--core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php3
-rw-r--r--core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php3
-rw-r--r--core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php60
-rw-r--r--core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php5
-rw-r--r--core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php3
76 files changed, 1458 insertions, 389 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index 07367cd8a2d0..e0660c7dc93e 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -9716,12 +9716,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/lib/Drupal/Core/Render/Element/Weight.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Core\\\\Render\\\\ElementInfoManager\\:\\:clearCachedDefinitions\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/lib/Drupal/Core/Render/ElementInfoManager.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\Core\\\\Render\\\\HtmlResponseAttachmentsProcessor\\:\\:renderHtmlResponseAttachmentPlaceholders\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index 91d836c34659..648959a56b32 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -120,14 +120,12 @@ Cache
- Kristiaan Van den Eynde 'kristiaanvandeneynde' https://www.drupal.org/u/kristiaanvandeneynde
CKEditor 5
-- Lauri Timmanee 'lauriii' https://www.drupal.org/u/lauriii
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
- Ben Mullins 'bnjmnm' https://www.drupal.org/u/bnjmnm
Claro
- Cristina Chumillas 'ckrina' https://www.drupal.org/u/ckrina
- Sascha Eggenberger 'saschaeggi' https://www.drupal.org/u/saschaeggi
-- Lauri Timmanee 'lauriii' https://www.drupal.org/u/lauriii
- Ben Mullins 'bnjmnm' https://www.drupal.org/u/bnjmnm
Comment
@@ -260,7 +258,6 @@ JavaScript
JSON:API
- Mateu Aguiló Bosch 'e0ipso' https://www.drupal.org/u/e0ipso
- Björn Brala 'bbrala' https://www.drupal.org/u/bbrala
-- Gabe Sullice 'gabesullice' https://www.drupal.org/u/gabesullice
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
Language
diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
index 132aaa439974..11422c6790c9 100644
--- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
+++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php
@@ -19,29 +19,38 @@ use Drupal\Core\Site\Settings;
* entities, which can come from all or specific bundles of an entity type.
*
* Properties:
- * - #target_type: (required) The ID of the target entity type.
- * - #tags: (optional) TRUE if the element allows multiple selection. Defaults
+ *
+ * @property $target_type
+ * (required) The ID of the target entity type.
+ * @property $tags
+ * (optional) TRUE if the element allows multiple selection. Defaults
* to FALSE.
- * - #default_value: (optional) The default entity or an array of default
+ * @property $default_value
+ * (optional) The default entity or an array of default
* entities, depending on the value of #tags.
- * - #selection_handler: (optional) The plugin ID of the entity reference
+ * @property $selection_handler
+ * (optional) The plugin ID of the entity reference
* selection handler (a plugin of type EntityReferenceSelection). The default
* value is the lowest-weighted plugin that is compatible with #target_type.
- * - #selection_settings: (optional) An array of settings for the selection
+ * @property $selection_settings
+ * (optional) An array of settings for the selection
* handler. Settings for the default selection handler
* \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection are:
* - target_bundles: Array of bundles to allow (omit to allow all bundles).
* - sort: Array with 'field' and 'direction' keys, determining how results
* will be sorted. Defaults to unsorted.
- * - #autocreate: (optional) Array of settings used to auto-create entities
+ * @property $autocreate
+ * (optional) Array of settings used to auto-create entities
* that do not exist (omit to not auto-create entities). Elements:
* - bundle: (required) Bundle to use for auto-created entities.
* - uid: User ID to use as the author of auto-created entities. Defaults to
* the current user.
- * - #process_default_value: (optional) Set to FALSE if the #default_value
+ * @property $process_default_value
+ * (optional) Set to FALSE if the #default_value
* property is processed and access checked elsewhere (such as by a Field API
* widget). Defaults to TRUE.
- * - #validate_reference: (optional) Set to FALSE if validation of the selected
+ * @property $validate_reference
+ * (optional) Set to FALSE if validation of the selected
* entities is performed elsewhere. Defaults to TRUE.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php
index 60920fad60f5..2a3e3657354e 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/OptionsWidgetBase.php
@@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\OptGroup;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
@@ -56,8 +57,8 @@ abstract class OptionsWidgetBase extends WidgetBase {
/**
* {@inheritdoc}
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ?ElementInfoManagerInterface $elementInfoManager = NULL) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
$property_names = $this->fieldDefinition->getFieldStorageDefinition()->getPropertyNames();
$this->column = $property_names[0];
}
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php
index 1523e4f618f7..2f8893243111 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/StringTextfieldWidget.php
@@ -6,6 +6,9 @@ use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Textfield;
+use Drupal\Core\Render\Element\Widget;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
@@ -66,17 +69,14 @@ class StringTextfieldWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
- public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
- $element['value'] = $element + [
- '#type' => 'textfield',
- '#default_value' => $items[$delta]->value ?? NULL,
- '#size' => $this->getSetting('size'),
- '#placeholder' => $this->getSetting('placeholder'),
- '#maxlength' => $this->getFieldSetting('max_length'),
- '#attributes' => ['class' => ['js-text-full', 'text-full']],
- ];
-
- return $element;
+ public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
+ $value = $widget->createChild('value', Textfield::class, copyProperties: TRUE);
+ $value->default_value = $items[$delta]->value ?? NULL;
+ $value->size = $this->getSetting('size');
+ $value->placeholder = $this->getSetting('placeholder');
+ $value->maxlength = $this->getFieldSetting('max_length');
+ $value->attributes = ['class' => ['js-text-full', 'text-full']];
+ return $widget;
}
}
diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index 5c3da0a1bf59..b6d7a283830e 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -11,6 +11,9 @@ use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Widget;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationListInterface;
@@ -49,19 +52,32 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface,
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager
+ * The element info manager.
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings) {
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, protected ?ElementInfoManagerInterface $elementInfoManager = NULL) {
parent::__construct([], $plugin_id, $plugin_definition);
$this->fieldDefinition = $field_definition;
$this->settings = $settings;
$this->thirdPartySettings = $third_party_settings;
+ if (!$this->elementInfoManager) {
+ @trigger_error('Calling ' . __METHOD__ . '() without the $elementInfoManager argument is deprecated in drupal:11.3.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3526683', E_USER_DEPRECATED);
+ $this->elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ }
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
- return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings']);
+ return new static(
+ $plugin_id,
+ $plugin_definition,
+ $configuration['field_definition'],
+ $configuration['settings'],
+ $configuration['third_party_settings'],
+ $container->get('plugin.manager.element_info'),
+ );
}
/**
@@ -461,7 +477,9 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface,
'#weight' => $delta,
];
- $element = $this->formElement($items, $delta, $element, $form, $form_state);
+ $formObject = $this->elementInfoManager->fromRenderable($form);
+ $widget = $this->elementInfoManager->fromRenderable($element, Widget::class);
+ $element = $this->singleElementObject($items, $delta, $widget, $formObject, $form_state)->toRenderable();
if ($element) {
// Allow modules to alter the field widget form element.
@@ -484,6 +502,21 @@ abstract class WidgetBase extends PluginSettingsBase implements WidgetInterface,
/**
* {@inheritdoc}
*/
+ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+ return $element;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
+ $element = $this->formElement($items, $delta, $widget->toRenderable(), $form->toRenderable(), $form_state);
+ return $this->elementInfoManager->fromRenderable($element);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
$field_name = $this->fieldDefinition->getName();
diff --git a/core/lib/Drupal/Core/Field/WidgetInterface.php b/core/lib/Drupal/Core/Field/WidgetInterface.php
index ab78308291bd..9107bd437f74 100644
--- a/core/lib/Drupal/Core/Field/WidgetInterface.php
+++ b/core/lib/Drupal/Core/Field/WidgetInterface.php
@@ -3,15 +3,17 @@
namespace Drupal\Core\Field;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Widget;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Interface definition for field widget plugins.
*
* This interface details the methods that most plugin implementations will want
- * to override. See Drupal\Core\Field\WidgetBaseInterface for base
+ * to override. See \Drupal\Core\Field\WidgetBaseInterface for base
* wrapping methods that should most likely be inherited directly from
- * Drupal\Core\Field\WidgetBase..
+ * \Drupal\Core\Field\WidgetBase.
*
* @ingroup field_widget
*/
@@ -104,6 +106,51 @@ interface WidgetInterface extends WidgetBaseInterface {
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state);
/**
+ * Returns the form for a single field widget.
+ *
+ * Field widget form elements should be based on the passed-in $element, which
+ * contains the base form element properties derived from the field
+ * configuration.
+ *
+ * The BaseWidget methods will set the weight, field name and delta values for
+ * each form element. If there are multiple values for this field, the
+ * formElement() method will be called as many times as needed.
+ *
+ * Other modules may alter the form element provided by this function using
+ * hook_field_widget_single_element_form_alter() or
+ * hook_field_widget_single_element_WIDGET_TYPE_form_alter().
+ *
+ * The FAPI element callbacks (such as #process, #element_validate,
+ * #value_callback, etc.) used by the widget do not have access to the
+ * original $field_definition passed to the widget's constructor. Therefore,
+ * if any information is needed from that definition by those callbacks, the
+ * widget implementing this method, or a
+ * hook_field_widget[_WIDGET_TYPE]_form_alter() implementation, must extract
+ * the needed properties from the field definition and set them as ad-hoc
+ * $element['#custom'] properties, for later use by its element callbacks.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * Array of default values for this field.
+ * @param int $delta
+ * The order of this item in the array of sub-elements (0, 1, 2, etc.).
+ * @param \Drupal\Core\Render\Element\Widget $widget
+ * A widget element.
+ * @param \Drupal\Core\Render\Element\ElementInterface $form
+ * The form structure where widgets are being attached to. This might be a
+ * full form structure, or a sub-element of a larger form.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return \Drupal\Core\Render\Element\ElementInterface
+ * The wrapper object. Some widgets need to change the type of it so the
+ * returned object might not be a Wrapper object.
+ *
+ * @see hook_field_widget_single_element_form_alter()
+ * @see hook_field_widget_single_element_WIDGET_TYPE_form_alter()
+ */
+ public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface;
+
+ /**
* Assigns a field-level validation error to the right widget sub-element.
*
* Depending on the widget's internal structure, a field-level validation
diff --git a/core/lib/Drupal/Core/Form/FormBase.php b/core/lib/Drupal/Core/Form/FormBase.php
index d88810943acd..44ca953ab077 100644
--- a/core/lib/Drupal/Core/Form/FormBase.php
+++ b/core/lib/Drupal/Core/Form/FormBase.php
@@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Logger\LoggerChannelTrait;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
@@ -76,6 +77,13 @@ abstract class FormBase implements FormInterface, ContainerInjectionInterface {
protected $routeMatch;
/**
+ * The element info manager.
+ *
+ * @var \Drupal\Core\Render\ElementInfoManagerInterface
+ */
+ protected ElementInfoManagerInterface $elementInfoManager;
+
+ /**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
@@ -126,6 +134,29 @@ abstract class FormBase implements FormInterface, ContainerInjectionInterface {
}
/**
+ * The element info manager.
+ *
+ * @return \Drupal\Core\Render\ElementInfoManagerInterface
+ * The element info manager.
+ */
+ protected function elementInfoManager(): ElementInfoManagerInterface {
+ if (!isset($this->elementInfoManager)) {
+ $this->elementInfoManager = $this->container()->get('plugin.manager.element_info');
+ }
+ return $this->elementInfoManager;
+ }
+
+ /**
+ * Sets the element info manager for this form.
+ *
+ * @return $this
+ */
+ public function setElementInfoManager(ElementInfoManagerInterface $elementInfoManager): static {
+ $this->elementInfoManager = $elementInfoManager;
+ return $this;
+ }
+
+ /**
* Sets the config factory for this form.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
diff --git a/core/lib/Drupal/Core/Render/Element/Button.php b/core/lib/Drupal/Core/Render/Element/Button.php
index a8f12e939636..95b78d9c4907 100644
--- a/core/lib/Drupal/Core/Render/Element/Button.php
+++ b/core/lib/Drupal/Core/Render/Element/Button.php
@@ -18,11 +18,15 @@ use Drupal\Core\Render\Element;
* using JavaScript or other mechanisms.
*
* Properties:
- * - #limit_validation_errors: An array of form element keys that will block
+ *
+ * @property $limit_validation_errors
+ * An array of form element keys that will block
* form submission when validation for these elements or any child elements
* fails. Specify an empty array to suppress all form validation errors.
- * - #value: The text to be shown on the button.
- * - #submit_button: This has a default value of TRUE. If set to FALSE, the
+ * @property $value
+ * The text to be shown on the button.
+ * @property $submit_button
+ * This has a default value of TRUE. If set to FALSE, the
* 'type' attribute is set to 'button.'
*
*
diff --git a/core/lib/Drupal/Core/Render/Element/Checkbox.php b/core/lib/Drupal/Core/Render/Element/Checkbox.php
index 65be5d22bf10..220d1c8f9693 100644
--- a/core/lib/Drupal/Core/Render/Element/Checkbox.php
+++ b/core/lib/Drupal/Core/Render/Element/Checkbox.php
@@ -10,7 +10,9 @@ use Drupal\Core\Render\Element;
* Provides a form element for a single checkbox.
*
* Properties:
- * - #return_value: The value to return when the checkbox is checked.
+ *
+ * @property $return_value
+ * The value to return when the checkbox is checked.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php
index 234f25aa9044..c713cc9cb531 100644
--- a/core/lib/Drupal/Core/Render/Element/Checkboxes.php
+++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php
@@ -9,7 +9,9 @@ use Drupal\Core\Render\Attribute\FormElement;
* Provides a form element for a set of checkboxes.
*
* Properties:
- * - #options: An associative array whose keys are the values returned for each
+ *
+ * @property $options
+ * An associative array whose keys are the values returned for each
* checkbox, and whose values are the labels next to each checkbox. The
* #options array cannot have a 0 key, as it would not be possible to discern
* checked and unchecked states.
diff --git a/core/lib/Drupal/Core/Render/Element/Color.php b/core/lib/Drupal/Core/Render/Element/Color.php
index 254300d976f9..616ed9dbe08e 100644
--- a/core/lib/Drupal/Core/Render/Element/Color.php
+++ b/core/lib/Drupal/Core/Render/Element/Color.php
@@ -11,7 +11,9 @@ use Drupal\Component\Utility\Color as ColorUtility;
* Provides a form element for choosing a color.
*
* Properties:
- * - #default_value: Default value, in a format like #ffffff.
+ *
+ * @property $default_value
+ * Default value, in a format like #ffffff.
*
* Example usage:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/ComponentElement.php b/core/lib/Drupal/Core/Render/Element/ComponentElement.php
index 62db902c0681..befd6aa269b6 100644
--- a/core/lib/Drupal/Core/Render/Element/ComponentElement.php
+++ b/core/lib/Drupal/Core/Render/Element/ComponentElement.php
@@ -11,11 +11,16 @@ use Drupal\Core\Security\DoTrustedCallbackTrait;
* Provides a Single-Directory Component render element.
*
* Properties:
- * - #component: The machine name of the component.
- * - #variant: (optional) The variant to be used for the component.
- * - #props: an associative array where the keys are the names of the
+ *
+ * @property $component
+ * The machine name of the component.
+ * @property $variant
+ * (optional) The variant to be used for the component.
+ * @property $props
+ * an associative array where the keys are the names of the
* component props, and the values are the prop values.
- * - #slots: an associative array where the keys are the slot names, and the
+ * @property $slots
+ * an associative array where the keys are the slot names, and the
* values are the slot values. Expected slot values are renderable arrays.
* - #propsAlter: an array of trusted callbacks. These are used to prepare the
* context. Typical uses include replacing tokens in props.
diff --git a/core/lib/Drupal/Core/Render/Element/Container.php b/core/lib/Drupal/Core/Render/Element/Container.php
index d5a2092718e7..105f1413efdd 100644
--- a/core/lib/Drupal/Core/Render/Element/Container.php
+++ b/core/lib/Drupal/Core/Render/Element/Container.php
@@ -14,7 +14,9 @@ use Drupal\Core\Render\Element;
* an HTML ID.
*
* Properties:
- * - #optional: Indicates whether the container should render when it has no
+ *
+ * @property $optional
+ * Indicates whether the container should render when it has no
* visible children. Defaults to FALSE.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Date.php b/core/lib/Drupal/Core/Render/Element/Date.php
index 8304167ae707..fbaeae4d600c 100644
--- a/core/lib/Drupal/Core/Render/Element/Date.php
+++ b/core/lib/Drupal/Core/Render/Element/Date.php
@@ -9,13 +9,18 @@ use Drupal\Core\Render\Element;
* Provides a form element for date or time selection.
*
* Properties:
- * - #attributes: An associative array containing:
+ *
+ * @property $attributes
+ * An associative array containing:
* - type: The type of date field rendered, valid values include 'date',
* 'time', 'datetime', and 'datetime-local'.
- * - #date_date_format: The date format used in PHP formats.
- * - #default_value: A string representing the date formatted as Y-m-d, or
+ * @property $date_date_format
+ * The date format used in PHP formats.
+ * @property $default_value
+ * A string representing the date formatted as Y-m-d, or
* hh:mm for time.
- * - #size: The size of the input element in characters.
+ * @property $size
+ * The size of the input element in characters.
*
* @code
* $form['expiration'] = [
diff --git a/core/lib/Drupal/Core/Render/Element/Details.php b/core/lib/Drupal/Core/Render/Element/Details.php
index 28e7396887d8..99a982727043 100644
--- a/core/lib/Drupal/Core/Render/Element/Details.php
+++ b/core/lib/Drupal/Core/Render/Element/Details.php
@@ -13,10 +13,14 @@ use Drupal\Core\Render\Element;
* element, showing or hiding the contained elements.
*
* Properties:
- * - #title: The title of the details container. Defaults to "Details".
- * - #open: Indicates whether the container should be open by default.
+ *
+ * @property $title
+ * The title of the details container. Defaults to "Details".
+ * @property $open
+ * Indicates whether the container should be open by default.
* Defaults to FALSE.
- * - #summary_attributes: An array of attributes to apply to the <summary>
+ * @property $summary_attributes
+ * An array of attributes to apply to the <summary>
* element.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Dropbutton.php b/core/lib/Drupal/Core/Render/Element/Dropbutton.php
index 20bb375ea879..4ac2d6dda8a5 100644
--- a/core/lib/Drupal/Core/Render/Element/Dropbutton.php
+++ b/core/lib/Drupal/Core/Render/Element/Dropbutton.php
@@ -17,9 +17,12 @@ use Drupal\Core\Render\Attribute\RenderElement;
* element property #links to provide $variables['links'] for theming.
*
* Properties:
- * - #links: An array of links to actions. See template_preprocess_links() for
+ *
+ * @property $links
+ * An array of links to actions. See template_preprocess_links() for
* documentation the properties of links in this array.
- * - #dropbutton_type: A string defining a type of dropbutton variant for
+ * @property $dropbutton_type
+ * A string defining a type of dropbutton variant for
* styling proposes. Renders as class `dropbutton--#dropbutton_type`.
*
* Usage Example:
diff --git a/core/lib/Drupal/Core/Render/Element/ElementInterface.php b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
index f7debae9efd4..dae671666c3a 100644
--- a/core/lib/Drupal/Core/Render/Element/ElementInterface.php
+++ b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
@@ -41,6 +41,22 @@ interface ElementInterface extends PluginInspectionInterface, RenderCallbackInte
public function getInfo();
/**
+ * Initialize storage.
+ *
+ * This will only have an effect the first time it is called, once it has
+ * been called, subsequent calls will not have an effect.
+ * Only the plugin manager should ever call this method.
+ *
+ * @param array $element
+ * The containing element.
+ *
+ * @return $this
+ *
+ * @internal
+ */
+ public function initializeInternalStorage(array &$element): static;
+
+ /**
* Sets a form element's class attribute.
*
* Adds 'required' and 'error' classes as needed.
@@ -52,4 +68,101 @@ interface ElementInterface extends PluginInspectionInterface, RenderCallbackInte
*/
public static function setAttributes(&$element, $class = []);
+ /**
+ * Returns a render array.
+ *
+ * @param string|null $wrapper_key
+ * An optional wrapper.
+ *
+ * @return array|\Drupal\Core\Render\Element\ElementInterface
+ * A render array. Make sure to take the return value as a reference.
+ * If $wrapper_key is not given then the stored render element is returned.
+ * If $wrapper_key is given then [$wrapper_key => &$element] is returned.
+ * The return value is typed with array|ElementInterface to prepare for
+ * Drupal 12, where the plan for this method is to return an
+ * ElementInterface object. If that plan goes through then in Drupal 13
+ * support for render arrays will be dropped.
+ */
+ public function &toRenderable(?string $wrapper_key = NULL): array|ElementInterface;
+
+ /**
+ * Returns child elements.
+ *
+ * @return \Traversable<\Drupal\Core\Render\Element\ElementInterface>
+ * Keys will be children names, values are render objects.
+ */
+ public function getChildren(): \Traversable;
+
+ /**
+ * Gets a child.
+ *
+ * @param int|string|list<int|string> $name
+ * The name of the child. Can also be an integer. Or a list of these.
+ * It is an integer when the field API uses the delta for children.
+ *
+ * @return ?\Drupal\Core\Render\Element\ElementInterface
+ * The child render object.
+ */
+ public function getChild(int|string|array $name): ?ElementInterface;
+
+ /**
+ * Adds a child render element.
+ *
+ * @param int|string $name
+ * The name of the child. Can also be an integer when the child is a delta.
+ * @param array|\Drupal\Core\Render\Element\ElementInterface $child
+ * A render array or a render object.
+ *
+ * @return \Drupal\Core\Render\Element\ElementInterface
+ * The added child as a render object.
+ */
+ public function addChild(int|string $name, ElementInterface|array &$child): ElementInterface;
+
+ /**
+ * Creates a render object and attaches it to the current render object.
+ *
+ * @param int|string $name
+ * The name of the child. Can also be an integer.
+ * @param class-string<T> $class
+ * The class of the render object.
+ * @param array $configuration
+ * An array of configuration relevant to the render object.
+ * @param bool $copyProperties
+ * Copy properties (but not children) from the parent. This is useful for
+ * widgets for example.
+ *
+ * @return T
+ * The child render object.
+ *
+ * @template T of \Drupal\Core\Render\Element\ElementInterface
+ */
+ public function createChild(int|string $name, string $class, array $configuration = [], bool $copyProperties = FALSE): ElementInterface;
+
+ /**
+ * Removes a child.
+ *
+ * @param int|string $name
+ * The name of the child. Can also be an integer.
+ *
+ * @return ?\Drupal\Core\Render\Element\ElementInterface
+ * The removed render object if any, or NULL if the child could not be
+ * found.
+ */
+ public function removeChild(int|string $name): ?ElementInterface;
+
+ /**
+ * Change the type of the element.
+ *
+ * Changes only the #type all other properties and children are preserved.
+ *
+ * @param class-string<T> $class
+ * The class of the new render object.
+ *
+ * @return T
+ * The new render object.
+ *
+ * @template T of \Drupal\Core\Render\Element\ElementInterface
+ */
+ public function changeType(string $class): ElementInterface;
+
}
diff --git a/core/lib/Drupal/Core/Render/Element/Email.php b/core/lib/Drupal/Core/Render/Element/Email.php
index 82e688ce38e2..debb2e10f446 100644
--- a/core/lib/Drupal/Core/Render/Element/Email.php
+++ b/core/lib/Drupal/Core/Render/Element/Email.php
@@ -10,9 +10,13 @@ use Drupal\Core\Render\Element;
* Provides a form input element for entering an email address.
*
* Properties:
- * - #default_value: An RFC-compliant email address.
- * - #size: The size of the input element in characters.
- * - #pattern: A string for the native HTML5 pattern attribute.
+ *
+ * @property $default_value
+ * An RFC-compliant email address.
+ * @property $size
+ * The size of the input element in characters.
+ * @property $pattern
+ * A string for the native HTML5 pattern attribute.
*
* Example usage:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
index df50492a9ec4..9336eda06960 100644
--- a/core/lib/Drupal/Core/Render/Element/File.php
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -13,8 +13,11 @@ use Drupal\Core\Render\Element;
* will automatically be added to the form element.
*
* Properties:
- * - #multiple: A Boolean indicating whether multiple files may be uploaded.
- * - #size: The size of the file input element in characters.
+ *
+ * @property $multiple
+ * A Boolean indicating whether multiple files may be uploaded.
+ * @property $size
+ * The size of the file input element in characters.
*
* The value of this form element will always be an array of
* \Symfony\Component\HttpFoundation\File\UploadedFile objects, regardless of
diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php
index 14ab865dd3b1..e991e7e7aaca 100644
--- a/core/lib/Drupal/Core/Render/Element/FormElement.php
+++ b/core/lib/Drupal/Core/Render/Element/FormElement.php
@@ -18,7 +18,8 @@ abstract class FormElement extends FormElementBase {
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
@trigger_error('\Drupal\Core\Render\Element\FormElement is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Core\Render\Element\FormElementBase instead. See https://www.drupal.org/node/3436275', E_USER_DEPRECATED);
}
diff --git a/core/lib/Drupal/Core/Render/Element/FormElementBase.php b/core/lib/Drupal/Core/Render/Element/FormElementBase.php
index 581607d716af..320ba9a75a1a 100644
--- a/core/lib/Drupal/Core/Render/Element/FormElementBase.php
+++ b/core/lib/Drupal/Core/Render/Element/FormElementBase.php
@@ -11,6 +11,15 @@ use Drupal\Core\Url;
*
* Form elements are a subset of render elements, representing elements for
* HTML forms, which can be referenced in form arrays. See the
+ *
+ * @see \Drupal\Core\Render\Attribute\FormElement
+ * @see \Drupal\Core\Render\Element\FormElementInterface
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\Element\RenderElementBase
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ * @see \Drupal\Core\Form\FormHelper::processStates()
* @link theme_render Render API topic @endlink for an overview of render
* arrays and render elements, and the @link form_api Form API topic @endlink
* for an overview of forms and form arrays.
@@ -30,69 +39,75 @@ use Drupal\Core\Url;
* processing of form elements, besides those properties documented in
* \Drupal\Core\Render\Element\RenderElementBase (for example: #prefix,
* #suffix):
- * - #after_build: (array) Array of callables or function names, which are
- * called after the element is built. Arguments: $element, $form_state.
- * - #ajax: (array) Array of elements to specify Ajax behavior. See
- * the @link ajax Ajax API topic @endlink for more information.
- * - #array_parents: (string[], read-only) Array of names of all the element's
- * parents (including itself) in the render array. See also #parents, #tree.
- * - #default_value: Default value for the element. See also #value.
- * - #description: (string) Help or description text for the element. In an
- * ideal user interface, the #title should be enough to describe the element,
- * so most elements should not have a description; if you do need one, make
- * sure it is translated. If it is not already wrapped in a safe markup
- * object, it will be filtered for XSS safety.
- * - #disabled: (bool) If TRUE, the element is shown but does not accept
- * user input.
- * - #element_validate: (array) Array of callables or function names, which
- * are called to validate the input. Arguments: $element, $form_state, $form.
- * - #field_prefix: (string) Prefix to display before the HTML input element.
- * Should be translated, normally. If it is not already wrapped in a safe
- * markup object, will be filtered for XSS safety. Note that the contents of
- * this prefix are wrapped in a <span> element, so the value should not
- * contain block level HTML. Any HTML added must be valid, i.e. any tags
- * introduced inside this prefix must also be terminated within the prefix.
- * - #field_suffix: (string) Suffix to display after the HTML input element.
- * Should be translated, normally. If it is not already wrapped in a safe
- * markup object, will be filtered for XSS safety. Note that the contents of
- * this suffix are wrapped in a <span> element, so the value should not
- * contain block level HTML. Any HTML must also be valid, i.e. any tags
- * introduce inside this suffix must also be terminated within the suffix.
- * - #value: (mixed) A value that cannot be edited by the user.
- * - #has_garbage_value: (bool) Internal only. Set to TRUE to indicate that the
- * #value property of an element should not be used or processed.
- * - #input: (bool, internal) Whether or not the element accepts input.
- * - #parents: (string[], read-only) Array of names of the element's parents
- * for purposes of getting values out of $form_state. See also
- * #array_parents, #tree.
- * - #process: (array) Array of callables or function names, which are
- * called during form building. Arguments: $element, $form_state, $form.
- * - #processed: (bool, internal) Set to TRUE when the element is processed.
- * - #required: (bool) Whether or not input is required on the element.
- * - #states: (array) Information about JavaScript states, such as when to
- * hide or show the element based on input on other elements.
- * See \Drupal\Core\Form\FormHelper::processStates() for documentation.
- * - #title: (string) Title of the form element. Should be translated.
- * - #title_display: (string) Where and how to display the #title. Possible
- * values:
- * - before: Label goes before the element (default for most elements).
- * - after: Label goes after the element (default for radio elements).
- * - invisible: Label is there but is made invisible using CSS.
- * - attribute: Make it the title attribute (hover tooltip).
- * - #tree: (bool) TRUE if the values of this element and its children should
- * be hierarchical in $form_state; FALSE if the values should be flat.
- * See also #parents, #array_parents.
- * - #value_callback: (callable) Callable or function name, which is called
- * to transform the raw user input to the element's value. Arguments:
- * $element, $input, $form_state.
- *
- * @see \Drupal\Core\Render\Attribute\FormElement
- * @see \Drupal\Core\Render\Element\FormElementInterface
- * @see \Drupal\Core\Render\ElementInfoManager
- * @see \Drupal\Core\Render\Element\RenderElementBase
- * @see plugin_api
- *
- * @ingroup theme_render
+ * @property array $after_build
+ * Array of callables or function names, which are called after the element
+ * is built. Arguments: $element, $form_state.
+ * @property array $ajax
+ * Array of elements to specify Ajax behavior. See the @link ajax Ajax API
+ * topic @endlink for more information.
+ * @property array<string> $array_parents
+ * Array of names of all the element's parents (including itself) in the
+ * render array. See also #parents, #tree.
+ * @property mixed $default_value
+ * Default value for the element. See also #value.
+ * @property scalar|\Stringable|\Drupal\Core\Render\RenderableInterface|array $description
+ * Help or description text for the element. In an ideal user interface,
+ * the #title should be enough to describe the element, so most elements
+ * should not have a description; if you do need one, make sure it is
+ * translated. It can be anything that Twig can print and will be filtered
+ * for XSS as necessary.
+ * @property bool $disabled
+ * If TRUE, the element is shown but does not accept user input.
+ * @property array<callable> $element_validate
+ * Array of callables or function names, which are called to validate the
+ * input. Arguments: $element, $form_state, $form.
+ * @property string $field_prefix
+ * Prefix to display before the HTML input element. Should be translated,
+ * normally. If it is not already wrapped in a safe markup object, will be
+ * filtered for XSS safety. Note that the contents of this prefix are
+ * wrapped in a <span> element, so the value should not contain block level
+ * HTML. Any HTML added must be valid, i.e. any tags introduced inside this
+ * prefix must also be terminated within the prefix.
+ * @property string $field_suffix
+ * Suffix to display after the HTML input element. Should be translated,
+ * normally. If it is not already wrapped in a safe markup object, will be
+ * filtered for XSS safety. Note that the contents of this suffix are
+ * wrapped in a <span> element, so the value should not contain block
+ * level HTML. Any HTML must also be valid, i.e. any tags introduce inside
+ * this suffix must also be terminated within the suffix.
+ * @property mixed $value
+ * A value that cannot be edited by the user.
+ * @property bool $has_garbage_value
+ * @internal
+ * Set to TRUE to indicate that the #value property of an
+ * element should not be used or processed.
+ * @property bool $input
+ * @internal
+ * Whether the element accepts input.
+ * @property array<string> $parents
+ * Array of names of the element's parents for purposes of getting values
+ * out of $form_state. See also #array_parents, #tree.
+ * @property array $process
+ * Array of callables or function names, which are called during form
+ * building. Arguments: $element, $form_state, $form.
+ * @property bool, internal $processed
+ * Set to TRUE when the element is processed.
+ * @property bool $required
+ * Whether input is required on the element.
+ * @property array $states
+ * Information about JavaScript states, such as when to hide or show the
+ * element based on input on other elements.
+ * @property string $title
+ * Title of the form element. Should be translated.
+ * @property \Drupal\Core\Render\Element\TitleDisplay $title_display
+ * Where and how to display the #title.
+ * @property bool $tree
+ * TRUE if the values of this element and its children should be hierarchical
+ * in $form_state; FALSE if the values should be flat. See also #parents,
+ * #array_parents.
+ * @property callable $value_callback
+ * Callable or function name, which is called to transform the raw user
+ * input to the element's value. Arguments: $element, $input, $form_state.
*/
abstract class FormElementBase extends RenderElementBase implements FormElementInterface {
diff --git a/core/lib/Drupal/Core/Render/Element/Generic.php b/core/lib/Drupal/Core/Render/Element/Generic.php
new file mode 100644
index 000000000000..4a0b6f09ebd9
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Generic.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Attribute\RenderElement;
+
+/**
+ * Provides a generic, empty element.
+ *
+ * Manually creating this element is not necessary; however, the system
+ * often needs to convert render arrays that do not have a type. While
+ * arrays without a #type are valid PHP code, it is not possible to create
+ * an object without a class.
+ */
+#[RenderElement('generic')]
+class Generic extends RenderElementBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo() {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setType(): void {
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Hidden.php b/core/lib/Drupal/Core/Render/Element/Hidden.php
index db3b23a08cce..1c8856143e68 100644
--- a/core/lib/Drupal/Core/Render/Element/Hidden.php
+++ b/core/lib/Drupal/Core/Render/Element/Hidden.php
@@ -11,9 +11,12 @@ use Drupal\Core\Render\Element;
* Specify either #default_value or #value but not both.
*
* Properties:
- * - #default_value: The initial value of the form element. JavaScript may
+ *
+ * @property $default_value
+ * The initial value of the form element. JavaScript may
* alter the value prior to submission.
- * - #value: The value of the form element. The Form API ensures that this
+ * @property $value
+ * The value of the form element. The Form API ensures that this
* value remains unchanged by the browser.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/HtmlTag.php b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
index 3b4dbd25d462..277f121df2cf 100644
--- a/core/lib/Drupal/Core/Render/Element/HtmlTag.php
+++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
@@ -13,12 +13,17 @@ use Drupal\Core\Template\Attribute;
* Provides a render element for any HTML tag, with properties and value.
*
* Properties:
- * - #tag: The tag name to output.
- * - #attributes: (array, optional) HTML attributes to apply to the tag. The
+ *
+ * @property $tag
+ * The tag name to output.
+ * @property $attributes
+ * (array, optional) HTML attributes to apply to the tag. The
* attributes are escaped, see \Drupal\Core\Template\Attribute.
- * - #value: (string|MarkupInterface, optional) The textual contents of the tag.
+ * @property $value
+ * (string|MarkupInterface, optional) The textual contents of the tag.
* Strings will be XSS admin filtered.
- * - #noscript: (bool, optional) When set to TRUE, the markup
+ * @property $noscript
+ * (bool, optional) When set to TRUE, the markup
* (including any prefix or suffix) will be wrapped in a <noscript> element.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Icon.php b/core/lib/Drupal/Core/Render/Element/Icon.php
index d903f0d17006..e71771044e78 100644
--- a/core/lib/Drupal/Core/Render/Element/Icon.php
+++ b/core/lib/Drupal/Core/Render/Element/Icon.php
@@ -12,9 +12,13 @@ use Drupal\Core\Theme\Icon\IconDefinition;
* Provides a render element to display an icon.
*
* Properties:
- * - #pack_id: (string) Icon Pack provider plugin id.
- * - #icon_id: (string) Name of the icon.
- * - #settings: (array) Settings sent to the inline Twig template.
+ *
+ * @property $pack_id
+ * (string) Icon Pack provider plugin id.
+ * @property $icon_id
+ * (string) Name of the icon.
+ * @property $settings
+ * (array) Settings sent to the inline Twig template.
*
* Usage Example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/InlineTemplate.php b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php
index 313bc4f13710..68f5fbf7091d 100644
--- a/core/lib/Drupal/Core/Render/Element/InlineTemplate.php
+++ b/core/lib/Drupal/Core/Render/Element/InlineTemplate.php
@@ -8,8 +8,11 @@ use Drupal\Core\Render\Attribute\RenderElement;
* Provides a render element where the user supplies an in-line Twig template.
*
* Properties:
- * - #template: The inline Twig template used to render the element.
- * - #context: (array) The variables to substitute into the Twig template.
+ *
+ * @property $template
+ * The inline Twig template used to render the element.
+ * @property $context
+ * (array) The variables to substitute into the Twig template.
* Each variable may be a string or a render array.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php
index 2a00e0526b68..b04704b1681d 100644
--- a/core/lib/Drupal/Core/Render/Element/Link.php
+++ b/core/lib/Drupal/Core/Render/Element/Link.php
@@ -14,8 +14,11 @@ use Drupal\Core\Url as CoreUrl;
* Provides a link render element.
*
* Properties:
- * - #title: The link text.
- * - #url: \Drupal\Core\Url object containing URL information pointing to an
+ *
+ * @property $title
+ * The link text.
+ * @property $url
+ * \Drupal\Core\Url object containing URL information pointing to an
* internal or external link. See \Drupal\Core\Utility\LinkGeneratorInterface.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php
index 11647cd02c57..93fbfe5be7f1 100644
--- a/core/lib/Drupal/Core/Render/Element/MachineName.php
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -21,7 +21,9 @@ use Drupal\Core\Render\Attribute\FormElement;
* machine name form element.
*
* Properties:
- * - #machine_name: An associative array containing:
+ *
+ * @property $machine_name
+ * An associative array containing:
* - exists: A callable to invoke for checking whether a submitted machine
* name value already exists. The arguments passed to the callback will be:
* - The submitted value.
@@ -49,9 +51,11 @@ use Drupal\Core\Render\Attribute\FormElement;
* form element rather than in the suffix of the source element. The source
* element must appear in the form structure before this element. Defaults
* to FALSE.
- * - #maxlength: (optional) Maximum allowed length of the machine name. Defaults
+ * @property $maxlength
+ * (optional) Maximum allowed length of the machine name. Defaults
* to 64.
- * - #disabled: (optional) Should be set to TRUE if an existing machine name
+ * @property $disabled
+ * (optional) Should be set to TRUE if an existing machine name
* must not be changed after initial creation.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/MoreLink.php b/core/lib/Drupal/Core/Render/Element/MoreLink.php
index 9d990942953d..57303585d5ba 100644
--- a/core/lib/Drupal/Core/Render/Element/MoreLink.php
+++ b/core/lib/Drupal/Core/Render/Element/MoreLink.php
@@ -8,7 +8,9 @@ use Drupal\Core\Render\Attribute\RenderElement;
* Provides a link render element for a "more" link, like those used in blocks.
*
* Properties:
- * - #title: The text of the link to generate (defaults to 'More').
+ *
+ * @property $title
+ * The text of the link to generate (defaults to 'More').
*
* See \Drupal\Core\Render\Element\Link for additional properties.
*
diff --git a/core/lib/Drupal/Core/Render/Element/Number.php b/core/lib/Drupal/Core/Render/Element/Number.php
index 7096b2925beb..2be176fd363e 100644
--- a/core/lib/Drupal/Core/Render/Element/Number.php
+++ b/core/lib/Drupal/Core/Render/Element/Number.php
@@ -11,10 +11,15 @@ use Drupal\Component\Utility\Number as NumberUtility;
* Provides a form element for numeric input, with special numeric validation.
*
* Properties:
- * - #default_value: A valid floating point number.
- * - #min: Minimum value.
- * - #max: Maximum value.
- * - #step: Ensures that the number is an even multiple of step, offset by #min
+ *
+ * @property $default_value
+ * A valid floating point number.
+ * @property $min
+ * Minimum value.
+ * @property $max
+ * Maximum value.
+ * @property $step
+ * Ensures that the number is an even multiple of step, offset by #min
* if specified. A #min of 1 and a #step of 2 would allow values of 1, 3, 5,
* etc.
*
diff --git a/core/lib/Drupal/Core/Render/Element/Page.php b/core/lib/Drupal/Core/Render/Element/Page.php
index f271c6adfcf1..cc7cd16dc1ee 100644
--- a/core/lib/Drupal/Core/Render/Element/Page.php
+++ b/core/lib/Drupal/Core/Render/Element/Page.php
@@ -8,7 +8,7 @@ use Drupal\Core\Render\Attribute\RenderElement;
* Provides a render element for the content of an HTML page.
*
* This represents the "main part" of the HTML page's body; see html.html.twig.
- */
+ */
#[RenderElement('page')]
class Page extends RenderElementBase {
diff --git a/core/lib/Drupal/Core/Render/Element/Pager.php b/core/lib/Drupal/Core/Render/Element/Pager.php
index a31c7f2f3719..b5aa9bacf2c4 100644
--- a/core/lib/Drupal/Core/Render/Element/Pager.php
+++ b/core/lib/Drupal/Core/Render/Element/Pager.php
@@ -13,15 +13,22 @@ use Drupal\Core\Render\Attribute\RenderElement;
* extend a select query with \Drupal\Core\Database\Query\PagerSelectExtender.
*
* Properties:
- * - #element: (optional, int) The pager ID, to distinguish between multiple
+ *
+ * @property $element
+ * (optional, int) The pager ID, to distinguish between multiple
* pagers on the same page (defaults to 0).
- * - #pagination_heading_level: (optional) A heading level for the pager.
- * - #parameters: (optional) An associative array of query string parameters to
+ * @property $pagination_heading_level
+ * (optional) A heading level for the pager.
+ * @property $parameters
+ * (optional) An associative array of query string parameters to
* append to the pager.
- * - #quantity: The maximum number of numbered page links to create (defaults
+ * @property $quantity
+ * The maximum number of numbered page links to create (defaults
* to 9).
- * - #tags: (optional) An array of labels for the controls in the pages.
- * - #route_name: (optional) The name of the route to be used to build pager
+ * @property $tags
+ * (optional) An array of labels for the controls in the pages.
+ * @property $route_name
+ * (optional) The name of the route to be used to build pager
* links. Defaults to '<none>', which will make links relative to the current
* URL. This makes the page more effectively cacheable.
*
diff --git a/core/lib/Drupal/Core/Render/Element/Password.php b/core/lib/Drupal/Core/Render/Element/Password.php
index 0c2e99d054b7..3b0b5d3a3785 100644
--- a/core/lib/Drupal/Core/Render/Element/Password.php
+++ b/core/lib/Drupal/Core/Render/Element/Password.php
@@ -10,8 +10,11 @@ use Drupal\Core\Render\Element;
* Provides a form element for entering a password, with hidden text.
*
* Properties:
- * - #size: The size of the input element in characters.
- * - #pattern: A string for the native HTML5 pattern attribute.
+ *
+ * @property $size
+ * The size of the input element in characters.
+ * @property $pattern
+ * A string for the native HTML5 pattern attribute.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
index 3ca411682a53..95a1677c7f42 100644
--- a/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
+++ b/core/lib/Drupal/Core/Render/Element/PasswordConfirm.php
@@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement;
* entered passwords match.
*
* Properties:
- * - #size: The size of the input element in characters.
+ *
+ * @property $size
+ * The size of the input element in characters.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Radios.php b/core/lib/Drupal/Core/Render/Element/Radios.php
index 7dc1815b36e7..08880ce1689f 100644
--- a/core/lib/Drupal/Core/Render/Element/Radios.php
+++ b/core/lib/Drupal/Core/Render/Element/Radios.php
@@ -10,7 +10,9 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* Provides a form element for a set of radio buttons.
*
* Properties:
- * - #options: An associative array, where the keys are the returned values for
+ *
+ * @property $options
+ * An associative array, where the keys are the returned values for
* each radio button, and the values are the labels next to each radio button.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Range.php b/core/lib/Drupal/Core/Render/Element/Range.php
index 0588dd555230..210b19c4ca34 100644
--- a/core/lib/Drupal/Core/Render/Element/Range.php
+++ b/core/lib/Drupal/Core/Render/Element/Range.php
@@ -12,8 +12,11 @@ use Drupal\Core\Render\Element;
* Provides an HTML5 input element with type of "range".
*
* Properties:
- * - #min: Minimum value (defaults to 0).
- * - #max: Maximum value (defaults to 100).
+ *
+ * @property $min
+ * Minimum value (defaults to 0).
+ * @property $max
+ * Maximum value (defaults to 100).
* Refer to \Drupal\Core\Render\Element\Number for additional properties.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index c37338c3219f..fd29573aad11 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -18,7 +18,8 @@ abstract class RenderElement extends RenderElementBase {
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
@trigger_error('\Drupal\Core\Render\Element\RenderElement is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Core\Render\Element\RenderElementBase instead. See https://www.drupal.org/node/3436275', E_USER_DEPRECATED);
}
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
index 5ff3a5a0f98e..a2562144b2d8 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElementBase.php
@@ -2,12 +2,16 @@
namespace Drupal\Core\Render\Element;
+use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a base class for render element plugins.
@@ -37,88 +41,110 @@ use Drupal\Core\Url;
* \Drupal\Core\StringTranslation\TranslatableMarkup objects instead.
*
* Here is the list of the properties used during the rendering of all render
- * elements:
- * - #access: (bool or AccessResultInterface)
+ * elements. These are available as properties on the render element (handled
+ * by magic setter/getter) and also the render array starting with a #
+ * character. For example $element['#access'] or $elementObject->access.
+ *
+ * @property bool|\Drupal\Core\Access\AccessResultInterface $access
* Whether the element is accessible or not.
- * When the value is FALSE (if boolean)
- * or the isAllowed() method returns FALSE (if AccessResultInterface),
- * the element is not rendered and user-submitted values are not taken
- * into consideration.
- * - #access_callback: A callable or function name to call to check access.
- * Argument: element.
- * - #allowed_tags: (array) Array of allowed HTML tags for XSS filtering of
- * #markup, #prefix, #suffix, etc.
- * - #attached: (array) Array of attachments associated with the element.
- * See the "Attaching libraries in render arrays" section of the
+ * When the value is FALSE (if boolean) or the isAllowed() method returns
+ * FALSE (if AccessResultInterface), the element is not rendered and
+ * user-submitted values are not taken into consideration.
+ * @property callable $access_callback
+ * A callable or function name to call to check access. Argument: element.
+ * @property array<string> $allowed_tags
+ * Array of allowed HTML tags for XSS filtering of #markup, #prefix, #suffix,
+ * etc.
+ * @property array $attached
+ * Array of attachments associated with the element. See the "Attaching
+ * libraries in render arrays" section of the
* @link theme_render Render API topic @endlink for an overview, and
* \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments
- * for a list of what this can contain. Besides this list, it may also contain
- * a 'placeholders' element; see the Placeholders section of the
+ * for a list of what this can contain. Besides this list, it may also
+ * contain a 'placeholders' element; see the Placeholders section of the
* @link theme_render Render API topic @endlink for an overview.
- * - #attributes: (array) HTML attributes for the element. The first-level
- * keys are the attribute names, such as 'class', and the attributes are
- * usually given as an array of string values to apply to that attribute
- * (the rendering system will concatenate them together into a string in
- * the HTML output).
- * - #cache: (array) Cache information. See the Caching section of the
+ * @property array $attributes
+ * HTML attributes for the element. The first-level keys are the attribute
+ * names, such as 'class', and the attributes are usually given as an array
+ * of string values to apply to that attribute (the rendering system will
+ * concatenate them together into a string in the HTML output).
+ * @property array $cache
+ * Cache information. See the Caching section of the
* @link theme_render Render API topic @endlink for more information.
- * - #children: (array, internal) Array of child elements of this element.
- * Set and used during the rendering process.
- * - #create_placeholder: (bool) TRUE if the element has placeholders that
- * are generated by #lazy_builder callbacks. Set internally during rendering
- * in some cases. See also #attached.
- * - #defaults_loaded: (bool) Set to TRUE during rendering when the defaults
- * for the element #type have been added to the element.
- * - #value: (mixed) A value that cannot be edited by the user.
- * - #has_garbage_value: (bool) Internal only. Set to TRUE to indicate that the
- * #value property of an element should not be used or processed.
- * - #id: (string) The HTML ID on the element. This is automatically set for
- * form elements, but not for all render elements; you can override the
- * default value or add an ID by setting this property.
- * - #lazy_builder: (array) Array whose first element is a lazy building
- * callback (callable), and whose second is an array of scalar arguments to
- * the callback. To use lazy building, the element array must be very
- * simple: no properties except #lazy_builder, #cache, #weight, and
- * #create_placeholder, and no children. A lazy builder callback typically
- * generates #markup and/or placeholders; see the Placeholders section of the
+ * @property array $children
+ * Array of child elements of this element. Set and used during the
+ * rendering process.
+ * @property bool $create_placeholder
+ * TRUE if the element has placeholders that are generated by #lazy_builder
+ * callbacks. Set internally during rendering in some cases. See also
+ * #attached.
+ * @property bool $defaults_loaded
+ * Set to TRUE during rendering when the defaults for the element #type have
+ * been added to the element.
+ * @property mixed $value
+ * A value that cannot be edited by the user.
+ * @property bool $has_garbage_value
+ * @internal
+ * Set to TRUE to indicate that the #value property of an element should not
+ * be used or processed.
+ * @property string $id
+ * The HTML ID on the element. This is automatically set for form elements,
+ * but not for all render elements; you can override the default value or
+ * add an ID by setting this property.
+ * @property array<callable, array<scalar>> $lazy_builder
+ * Array whose first element is a lazy building callback (callable), and
+ * whose second is an array of scalar arguments to the callback. To use
+ * lazy building, the element array must be very simple: no properties
+ * except #lazy_builder, #cache, #weight, and #create_placeholder, and no
+ * children. A lazy builder callback typically generates #markup and/or
+ * placeholders; see the Placeholders section of the
* @link theme_render Render API topic @endlink for information about
* placeholders.
- * - #markup: (string) During rendering, this will be set to the HTML markup
- * output. It can also be set on input, as a fallback if there is no
- * theming for the element. This will be filtered for XSS problems during
- * rendering; see also #plain_text and #allowed_tags.
- * - #plain_text: (string) Elements can set this instead of #markup. All HTML
- * tags will be escaped in this text, and if both #plain_text and #markup
- * are provided, #plain_text is used.
- * - #post_render: (array) Array of callables or function names, which are
- * called after the element is rendered. Arguments: rendered element string,
- * children.
- * - #pre_render: (array) Array of callables or function names, which are
- * called just before the element is rendered. Argument: $element.
- * Return value: an altered $element.
- * - #prefix: (string) Text to render before the entire element output. See
- * also #suffix. If it is not already wrapped in a safe markup object, will
- * be filtered for XSS safety.
- * - #printed: (bool, internal) Set to TRUE when an element and its children
- * have been rendered.
- * - #render_children: (bool, internal) Set to FALSE by the rendering process
- * if the #theme call should be bypassed (normally, the theme is used to
- * render the children). Set to TRUE by the rendering process if the children
- * should be rendered by rendering each one separately and concatenating.
- * - #suffix: (string) Text to render after the entire element output. See
- * also #prefix. If it is not already wrapped in a safe markup object, will
- * be filtered for XSS safety.
- * - #theme: (string) Name of the theme hook to use to render the element.
- * A default is generally set for elements; users of the element can
- * override this (typically by adding __suggestion suffixes).
- * - #theme_wrappers: (array) Array of theme hooks, which are invoked
- * after the element and children are rendered, and before #post_render
- * functions.
- * - #type: (string) The machine name of the type of render/form element.
- * - #weight: (float) The sort order for rendering, with lower numbers coming
- * before higher numbers. Default if not provided is zero; elements with
- * the same weight are rendered in the order they appear in the render
- * array.
+ * @property string $markup
+ * During rendering, this will be set to the HTML markup output. It can also
+ * be set on input, as a fallback if there is no theming for the element.
+ * This will be filtered for XSS problems during rendering; see also
+ * #plain_text and #allowed_tags.
+ * @property string $plain_text
+ * Elements can set this instead of #markup. All HTML tags will be escaped
+ * in this text, and if both #plain_text and #markup are provided,
+ * #plain_text is used.
+ * @property array<callable> $post_render
+ * Array of callables or function names, which are called after the element
+ * is rendered. Arguments: rendered element string, children.
+ * @property array<callable> $pre_render
+ * Array of callables or function names, which are called just before the
+ * element is rendered. Argument: $element. Return value: an altered
+ * $element.
+ * @property string $prefix
+ * Text to render before the entire element output. See also #suffix. If it
+ * is not already wrapped in a safe markup object, will be filtered for XSS
+ * safety.
+ * @property bool $printed
+ * Set to TRUE when an element and its children have been rendered.
+ * @property bool $render_children
+ * @internal
+ * Set to FALSE by the rendering process if the #theme call should be
+ * bypassed (normally, the theme is used to render the children). Set to
+ * TRUE by the rendering process if the children should be rendered by
+ * rendering each one separately and concatenating.
+ * @property string $suffix
+ * Text to render after the entire element output. See also #prefix. If it
+ * is not already wrapped in a safe markup object, will be filtered for XSS
+ * safety.
+ * @property string $theme
+ * Name of the theme hook to use to render the element. A default is
+ * generally set for elements; users of the element can override this
+ * (typically by adding __suggestion suffixes).
+ * @property array<string> $theme_wrappers
+ * Array of theme hooks, which are invoked after the element and children
+ * are rendered, and before #post_render functions.
+ * @property string $type
+ * The machine name of the type of render/form element.
+ * @property float $weight
+ * The sort order for rendering, with lower numbers coming before higher
+ * numbers. Default if not provided is zero; elements with the same weight
+ * are rendered in the order they appear in the render array.
*
* @see \Drupal\Core\Render\Attribute\RenderElement
* @see \Drupal\Core\Render\ElementInterface
@@ -127,7 +153,60 @@ use Drupal\Core\Url;
*
* @ingroup theme_render
*/
-abstract class RenderElementBase extends PluginBase implements ElementInterface {
+abstract class RenderElementBase extends PluginBase implements ElementInterface, ContainerFactoryPluginInterface {
+
+ /**
+ * Constructs a new render element object.
+ *
+ * @param array $configuration
+ * A configuration array containing information about the plugin instance.
+ * @param string $plugin_id
+ * The plugin ID for the plugin instance.
+ * @param mixed $plugin_definition
+ * The plugin implementation definition.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface|null $elementInfoManager
+ * The element info manager.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, protected ?ElementInfoManagerInterface $elementInfoManager = NULL) {
+ if (!$this->elementInfoManager) {
+ @trigger_error('Calling ' . __METHOD__ . '() without the $elementInfoManager argument is deprecated in drupal:11.3.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/node/3526683', E_USER_DEPRECATED);
+ $this->elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ }
+ parent::__construct($configuration, $plugin_id, $plugin_definition);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('plugin.manager.element_info'),
+ );
+ }
+
+ /**
+ * The storage.
+ *
+ * @internal
+ */
+ protected array $storage = [];
+
+ /**
+ * The parent element.
+ *
+ * @var static
+ */
+ protected ElementInterface $renderParent;
+
+ /**
+ * The parent key.
+ *
+ * @var string
+ */
+ protected string $renderParentName;
/**
* {@inheritdoc}
@@ -474,4 +553,183 @@ abstract class RenderElementBase extends PluginBase implements ElementInterface
return $element;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function initializeInternalStorage(array &$element): static {
+ $this->storage = &$element;
+ $element['##object'] = $this;
+ $this->setType();
+ return $this;
+ }
+
+ /**
+ * Set type on initialize.
+ *
+ * There is no need to either call or override this method.
+ *
+ * @internal
+ */
+ protected function setType(): void {
+ $this->storage['#type'] = $this->getPluginId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function &toRenderable(?string $wrapper_key = NULL): array {
+ if ($wrapper_key) {
+ $return = [$wrapper_key => &$this->storage];
+ return $return;
+ }
+ return $this->storage;
+ }
+
+ /**
+ * Magic method: Sets a property value.
+ *
+ * @param string $name
+ * The name of a property. $value will be accessible with $this->name and
+ * also $element['#' . $name] where the element is the render array this
+ * object was created from.
+ * @param mixed $value
+ * The value.
+ */
+ public function __set(string $name, $value): void {
+ $this->storage['#' . $name] = $value;
+ }
+
+ /**
+ * Magic method: gets a property value.
+ *
+ * @param string $name
+ * The name of the property. $value is accessible with $this->name and
+ * also $element['#' . $name] where the element is the render array this
+ * object was created from.
+ *
+ * @return mixed
+ * The value.
+ */
+ public function __get(string $name): mixed {
+ return $this->storage['#' . $name] ?? NULL;
+ }
+
+ /**
+ * Magic method: unsets a property value.
+ *
+ * @param string $name
+ * The name of the property. This will unset both the object property
+ * $this->name and also the render key $element['#' . $name] where the
+ * element is the render array this object was created from.
+ */
+ public function __unset(string $name): void {
+ unset($this->storage['#' . $name]);
+ }
+
+ /**
+ * Magic method: checks if a property value is set.
+ *
+ * @param string $name
+ * The name of the property. Check whether the render key
+ * $element['#' . $name] is set where element is the render array this
+ * object was created from. This value is also accessible as $this->name.
+ *
+ * @return bool
+ * Whether it is set or not.
+ */
+ public function __isset(string $name): bool {
+ return isset($this->storage['#' . $name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getChildren(): \Traversable {
+ foreach (Element::children($this->storage) as $key) {
+ yield $key => $this->elementInfoManager()->fromRenderable($this->storage[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getChild(int|string|array $name): ?ElementInterface {
+ $value = &NestedArray::getValue($this->storage, (array) $name, $exists);
+ return $exists ? $this->elementInfoManager()->fromRenderable($value) : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addChild(string|int $name, ElementInterface|array &$child): ElementInterface {
+ if ($name[0] === '#') {
+ throw new \LogicException('The name of children can not start with a #.');
+ }
+ $childObject = $this->elementInfoManager()->fromRenderable($child);
+ $childObject->renderParent = $this;
+ $childObject->renderParentName = $name;
+ $this->storage[$name] = &$childObject->toRenderable();
+ return $childObject;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createChild(int|string $name, string $class, array $configuration = [], bool $copyProperties = FALSE): ElementInterface {
+ $childObject = $this->elementInfoManager()->fromClass($class, $configuration);
+ $childObject = $this->addChild($name, $childObject);
+ if ($copyProperties) {
+ $childObject->storage += array_filter($this->storage, Element::property(...), \ARRAY_FILTER_USE_KEY);
+ }
+ return $childObject;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeChild(int|string $name): ?ElementInterface {
+ $return = $this->storage[$name] ?? NULL;
+ unset($this->storage[$name]);
+ return $return ? $this->elementInfoManager()->fromRenderable($return) : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function changeType(string $class): ElementInterface {
+ $this->storage['#type'] = $this->elementInfoManager()->getIdFromClass($class);
+ unset($this->storage['##object']);
+ return $this->elementInfoManager()->fromRenderable($this->storage);
+ }
+
+ /**
+ * Returns the element info manager.
+ *
+ * @return \Drupal\Core\Render\ElementInfoManagerInterface
+ * The element info manager/
+ */
+ protected function elementInfoManager(): ElementInfoManagerInterface {
+ if (!$this->elementInfoManager) {
+ $this->elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ }
+ return $this->elementInfoManager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __sleep(): array {
+ $vars = parent::__sleep();
+ unset($this->storage['##object']);
+ return $vars;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __wakeup(): void {
+ parent::__wakeup();
+ $this->storage['##object'] = $this;
+ }
+
}
diff --git a/core/lib/Drupal/Core/Render/Element/Select.php b/core/lib/Drupal/Core/Render/Element/Select.php
index 094b963212bd..c38582d4b23b 100644
--- a/core/lib/Drupal/Core/Render/Element/Select.php
+++ b/core/lib/Drupal/Core/Render/Element/Select.php
@@ -10,7 +10,9 @@ use Drupal\Core\Render\Element;
* Provides a form element for a drop-down menu or scrolling selection box.
*
* Properties:
- * - #options: An associative array of options for the select. Do not use
+ *
+ * @property $options
+ * An associative array of options for the select. Do not use
* placeholders that sanitize data in any labels, as doing so will lead to
* double-escaping. Each array value can be:
* - A single translated string representing an HTML option element, where
@@ -28,18 +30,22 @@ use Drupal\Core\Render\Element;
* is ignored, and the contents of the 'option' property are interpreted as
* an array of options to be merged with any other regular options and
* option groups found in the outer array.
- * - #sort_options: (optional) If set to TRUE (default is FALSE), sort the
+ * @property $sort_options
+ * (optional) If set to TRUE (default is FALSE), sort the
* options by their labels, after rendering and translation is complete.
* Can be set within an option group to sort that group.
- * - #sort_start: (optional) Option index to start sorting at, where 0 is the
+ * @property $sort_start
+ * (optional) Option index to start sorting at, where 0 is the
* first option. Can be used within an option group. If an empty option is
* being added automatically (see #empty_option and #empty_value properties),
* this defaults to 1 to keep the empty option at the top of the list.
* Otherwise, it defaults to 0.
- * - #empty_option: (optional) The label to show for the first default option.
+ * @property $empty_option
+ * (optional) The label to show for the first default option.
* By default, the label is automatically set to "- Select -" for a required
* field and "- None -" for an optional field.
- * - #empty_value: (optional) The value for the first default option, which is
+ * @property $empty_value
+ * (optional) The value for the first default option, which is
* used to determine whether the user submitted a value or not.
* - If #required is TRUE, this defaults to '' (an empty string). Note that
* if #empty_value is the same as a key in #options then the value of
@@ -57,15 +63,19 @@ use Drupal\Core\Render\Element;
* - If #required is not TRUE and this value is set (most commonly to an
* empty string), then an extra option (see #empty_option above)
* representing a "non-selection" is added with this as its value.
- * - #multiple: (optional) Indicates whether one or more options can be
+ * @property $multiple
+ * (optional) Indicates whether one or more options can be
* selected. Defaults to FALSE.
- * - #default_value: Must be NULL or not set in case there is no value for the
+ * @property $default_value
+ * Must be NULL or not set in case there is no value for the
* element yet, in which case a first default option is inserted by default.
* Whether this first option is a valid option depends on whether the field
* is #required or not.
- * - #required: (optional) Whether the user needs to select an option (TRUE)
+ * @property $required
+ * (optional) Whether the user needs to select an option (TRUE)
* or not (FALSE). Defaults to FALSE.
- * - #size: The number of rows in the list that should be visible at one time.
+ * @property $size
+ * The number of rows in the list that should be visible at one time.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Submit.php b/core/lib/Drupal/Core/Render/Element/Submit.php
index 980f4ff6c591..d47ca57cb668 100644
--- a/core/lib/Drupal/Core/Render/Element/Submit.php
+++ b/core/lib/Drupal/Core/Render/Element/Submit.php
@@ -11,10 +11,13 @@ use Drupal\Core\Render\Attribute\FormElement;
* the form's submit handler.
*
* Properties:
- * - #submit: Specifies an alternate callback for form submission when the
+ *
+ * @property $submit
+ * Specifies an alternate callback for form submission when the
* submit button is pressed. Use '::methodName' format or an array containing
* the object and method name (for example, [ $this, 'methodName'] ).
- * - #value: The text to be shown on the button.
+ * @property $value
+ * The text to be shown on the button.
*
* Usage Example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php
index 85111505e9bb..c9af86146829 100644
--- a/core/lib/Drupal/Core/Render/Element/Table.php
+++ b/core/lib/Drupal/Core/Render/Element/Table.php
@@ -14,19 +14,27 @@ use Drupal\Component\Utility\Html as HtmlUtility;
* context of a form.
*
* Properties:
- * - #header: An array of table header labels.
- * - #rows: An array of the rows to be displayed. Each row is either an array
+ *
+ * @property $header
+ * An array of table header labels.
+ * @property $rows
+ * An array of the rows to be displayed. Each row is either an array
* of cell contents or an array of properties as described in table.html.twig
* Alternatively specify the data for the table as child elements of the table
* element. Table elements would contain rows elements that would in turn
* contain column elements.
- * - #empty: Text to display when no rows are present.
- * - #responsive: Indicates whether to add the drupal.tableresponsive library
+ * @property $empty
+ * Text to display when no rows are present.
+ * @property $responsive
+ * Indicates whether to add the drupal.tableresponsive library
* providing responsive tables. Defaults to TRUE.
- * - #sticky: Indicates whether to make the table headers sticky at
+ * @property $sticky
+ * Indicates whether to make the table headers sticky at
* the top of the page. Defaults to FALSE.
- * - #footer: Table footer rows, in the same format as the #rows property.
- * - #caption: A localized string for the <caption> tag.
+ * @property $footer
+ * Table footer rows, in the same format as the #rows property.
+ * @property $caption
+ * A localized string for the <caption> tag.
*
* Usage example 1: A simple form with an additional information table which
* doesn't include any other form field.
diff --git a/core/lib/Drupal/Core/Render/Element/Tableselect.php b/core/lib/Drupal/Core/Render/Element/Tableselect.php
index cb38a0722d22..297115a30151 100644
--- a/core/lib/Drupal/Core/Render/Element/Tableselect.php
+++ b/core/lib/Drupal/Core/Render/Element/Tableselect.php
@@ -12,13 +12,19 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
* Provides a form element for a table with radios or checkboxes in left column.
*
* Properties:
- * - #header: An array of table header labels.
- * - #options: An associative array where each key is the value returned when
+ *
+ * @property $header
+ * An array of table header labels.
+ * @property $options
+ * An associative array where each key is the value returned when
* a user selects the radio button or checkbox, and each value is the row of
* table data.
- * - #empty: The message to display if table does not have any options.
- * - #multiple: Set to FALSE to render the table with radios instead checkboxes.
- * - #js_select: Set to FALSE if you don't want the select all checkbox added to
+ * @property $empty
+ * The message to display if table does not have any options.
+ * @property $multiple
+ * Set to FALSE to render the table with radios instead checkboxes.
+ * @property $js_select
+ * Set to FALSE if you don't want the select all checkbox added to
* the header.
*
* Other properties of the \Drupal\Core\Render\Element\Table element are also
diff --git a/core/lib/Drupal/Core/Render/Element/Tel.php b/core/lib/Drupal/Core/Render/Element/Tel.php
index 9d5951e7d4e6..3d752708ebc9 100644
--- a/core/lib/Drupal/Core/Render/Element/Tel.php
+++ b/core/lib/Drupal/Core/Render/Element/Tel.php
@@ -12,8 +12,11 @@ use Drupal\Core\Render\Element;
* validation.
*
* Properties:
- * - #size: The size of the input element in characters.
- * - #pattern: A string for the native HTML5 pattern attribute.
+ *
+ * @property $size
+ * The size of the input element in characters.
+ * @property $pattern
+ * A string for the native HTML5 pattern attribute.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Textarea.php b/core/lib/Drupal/Core/Render/Element/Textarea.php
index c3fc60219244..9e41f15a891a 100644
--- a/core/lib/Drupal/Core/Render/Element/Textarea.php
+++ b/core/lib/Drupal/Core/Render/Element/Textarea.php
@@ -9,11 +9,16 @@ use Drupal\Core\Render\Attribute\FormElement;
* Provides a form element for input of multiple-line text.
*
* Properties:
- * - #rows: Number of rows in the text box.
- * - #cols: Number of columns in the text box.
- * - #resizable: Controls whether the text area is resizable. Allowed values
+ *
+ * @property $rows
+ * Number of rows in the text box.
+ * @property $cols
+ * Number of columns in the text box.
+ * @property $resizable
+ * Controls whether the text area is resizable. Allowed values
* are "none", "vertical", "horizontal", or "both" (defaults to "vertical").
- * - #maxlength: The maximum amount of characters to accept as input.
+ * @property $maxlength
+ * The maximum amount of characters to accept as input.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php
index de144ab4348d..47dbaf2b325a 100644
--- a/core/lib/Drupal/Core/Render/Element/Textfield.php
+++ b/core/lib/Drupal/Core/Render/Element/Textfield.php
@@ -10,13 +10,21 @@ use Drupal\Core\Render\Element;
* Provides a one-line text field form element.
*
* Properties:
- * - #maxlength: Maximum number of characters of input allowed.
- * - #size: The size of the input element in characters.
- * - #autocomplete_route_name: A route to be used as callback URL by the
+ *
+ * @property $maxlength
+ * Maximum number of characters of input allowed.
+ * @property $size
+ * The size of the input element in characters.
+ * @property $autocomplete_route_name
+ * A route to be used as callback URL by the
* autocomplete JavaScript library.
- * - #autocomplete_route_parameters: An array of parameters to be used in
+ * @property $autocomplete_route_parameters
+ * An array of parameters to be used in
* conjunction with the route name.
- * - #pattern: A string for the native HTML5 pattern attribute.
+ * @property $pattern
+ * A string for the native HTML5 pattern attribute.
+ * @property $placeholder
+ * A string to displayed in a textfield when it has no value.
*
* Usage example:
*
diff --git a/core/lib/Drupal/Core/Render/Element/TitleDisplay.php b/core/lib/Drupal/Core/Render/Element/TitleDisplay.php
new file mode 100644
index 000000000000..d194fd1b27a8
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/TitleDisplay.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Defines how and where a title should be displayed for a form element.
+ */
+enum TitleDisplay: string {
+
+ // Label goes before the element (default for most elements).
+ case Before = 'before';
+
+ // Label goes after the element (default for radio elements).
+ case After = 'after';
+
+ // Label is present in the markup but made invisible using CSS.
+ case Invisible = 'invisible';
+
+ // Label is set as the title attribute, displayed as a tooltip on hover.
+ case Attribute = 'attribute';
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Url.php b/core/lib/Drupal/Core/Render/Element/Url.php
index 7c5a9af2fbb4..1e7d499d8372 100644
--- a/core/lib/Drupal/Core/Render/Element/Url.php
+++ b/core/lib/Drupal/Core/Render/Element/Url.php
@@ -11,9 +11,13 @@ use Drupal\Core\Render\Element;
* Provides a form element for input of a URL.
*
* Properties:
- * - #default_value: A valid URL string.
- * - #size: The size of the input element in characters.
- * - #pattern: A string for the native HTML5 pattern attribute.
+ *
+ * @property $default_value
+ * A valid URL string.
+ * @property $size
+ * The size of the input element in characters.
+ * @property $pattern
+ * A string for the native HTML5 pattern attribute.
*
* Usage example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/Value.php b/core/lib/Drupal/Core/Render/Element/Value.php
index ce6efe9620ff..c1bb28348528 100644
--- a/core/lib/Drupal/Core/Render/Element/Value.php
+++ b/core/lib/Drupal/Core/Render/Element/Value.php
@@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement;
* in validation and submit processing.
*
* Properties:
- * - #value: The value of the form element that cannot be edited by the user.
+ *
+ * @property $value
+ * The value of the form element that cannot be edited by the user.
*
* Usage Example:
* @code
diff --git a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
index bf55119913b0..083b79018c0c 100644
--- a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
+++ b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
@@ -13,7 +13,9 @@ use Drupal\Core\Render\Element;
* this element's name as vertical tabs.
*
* Properties:
- * - #default_tab: The HTML ID of the rendered details element to be used as
+ *
+ * @property $default_tab
+ * The HTML ID of the rendered details element to be used as
* the default tab. View the source of the rendered page to determine the ID.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Weight.php b/core/lib/Drupal/Core/Render/Element/Weight.php
index 89df69b1879f..3fa4e2c8a969 100644
--- a/core/lib/Drupal/Core/Render/Element/Weight.php
+++ b/core/lib/Drupal/Core/Render/Element/Weight.php
@@ -12,7 +12,9 @@ use Drupal\Core\Render\Attribute\FormElement;
* the order.
*
* Properties:
- * - #delta: The range of possible weight values used. A delta of 10 would
+ *
+ * @property $delta
+ * The range of possible weight values used. A delta of 10 would
* indicate possible weight values between -10 and 10.
*
* Usage example:
diff --git a/core/lib/Drupal/Core/Render/Element/Widget.php b/core/lib/Drupal/Core/Render/Element/Widget.php
new file mode 100644
index 000000000000..ca5d87ea183e
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Widget.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Attribute\RenderElement;
+
+/**
+ * Provides a widget element.
+ *
+ * A form wrapper containing basic properties for the widget, attach
+ * the widget elements to this wrapper. This element renders to an empty
+ * string.
+ *
+ * @property $field_parents
+ * The 'parents' space for the field in the form. Most widgets can simply
+ * overlook this property. This identifies the location where the field
+ * values are placed within $form_state->getValues(), and is used to
+ * access processing information for the field through the
+ * WidgetBase::getWidgetState() and WidgetBase::setWidgetState() methods.
+ * @property $delta
+ * The order of this item in the array of sub-elements. (0, 1, 2, etc.)
+ */
+#[RenderElement('widget')]
+class Widget extends Generic {
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php
index 9f1c171cf569..b906c6204c26 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoManager.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php
@@ -2,6 +2,9 @@
namespace Drupal\Core\Render;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
+use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
@@ -11,6 +14,7 @@ use Drupal\Core\PreWarm\PreWarmableInterface;
use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Render\Element\ElementInterface;
use Drupal\Core\Render\Element\FormElementInterface;
+use Drupal\Core\Render\Element\Generic;
use Drupal\Core\Theme\ThemeManagerInterface;
/**
@@ -36,6 +40,16 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana
protected $elementInfo;
/**
+ * Class => plugin id mapping.
+ *
+ * More performant than reflecting runtime.
+ *
+ * @var array
+ * @internal
+ */
+ protected array $reverseMapping = [];
+
+ /**
* Constructs an ElementInfoManager object.
*
* @param \Traversable $namespaces
@@ -65,6 +79,79 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana
/**
* {@inheritdoc}
*/
+ protected function getDiscovery(): DiscoveryInterface {
+ $discovery = parent::getDiscovery();
+ return new class ($discovery, $this->reverseMapping) implements DiscoveryInterface {
+ use DiscoveryTrait;
+
+ public function __construct(protected DiscoveryInterface $decorated, protected array &$reverseMapping) {}
+
+ public function getDefinitions(): array {
+ $definitions = $this->decorated->getDefinitions();
+ foreach ($definitions as $element_type => $definition) {
+ $this->reverseMapping[$definition['class']] = $element_type;
+ }
+ return $definitions;
+ }
+
+ };
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getCachedDefinitions(): ?array {
+ if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) {
+ $this->definitions = $cache->data['definitions'];
+ $this->reverseMapping = $cache->data['reverse_mapping'];
+ }
+ return $this->definitions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setCachedDefinitions($definitions): void {
+ $data = [
+ 'definitions' => $definitions,
+ 'reverse_mapping' => $this->reverseMapping,
+ ];
+ $this->cacheSet($this->cacheKey, $data, Cache::PERMANENT, $this->cacheTags);
+ $this->definitions = $definitions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clearCachedDefinitions(): void {
+ $this->elementInfo = NULL;
+
+ $cids = [];
+ foreach ($this->themeHandler->listInfo() as $theme_name => $info) {
+ $cids[] = $this->getCid($theme_name);
+ }
+
+ $this->cacheBackend->deleteMultiple($cids);
+
+ parent::clearCachedDefinitions();
+ }
+
+ /**
+ * Returns the CID used to cache the element info.
+ *
+ * @param string $theme_name
+ * The theme name.
+ *
+ * @return string
+ * The cache ID.
+ */
+ protected function getCid($theme_name): string {
+ return 'element_info_build:' . $theme_name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
public function getInfo($type) {
$theme_name = $this->themeManager->getActiveTheme()->getName();
if (!isset($this->elementInfo[$theme_name])) {
@@ -144,37 +231,47 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana
* @return \Drupal\Core\Render\Element\ElementInterface
* The render element plugin instance.
*/
- public function createInstance($plugin_id, array $configuration = []) {
- return parent::createInstance($plugin_id, $configuration);
+ public function createInstance($plugin_id, array $configuration = [], &$element = []): ElementInterface {
+ $instance = parent::createInstance($plugin_id, $configuration);
+ assert($instance instanceof ElementInterface);
+ $instance->initializeInternalStorage($element);
+ return $instance;
}
/**
* {@inheritdoc}
*/
- public function clearCachedDefinitions() {
- $this->elementInfo = NULL;
-
- $cids = [];
- foreach ($this->themeHandler->listInfo() as $theme_name => $info) {
- $cids[] = $this->getCid($theme_name);
+ public function fromClass(string $class, array $configuration = []): ElementInterface {
+ $this->getDefinitions();
+ if ($id = $this->getIdFromClass($class)) {
+ return $this->createInstance($id, $configuration);
}
+ throw new \LogicException("$class is not a valid element class.");
+ }
- $this->cacheBackend->deleteMultiple($cids);
-
- parent::clearCachedDefinitions();
+ /**
+ * {@inheritdoc}
+ */
+ public function getIdFromClass(string $class): ?string {
+ $this->getDefinitions();
+ return $this->reverseMapping[$class] ?? NULL;
}
/**
- * Returns the CID used to cache the element info.
- *
- * @param string $theme_name
- * The theme name.
- *
- * @return string
- * The cache ID.
+ * {@inheritdoc}
*/
- protected function getCid($theme_name) {
- return 'element_info_build:' . $theme_name;
+ public function fromRenderable(ElementInterface|array &$element, string $class = Generic::class): ElementInterface {
+ if ($element instanceof ElementInterface) {
+ return $element;
+ }
+ if (isset($element['##object']) && $element['##object'] instanceof ElementInterface) {
+ return $element['##object']->initializeInternalStorage($element);
+ }
+ $type = $element['#type'] ?? $this->getIdFromClass($class);
+ if (!$type) {
+ throw new \LogicException('The element passed to ElementInfoManager::fromRenderable must have a #type or a valid class must be provided.');
+ }
+ return $this->createInstance($type, element: $element);
}
}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
index ea6f21b849cd..e1a891f47643 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
@@ -3,11 +3,14 @@
namespace Drupal\Core\Render;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Form;
/**
* Collects available render array element types.
*/
-interface ElementInfoManagerInterface extends DiscoveryInterface {
+interface ElementInfoManagerInterface extends DiscoveryInterface, FactoryInterface {
/**
* Retrieves the default properties for the defined element type.
@@ -61,4 +64,61 @@ interface ElementInfoManagerInterface extends DiscoveryInterface {
*/
public function getInfoProperty($type, $property_name, $default = NULL);
+ /**
+ * Creates a render object from a render array.
+ *
+ * @param \Drupal\Core\Render\Element\ElementInterface|array $element
+ * A render array or render objects. The latter is returned unchanged.
+ * @param class-string<T> $class
+ * The class of the render object being created.
+ *
+ * @return T
+ * A render object.
+ *
+ * @template T of ElementInterface
+ */
+ public function fromRenderable(ElementInterface|array &$element, string $class = Form::class): ElementInterface;
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return \Drupal\Core\Render\Element\ElementInterface
+ * A fully configured render object.
+ */
+ public function createInstance($plugin_id, array $configuration = []): ElementInterface;
+
+ /**
+ * Creates a render object based on the provided class and configuration.
+ *
+ * @param class-string<T> $class
+ * The class of the render object being instantiated.
+ * @param array $configuration
+ * An array of configuration relevant to the render object.
+ *
+ * @return T
+ * A fully configured render object.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\PluginException
+ * If the render object cannot be created, such as if the class is invalid.
+ *
+ * @template T of ElementInterface
+ */
+ public function fromClass(string $class, array $configuration = []): ElementInterface;
+
+ /**
+ * Get the plugin ID from the class.
+ *
+ * Whenever possible, use the class type inference. Calling this method
+ * should not be necessary.
+ *
+ * @param string $class
+ * The class of an element object.
+ *
+ * @return ?string
+ * The plugin ID or null if not found.
+ *
+ * @internal
+ */
+ public function getIdFromClass(string $class): ?string;
+
}
diff --git a/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php b/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php
index 4467632eeb77..7052aab1d5d0 100644
--- a/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php
+++ b/core/lib/Drupal/Core/TempStore/Element/BreakLockLink.php
@@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Render\Element\RenderElementBase;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -14,9 +15,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* Provides a link to break a tempstore lock.
*
* Properties:
- * - #label: The label of the object that is locked.
- * - #lock: \Drupal\Core\TempStore\Lock object.
- * - #url: \Drupal\Core\Url object pointing to the break lock form.
+ *
+ * @property $label
+ * The label of the object that is locked.
+ * @property $lock
+ * \Drupal\Core\TempStore\Lock object.
+ * @property $url
+ * \Drupal\Core\Url object pointing to the break lock form.
*
* Usage example:
* @code
@@ -67,9 +72,19 @@ class BreakLockLink extends RenderElementBase implements ContainerFactoryPluginI
* The entity type manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager
+ * The element info manager.
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatterInterface $date_formatter, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ public function __construct(
+ array $configuration,
+ $plugin_id,
+ $plugin_definition,
+ DateFormatterInterface $date_formatter,
+ EntityTypeManagerInterface $entity_type_manager,
+ RendererInterface $renderer,
+ ElementInfoManagerInterface $elementInfoManager,
+ ) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
$this->dateFormatter = $date_formatter;
$this->entityTypeManager = $entity_type_manager;
@@ -86,7 +101,8 @@ class BreakLockLink extends RenderElementBase implements ContainerFactoryPluginI
$plugin_definition,
$container->get('date.formatter'),
$container->get('entity_type.manager'),
- $container->get('renderer')
+ $container->get('renderer'),
+ $container->get('plugin.manager.element_info')
);
}
diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
index 4e8e77ff7da2..5a130bf160c3 100644
--- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
+++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
@@ -9,6 +9,7 @@ use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\content_moderation\ModerationInformation;
use Drupal\content_moderation\StateTransitionValidationInterface;
@@ -66,6 +67,8 @@ class ModerationStateWidget extends OptionsSelectWidget {
* Field settings.
* @param array $third_party_settings
* Third party settings.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager
+ * The element info manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
@@ -75,8 +78,8 @@ class ModerationStateWidget extends OptionsSelectWidget {
* @param \Drupal\content_moderation\StateTransitionValidationInterface $validator
* Moderation state transition validation service.
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidationInterface $validator) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ModerationInformation $moderation_information, StateTransitionValidationInterface $validator) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
$this->moderationInformation = $moderation_information;
@@ -93,6 +96,7 @@ class ModerationStateWidget extends OptionsSelectWidget {
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
+ $container->get('plugin.manager.element_info'),
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('content_moderation.moderation_information'),
diff --git a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
index 9055d44982bf..4fc259a760a1 100644
--- a/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
+++ b/core/modules/datetime/src/Plugin/Field/FieldWidget/DateTimeDefaultWidget.php
@@ -7,6 +7,7 @@ use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -31,8 +32,8 @@ class DateTimeDefaultWidget extends DateTimeWidgetBase {
/**
* {@inheritdoc}
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityStorageInterface $date_storage) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
$this->dateStorage = $date_storage;
}
@@ -47,7 +48,8 @@ class DateTimeDefaultWidget extends DateTimeWidgetBase {
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
- $container->get('entity_type.manager')->getStorage('date_format')
+ $container->get('plugin.manager.element_info'),
+ $container->get('entity_type.manager')->getStorage('date_format'),
);
}
diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
index 04c6f4eaf1c2..b2f90d662e9a 100644
--- a/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
+++ b/core/modules/datetime_range/src/Plugin/Field/FieldWidget/DateRangeDefaultWidget.php
@@ -7,6 +7,7 @@ use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -31,8 +32,8 @@ class DateRangeDefaultWidget extends DateRangeWidgetBase {
/**
* {@inheritdoc}
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityStorageInterface $date_storage) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
$this->dateStorage = $date_storage;
}
@@ -47,6 +48,7 @@ class DateRangeDefaultWidget extends DateRangeWidgetBase {
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
+ $container->get('plugin.manager.element_info'),
$container->get('entity_type.manager')->getStorage('date_format')
);
}
diff --git a/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php b/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php
index 369575020135..dd9a4167ba53 100644
--- a/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php
+++ b/core/modules/field/tests/modules/field_third_party_test/src/Hook/FieldThirdPartyTestHooks.php
@@ -9,6 +9,9 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Render\Element\Number;
+use Drupal\Core\Render\Element\Textfield;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
@@ -18,17 +21,17 @@ class FieldThirdPartyTestHooks {
use StringTranslationTrait;
+ public function __construct(protected ElementInfoManagerInterface $elementInfoManager) {}
+
/**
* Implements hook_field_widget_third_party_settings_form().
*/
#[Hook('field_widget_third_party_settings_form')]
public function fieldWidgetThirdPartySettingsForm(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array {
- $element['field_test_widget_third_party_settings_form'] = [
- '#type' => 'textfield',
- '#title' => $this->t('3rd party widget settings form'),
- '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form'),
- ];
- return $element;
+ $textfield = $this->elementInfoManager->fromClass(Textfield::class);
+ $textfield->title = $this->t('3rd party widget settings form');
+ $textfield->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form');
+ return $textfield->toRenderable('field_test_widget_third_party_settings_form');
}
/**
@@ -36,12 +39,10 @@ class FieldThirdPartyTestHooks {
*/
#[Hook('field_widget_third_party_settings_form')]
public function fieldWidgetThirdPartySettingsFormAdditionalImplementation(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array {
- $element['second_field_widget_third_party_settings_form'] = [
- '#type' => 'number',
- '#title' => $this->t('Second 3rd party widget settings form'),
- '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_widget_third_party_settings_form'),
- ];
- return $element;
+ $number = $this->elementInfoManager->fromClass(Number::class);
+ $number->title = $this->t('Second 3rd party widget settings form');
+ $number->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'second_field_widget_third_party_settings_form');
+ return $number->toRenderable('second_field_widget_third_party_settings_form');
}
/**
@@ -57,12 +58,10 @@ class FieldThirdPartyTestHooks {
*/
#[Hook('field_formatter_third_party_settings_form')]
public function fieldFormatterThirdPartySettingsForm(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array {
- $element['field_test_field_formatter_third_party_settings_form'] = [
- '#type' => 'textfield',
- '#title' => $this->t('3rd party formatter settings form'),
- '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form'),
- ];
- return $element;
+ $textfield = $this->elementInfoManager->fromClass(Textfield::class);
+ $textfield->title = $this->t('3rd party formatter settings form');
+ $textfield->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form');
+ return $textfield->toRenderable('field_test_field_formatter_third_party_settings_form');
}
/**
@@ -70,12 +69,10 @@ class FieldThirdPartyTestHooks {
*/
#[Hook('field_formatter_third_party_settings_form')]
public function fieldFormatterThirdPartySettingsFormAdditionalImplementation(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array {
- $element['second_field_formatter_third_party_settings_form'] = [
- '#type' => 'number',
- '#title' => $this->t('Second 3rd party formatter settings form'),
- '#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_formatter_third_party_settings_form'),
- ];
- return $element;
+ $number = $this->elementInfoManager->fromClass(Number::class);
+ $number->title = $this->t('Second 3rd party formatter settings form');
+ $number->default_value = $plugin->getThirdPartySetting('field_third_party_test', 'second_field_formatter_third_party_settings_form');
+ return $number->toRenderable('second_field_formatter_third_party_settings_form');
}
/**
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index a38e64ab53cc..bb4268ff389e 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -29,16 +29,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
class FileWidget extends WidgetBase {
/**
- * The element info manager.
- */
- protected ElementInfoManagerInterface $elementInfo;
-
- /**
* {@inheritdoc}
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
- $this->elementInfo = $element_info;
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $element_info);
}
/**
@@ -238,7 +232,7 @@ class FileWidget extends WidgetBase {
// Essentially we use the managed_file type, extended with some
// enhancements.
- $element_info = $this->elementInfo->getInfo('managed_file');
+ $element_info = $this->elementInfoManager->getInfo('managed_file');
$element += [
'#type' => 'managed_file',
'#upload_location' => $items[$delta]->getUploadLocation(),
diff --git a/core/modules/filter/src/Element/TextFormat.php b/core/modules/filter/src/Element/TextFormat.php
index 38ca133e3cd2..6b1fc25c3ffb 100644
--- a/core/modules/filter/src/Element/TextFormat.php
+++ b/core/modules/filter/src/Element/TextFormat.php
@@ -12,11 +12,15 @@ use Drupal\Core\Url;
* Provides a text format render element.
*
* Properties:
- * - #base_type: The form element #type to use for the 'value' element.
+ *
+ * @property $base_type
+ * The form element #type to use for the 'value' element.
* 'textarea' by default.
- * - #format: (optional) The text format ID to preselect. If omitted, the
+ * @property $format
+ * (optional) The text format ID to preselect. If omitted, the
* default format for the current user will be used.
- * - #allowed_formats: (optional) An array of text format IDs that are available
+ * @property $allowed_formats
+ * (optional) An array of text format IDs that are available
* for this element. If omitted, all text formats that the current user has
* access to will be allowed.
*
diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php
index d673d24d1d38..2b128f931887 100644
--- a/core/modules/layout_builder/src/Element/LayoutBuilder.php
+++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
@@ -11,6 +11,7 @@ use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\RenderElementBase;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Security\Attribute\TrustedCallback;
use Drupal\Core\Url;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
@@ -52,9 +53,11 @@ class LayoutBuilder extends RenderElementBase implements ContainerFactoryPluginI
* The plugin implementation definition.
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher service.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface|null $elementInfoManager
+ * The element info manager.
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher, ?ElementInfoManagerInterface $elementInfoManager = NULL) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
$this->eventDispatcher = $event_dispatcher;
}
@@ -66,7 +69,8 @@ class LayoutBuilder extends RenderElementBase implements ContainerFactoryPluginI
$configuration,
$plugin_id,
$plugin_definition,
- $container->get('event_dispatcher')
+ $container->get('event_dispatcher'),
+ $container->get('plugin.manager.element_info')
);
}
diff --git a/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php b/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
index 5fa53b2b9e92..0230cc19b465 100644
--- a/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
+++ b/core/modules/media/src/Plugin/Field/FieldWidget/OEmbedWidget.php
@@ -7,6 +7,8 @@ use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Widget;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Entity\MediaType;
use Drupal\media\Plugin\media\Source\OEmbedInterface;
@@ -28,24 +30,35 @@ class OEmbedWidget extends StringTextfieldWidget {
/**
* {@inheritdoc}
*/
- public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
- $element = parent::formElement($items, $delta, $element, $form, $form_state);
+ public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
+ $widget = parent::singleElementObject($items, $delta, $widget, $form, $form_state);
+ $value = $widget->getChild('value');
+ $value->description = $this->getValueDescription($items, $value->description);
+ return $widget;
+ }
+ /**
+ * Merges description and provider messages.
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $items
+ * FieldItemList containing the values to be edited.
+ * @param scalar|\Stringable|\Drupal\Core\Render\RenderableInterface|array $description
+ * The description on the form element.
+ *
+ * @return string|array
+ * The description on the value child.
+ */
+ protected function getValueDescription(FieldItemListInterface $items, mixed $description): string|array {
/** @var \Drupal\media\Plugin\media\Source\OEmbedInterface $source */
$source = $items->getEntity()->getSource();
$message = $this->t('You can link to media from the following services: @providers', ['@providers' => implode(', ', $source->getProviders())]);
-
- if (!empty($element['value']['#description'])) {
- $element['value']['#description'] = [
+ if ($description) {
+ return [
'#theme' => 'item_list',
- '#items' => [$element['value']['#description'], $message],
+ '#items' => [$description, $message],
];
}
- else {
- $element['value']['#description'] = $message;
- }
-
- return $element;
+ return $message;
}
/**
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index 3b36ee5e3773..33ffe1a39920 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -18,6 +18,7 @@ use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -78,6 +79,8 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
+ * @param \Drupal\Core\Render\ElementInfoManagerInterface $elementInfoManager
+ * The element info manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Drupal\Core\Session\AccountInterface $current_user
@@ -85,8 +88,8 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleHandlerInterface $module_handler) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
+ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $elementInfoManager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleHandlerInterface $module_handler) {
+ parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $elementInfoManager);
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
$this->moduleHandler = $module_handler;
@@ -102,6 +105,7 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
+ $container->get('plugin.manager.element_info'),
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('module_handler')
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index f14d843faa10..1fb4071ba47d 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -36,8 +36,12 @@ use Drupal\node\NodeTypeInterface;
* @return array|false
* A renderable array containing a list of linked node titles fetched from
* $result, or FALSE if there are no rows in $result.
+ *
+ * @deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement.
+ * @see https://www.drupal.org/node/3531959
*/
function node_title_list(StatementInterface $result, $title = NULL) {
+ @trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3531959', E_USER_DEPRECATED);
$items = [];
$num_rows = FALSE;
$nids = [];
diff --git a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
index f54ad8f9a9a3..c052c5a488fb 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Element/Deprecated.php
@@ -17,7 +17,8 @@ class Deprecated extends RenderElementBase {
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
+ $elementInfoManager = \Drupal::service('plugin.manager.element_info');
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $elementInfoManager);
@trigger_error(__CLASS__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/3068104', E_USER_DEPRECATED);
}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
index ea72bd033fb9..53150495cb9b 100644
--- a/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
+++ b/core/modules/system/tests/modules/element_info_test/src/Hook/ElementInfoTestHooks.php
@@ -4,8 +4,9 @@ declare(strict_types=1);
namespace Drupal\element_info_test\Hook;
-use Drupal\element_info_test\ElementInfoTestNumberBuilder;
use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\element_info_test\ElementInfoTestNumberBuilder;
+use Drupal\element_info_test\Render\Element\Details;
/**
* Hook implementations for element_info_test.
@@ -30,6 +31,9 @@ class ElementInfoTestHooks {
if (\Drupal::state()->get('hook_element_plugin_alter:remove_weight', FALSE)) {
unset($definitions['weight']);
}
+
+ $definitions['details']['class'] = Details::class;
+ $definitions['details']['provider'] = 'element_info_test';
}
}
diff --git a/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php
new file mode 100644
index 000000000000..8e4d84558ca4
--- /dev/null
+++ b/core/modules/system/tests/modules/element_info_test/src/Render/Element/Details.php
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\element_info_test\Render\Element;
+
+use Drupal\Core\Render\Attribute\RenderElement;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\RenderElementBase;
+
+/**
+ * Provides a render element for a details element.
+ *
+ * Properties:
+ *
+ * @property $title
+ * The title of the details container. Defaults to "Details".
+ * @property $open
+ * Indicates whether the container should be open by default.
+ * Defaults to FALSE.
+ * @property $custom
+ * Confirm that this class has been swapped properly.
+ * @property $summary_attributes
+ * An array of attributes to apply to the <summary>
+ * element.
+ */
+#[RenderElement('details')]
+class Details extends RenderElementBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo(): array {
+ return [
+ '#open' => FALSE,
+ '#summary_attributes' => [],
+ '#custom' => 'Custom',
+ ];
+ }
+
+ /**
+ * Adds form element theming to details.
+ *
+ * @param array $element
+ * An associative array containing the properties and children of the
+ * details.
+ *
+ * @return array
+ * The modified element.
+ */
+ public static function preRenderDetails($element): array {
+ Element::setAttributes($element, ['custom']);
+
+ return $element;
+ }
+
+}
diff --git a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
index cda2b92b3477..441ebaa1d128 100644
--- a/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
+++ b/core/modules/system/tests/modules/form_test/src/Hook/FormTestHooks.php
@@ -6,6 +6,8 @@ namespace Drupal\form_test\Hook;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Render\Element\Submit;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\form_test\Callbacks;
@@ -16,6 +18,8 @@ class FormTestHooks {
use StringTranslationTrait;
+ public function __construct(protected ElementInfoManagerInterface $elementInfoManager) {}
+
/**
* Implements hook_form_FORM_ID_alter().
*/
@@ -55,13 +59,10 @@ class FormTestHooks {
*/
#[Hook('form_user_register_form_alter')]
public function formUserRegisterFormAlter(&$form, FormStateInterface $form_state) : void {
- $form['test_rebuild'] = [
- '#type' => 'submit',
- '#value' => $this->t('Rebuild'),
- '#submit' => [
- [Callbacks::class, 'userRegisterFormRebuild'],
- ],
- ];
+ $submit = $this->elementInfoManager->fromRenderable($form)
+ ->createChild('test_rebuild', Submit::class);
+ $submit->value = $this->t('Rebuild');
+ $submit->submit = [[Callbacks::class, 'userRegisterFormRebuild']];
}
/**
@@ -69,11 +70,12 @@ class FormTestHooks {
*/
#[Hook('form_form_test_vertical_tabs_access_form_alter')]
public function formFormTestVerticalTabsAccessFormAlter(&$form, &$form_state, $form_id) : void {
- $form['vertical_tabs1']['#access'] = FALSE;
- $form['vertical_tabs2']['#access'] = FALSE;
- $form['tabs3']['#access'] = TRUE;
- $form['fieldset1']['#access'] = FALSE;
- $form['container']['#access'] = FALSE;
+ $element_object = $this->elementInfoManager->fromRenderable($form);
+ $element_object->getChild('vertical_tabs1')->access = FALSE;
+ $element_object->getChild('vertical_tabs2')->access = FALSE;
+ $element_object->getChild('tab3')->access = FALSE;
+ $element_object->getChild('fieldset1')->access = FALSE;
+ $element_object->getChild('container')->access = FALSE;
}
}
diff --git a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
index 16b7549681c1..289fbbd332cd 100644
--- a/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
+++ b/core/modules/text/src/Plugin/Field/FieldWidget/TextfieldWidget.php
@@ -6,7 +6,10 @@ use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element\ElementInterface;
+use Drupal\Core\Render\Element\Widget;
use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\filter\Element\TextFormat;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
@@ -22,20 +25,20 @@ class TextfieldWidget extends StringTextfieldWidget {
/**
* {@inheritdoc}
*/
- public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
- $main_widget = parent::formElement($items, $delta, $element, $form, $form_state);
+ public function singleElementObject(FieldItemListInterface $items, $delta, Widget $widget, ElementInterface $form, FormStateInterface $form_state): ElementInterface {
+ $widget = parent::singleElementObject($items, $delta, $widget, $form, $form_state);
$allowed_formats = $this->getFieldSetting('allowed_formats');
- $element = $main_widget['value'];
- $element['#type'] = 'text_format';
- $element['#format'] = $items[$delta]->format ?? NULL;
- $element['#base_type'] = $main_widget['value']['#type'];
-
+ $widget = $widget->getChild('value');
+ $type = $widget->type;
+ $widget = $widget->changeType(TextFormat::class);
+ $widget->format = $items[$delta]->format ?? NULL;
+ $widget->base_type = $type;
if ($allowed_formats && !$this->isDefaultValueWidget($form_state)) {
- $element['#allowed_formats'] = $allowed_formats;
+ $widget->allowed_formats = $allowed_formats;
}
- return $element;
+ return $widget;
}
/**
diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php
index 7ad8afa75be6..c300cd4c019f 100644
--- a/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Render/Element/PluginAlterTest.php
@@ -23,7 +23,7 @@ class PluginAlterTest extends KernelTestBase {
$info_manager = $this->container->get('plugin.manager.element_info');
$this->assertArrayHasKey('weight', $info_manager->getDefinitions());
- // @see element_info_test_element_plugin_alter()
+ // @see ElementInfoTestHooks::elementPluginAlter().
$this->container->get('state')->set('hook_element_plugin_alter:remove_weight', TRUE);
// The definition will be cached.
$this->assertArrayHasKey('weight', $info_manager->getDefinitions());
@@ -33,4 +33,27 @@ class PluginAlterTest extends KernelTestBase {
$this->assertArrayNotHasKey('weight', $info_manager->getDefinitions());
}
+ /**
+ * Tests hook_element_plugin_alter().
+ */
+ public function testPluginClassSwap(): void {
+ $info_manager = $this->container->get('plugin.manager.element_info');
+ $test_details = [
+ '#type' => 'details',
+ '#title' => 'Title',
+ '#description' => 'Description',
+ '#open' => TRUE,
+ ];
+
+ // @see ElementInfoTestHooks::elementPluginAlter().
+ $expected = [
+ 'class' => 'Drupal\element_info_test\Render\Element\Details',
+ 'provider' => 'element_info_test',
+ 'id' => 'details',
+ ];
+ $this->assertEquals($expected, $info_manager->getDefinitions()['details']);
+ \Drupal::service('renderer')->renderRoot($test_details);
+ $this->assertArrayHasKey('#custom', $test_details);
+ }
+
}
diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
index 00b7948f2a81..e36da16d9028 100644
--- a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
@@ -8,6 +8,7 @@ use Drupal\Core\Form\FormState;
use Drupal\Core\Render\Element\Number;
use Drupal\Core\Render\Element\Select;
use Drupal\Core\Render\Element\Weight;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\element_info_test\ElementInfoTestNumberBuilder;
use Drupal\KernelTests\KernelTestBase;
@@ -40,7 +41,7 @@ class WeightTest extends KernelTestBase {
$form_state = new FormState();
$complete_form = [];
- $element_object = new Weight([], 'weight', []);
+ $element_object = new Weight([], 'weight', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class));
$info = $element_object->getInfo();
$element += $info;
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php b/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php
index d0c09e97fc53..8490a5c0876e 100644
--- a/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/Element/HtmlTagTest.php
@@ -6,6 +6,7 @@ namespace Drupal\Tests\Core\Render\Element;
use Drupal\Core\Render\Markup;
use Drupal\Tests\Core\Render\RendererTestBase;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Render\Element\HtmlTag;
/**
@@ -18,7 +19,7 @@ class HtmlTagTest extends RendererTestBase {
* @covers ::getInfo
*/
public function testGetInfo(): void {
- $htmlTag = new HtmlTag([], 'test', 'test');
+ $htmlTag = new HtmlTag([], 'test', 'test', elementInfoManager: $this->createStub(ElementInfoManagerInterface::class));
$info = $htmlTag->getInfo();
$this->assertArrayHasKey('#pre_render', $info);
$this->assertArrayHasKey('#attributes', $info);
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php b/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php
new file mode 100644
index 000000000000..c91b74f8c0a4
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/Element/ModernRenderElementTest.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\Render\Element;
+
+use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\Textfield;
+use Drupal\Core\Render\ElementInfoManager;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Element\RenderElementBase
+ * @group Render
+ */
+class ModernRenderElementTest extends UnitTestCase {
+
+ public function testChildren(): void {
+ $factory = $this->createMock(FactoryInterface::class);
+ $elementInfoManager = new class ($factory) extends ElementInfoManager {
+
+ public function __construct(protected $factory) {}
+
+ };
+ $factory->expects($this->any())
+ ->method('createInstance')
+ ->willReturnCallback(fn () => new Textfield([], '', NULL, $elementInfoManager));
+ // If the type is not given ::fromRenderable presumes "form" and uses the
+ // plugin discovery to find which class provides the form element. This
+ // test does not set up discovery so some type must be provided.
+ $element = ['#type' => 'ignored by the mock factory'];
+ $elementObject = $elementInfoManager->fromRenderable($element);
+ for ($i = 0; $i <= 2; $i++) {
+ $child = [
+ '#type' => 'ignored by the mock factory',
+ '#test' => $i,
+ ];
+ $elementObject->addChild("test$i", $child);
+ // addChild() takes the $child render array by reference and stores a
+ // reference to it in the render object. To avoid modifying the
+ // previously created render object when reusing the $child variable,
+ // unset() it to break the reference before reassigning.
+ unset($child);
+ }
+ foreach ([1 => ['test0', 'test1', 'test2'], 2 => ['test0', 'test2']] as $delta => $expectedChildrenKeys) {
+ $i = 0;
+ foreach ($elementObject->getChildren() as $name => $child) {
+ $this->assertSame($name, "test$i");
+ $this->assertSame($i, $child->test);
+ $i += $delta;
+ }
+ $this->assertSame(Element::children($elementObject->toRenderable()), $expectedChildrenKeys);
+ // The first iteration tests removing an existing child. The second
+ // iteration tests removing a nonexistent child.
+ $elementObject->removeChild('test1');
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php b/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php
index fc58c1db4efe..7acfef4ca509 100644
--- a/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/Element/TableSelectTest.php
@@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Render\Element;
use Drupal\Core\Form\FormState;
use Drupal\Core\Link;
use Drupal\Core\Render\Element\Tableselect;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
@@ -25,7 +26,7 @@ class TableSelectTest extends UnitTestCase {
$form_state = new FormState();
$complete_form = [];
- $element_object = new Tableselect([], 'table_select', []);
+ $element_object = new Tableselect([], 'table_select', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class));
$info = $element_object->getInfo();
$element += $info;
@@ -50,7 +51,7 @@ class TableSelectTest extends UnitTestCase {
$form_state = new FormState();
$complete_form = [];
- $element_object = new Tableselect([], 'table_select', []);
+ $element_object = new Tableselect([], 'table_select', [], elementInfoManager: $this->createStub(ElementInfoManagerInterface::class));
$info = $element_object->getInfo();
$element += $info;
diff --git a/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php b/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php
index 2c67cf9193fb..d1a6e6433407 100644
--- a/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php
+++ b/core/tests/Drupal/Tests/Core/Theme/Icon/IconTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\Core\Theme\Icon;
use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Render\Element\Icon;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\Icon\IconDefinition;
@@ -41,7 +42,7 @@ class IconTest extends UnitTestCase {
* Test the Icon::getInfo method.
*/
public function testGetInfo(): void {
- $icon = new Icon([], 'test', 'test');
+ $icon = new Icon([], 'test', 'test', elementInfoManager: $this->createStub(ElementInfoManagerInterface::class));
$info = $icon->getInfo();
$this->assertArrayHasKey('#pre_render', $info);