EntityForm.php
Same filename in other branches
Namespace
Drupal\Core\EntityFile
-
core/
lib/ Drupal/ Core/ Entity/ EntityForm.php
View source
<?php
namespace Drupal\Core\Entity;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Base class for entity forms.
*
* @ingroup entity_api
*/
class EntityForm extends FormBase implements EntityFormInterface {
/**
* The name of the current operation.
*
* Subclasses may use this to implement different behaviors depending on its
* value.
*
* @var string
*/
protected $operation;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function setOperation($operation) {
// If NULL is passed, do not overwrite the operation.
if ($operation) {
$this->operation = $operation;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
// Assign ENTITY_TYPE_form as base form ID to invoke corresponding
// hook_form_alter(), #validate, #submit, and #theme callbacks, but only if
// it is different from the actual form ID, since callbacks would be invoked
// twice otherwise.
$base_form_id = $this->entity
->getEntityTypeId() . '_form';
if ($base_form_id == $this->getFormId()) {
$base_form_id = NULL;
}
return $base_form_id;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
$form_id = $this->entity
->getEntityTypeId();
if ($this->entity
->getEntityType()
->hasKey('bundle')) {
$form_id .= '_' . $this->entity
->bundle();
}
if ($this->operation != 'default') {
$form_id = $form_id . '_' . $this->operation;
}
return $form_id . '_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// During the initial form build, add this form object to the form state and
// allow for initial preparation before form building and processing.
if (!$form_state->has('entity_form_initialized')) {
$this->init($form_state);
}
// Ensure that edit forms have the correct cacheability metadata so they can
// be cached.
if (!$this->entity
->isNew()) {
\Drupal::service('renderer')->addCacheableDependency($form, $this->entity);
}
// Retrieve the form array using the possibly updated entity in form state.
$form = $this->form($form, $form_state);
// Retrieve and add the form actions array.
$actions = $this->actionsElement($form, $form_state);
if (!empty($actions)) {
$form['actions'] = $actions;
}
return $form;
}
/**
* Initialize the form state and the entity before the first form build.
*/
protected function init(FormStateInterface $form_state) {
// Flag that this form has been initialized.
$form_state->set('entity_form_initialized', TRUE);
// Prepare the entity to be presented in the entity form.
$this->prepareEntity();
// Invoke the prepare form hooks.
$this->prepareInvokeAll('entity_prepare_form', $form_state);
$this->prepareInvokeAll($this->entity
->getEntityTypeId() . '_prepare_form', $form_state);
}
/**
* Gets the actual form array to be built.
*
* @see \Drupal\Core\Entity\EntityForm::processForm()
* @see \Drupal\Core\Entity\EntityForm::afterBuild()
*/
public function form(array $form, FormStateInterface $form_state) {
// Add #process and #after_build callbacks.
$form['#process'][] = '::processForm';
$form['#after_build'][] = '::afterBuild';
return $form;
}
/**
* Process callback: assigns weights and hides extra fields.
*
* @see \Drupal\Core\Entity\EntityForm::form()
*/
public function processForm($element, FormStateInterface $form_state, $form) {
// If the form is cached, process callbacks may not have a valid reference
// to the entity object, hence we must restore it.
$this->entity = $form_state->getFormObject()
->getEntity();
return $element;
}
/**
* Form element #after_build callback: Updates the entity with submitted data.
*
* Updates the internal $this->entity object with submitted values when the
* form is being rebuilt (e.g. submitted via AJAX), so that subsequent
* processing (e.g. AJAX callbacks) can rely on it.
*/
public function afterBuild(array $element, FormStateInterface $form_state) {
// Rebuild the entity if #after_build is being called as part of a form
// rebuild, i.e. if we are processing input.
if ($form_state->isProcessingInput()) {
$this->entity = $this->buildEntity($element, $form_state);
}
return $element;
}
/**
* Returns the action form element for the current entity form.
*/
protected function actionsElement(array $form, FormStateInterface $form_state) {
$element = $this->actions($form, $form_state);
if (isset($element['delete'])) {
// Move the delete action as last one, unless weights are explicitly
// provided.
$delete = $element['delete'];
unset($element['delete']);
$element['delete'] = $delete;
$element['delete']['#button_type'] = 'danger';
}
if (isset($element['submit'])) {
// Give the primary submit button a #button_type of primary.
$element['submit']['#button_type'] = 'primary';
}
$count = 0;
foreach (Element::children($element) as $action) {
$element[$action] += [
'#weight' => ++$count * 5,
];
}
if (!empty($element)) {
$element['#type'] = 'actions';
}
return $element;
}
/**
* Returns an array of supported actions for the current entity form.
*
* This function generates a list of Form API elements which represent
* actions supported by the current entity form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* An array of supported Form API action elements keyed by name.
*
* @todo Consider introducing a 'preview' action here, since it is used by
* many entity types.
*/
protected function actions(array $form, FormStateInterface $form_state) {
// @todo Consider renaming the action key from submit to save. The impacts
// are hard to predict. For example, see
// \Drupal\language\Element\LanguageConfiguration::processLanguageConfiguration().
$actions['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
'#submit' => [
'::submitForm',
'::save',
],
];
if (!$this->entity
->isNew() && $this->entity
->hasLinkTemplate('delete-form')) {
$route_info = $this->entity
->toUrl('delete-form');
if ($this->getRequest()->query
->has('destination')) {
$query = $route_info->getOption('query');
$query['destination'] = $this->getRequest()->query
->get('destination');
$route_info->setOption('query', $query);
}
$actions['delete'] = [
'#type' => 'link',
'#title' => $this->t('Delete'),
'#access' => $this->entity
->access('delete'),
'#attributes' => [
'class' => [
'button',
'button--danger',
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 880,
]),
],
'#url' => $route_info,
'#attached' => [
'library' => [
'core/drupal.dialog.ajax',
],
],
];
}
return $actions;
}
/**
* {@inheritdoc}
*
* This is the default entity object builder function. It is called before any
* other submit handler to build the new entity object to be used by the
* following submit handlers. At this point of the form workflow the entity is
* validated and the form state can be updated, this way the subsequently
* invoked handlers can retrieve a regular entity object to act on. Generally
* this method should not be overridden unless the entity requires the same
* preparation for two actions, see \Drupal\comment\CommentForm for an example
* with the save and preview actions.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Remove button and internal Form API values from submitted values.
$form_state->cleanValues();
$this->entity = $this->buildEntity($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
return $this->entity
->save();
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
$entity = clone $this->entity;
$this->copyFormValuesToEntity($entity, $form, $form_state);
// Invoke all specified builders for copying form values to entity
// properties.
if (isset($form['#entity_builders'])) {
foreach ($form['#entity_builders'] as $function) {
call_user_func_array($form_state->prepareCallback($function), [
$entity->getEntityTypeId(),
$entity,
&$form,
&$form_state,
]);
}
}
return $entity;
}
/**
* Copies top-level form values to entity properties.
*
* This should not change existing entity properties that are not being edited
* by this form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @see \Drupal\Core\Form\ConfigFormBase::copyFormValuesToConfig()
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$values = $form_state->getValues();
if ($this->entity instanceof EntityWithPluginCollectionInterface) {
// Do not manually update values represented by plugin collections.
$values = array_diff_key($values, $this->entity
->getPluginCollections());
}
// @todo This relies on a method that only exists for config and content
// entities, in a different way. Consider moving this logic to a config
// entity specific implementation.
foreach ($values as $key => $value) {
$entity->set($key, $value);
}
}
/**
* {@inheritdoc}
*/
public function getEntity() {
return $this->entity;
}
/**
* {@inheritdoc}
*/
public function setEntity(EntityInterface $entity) {
$this->entity = $entity;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
if ($route_match->getRawParameter($entity_type_id) !== NULL) {
$entity = $route_match->getParameter($entity_type_id);
}
else {
$values = [];
// If the entity has bundles, fetch it from the route match.
$entity_type = $this->entityTypeManager
->getDefinition($entity_type_id);
if ($bundle_key = $entity_type->getKey('bundle')) {
if (($bundle_entity_type_id = $entity_type->getBundleEntityType()) && $route_match->getRawParameter($bundle_entity_type_id)) {
$values[$bundle_key] = $route_match->getParameter($bundle_entity_type_id)
->id();
}
elseif ($route_match->getRawParameter($bundle_key)) {
$values[$bundle_key] = $route_match->getParameter($bundle_key);
}
}
$entity = $this->entityTypeManager
->getStorage($entity_type_id)
->create($values);
}
return $entity;
}
/**
* Prepares the entity object before the form is built first.
*/
protected function prepareEntity() {
}
/**
* Invokes the specified prepare hook variant.
*
* @param string $hook
* The hook variant name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function prepareInvokeAll($hook, FormStateInterface $form_state) {
$this->moduleHandler
->invokeAllWith($hook, function (callable $hook, string $module) use ($form_state) {
// Ensure we pass an updated translation object and form display at
// each invocation, since they depend on form state which is alterable.
$hook($this->entity, $this->operation, $form_state);
});
}
/**
* {@inheritdoc}
*/
public function getOperation() {
return $this->operation;
}
/**
* {@inheritdoc}
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
return $this;
}
/**
* {@inheritdoc}
*/
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
return $this;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
EntityForm | Base class for entity forms. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.