Commit 620d4979 authored by alexpott's avatar alexpott

Issue #1913618 by tim.plunkett, amateescu: Convert...

Issue #1913618 by tim.plunkett, amateescu: Convert EntityFormControllerInterface to extend FormInterface.
parent 422bf9b0
......@@ -270,6 +270,11 @@ services:
tags:
- { name: route_enhancer, priority: 20 }
- { name: legacy_route_enhancer, priority: 20 }
route_enhancer.entity_form:
class: Drupal\Core\Entity\Enhancer\EntityFormEnhancer
arguments: ['@content_negotiation']
tags:
- { name: route_enhancer, priority: 15 }
route_enhancer.form:
class: Drupal\Core\Routing\Enhancer\FormEnhancer
arguments: ['@content_negotiation']
......
......@@ -430,30 +430,6 @@ function entity_form_controller($entity_type, $operation = 'default') {
->getFormController($entity_type, $operation);
}
/**
* Returns the form id for the given entity and operation.
*
* @param EntityInterface $entity
* The entity to be created or edited.
* @param $operation
* (optional) The operation for the form to be processed.
*
* @return
* A string representing the entity form id.
*/
function entity_form_id(EntityInterface $entity, $operation = 'default') {
$entity_type = $entity->entityType();
$bundle = $entity->bundle();
$form_id = $entity_type;
if ($bundle != $entity_type) {
$form_id = $bundle . '_' . $form_id;
}
if ($operation != 'default') {
$form_id = $form_id . '_' . $operation;
}
return $form_id . '_form';
}
/**
* Returns the default form state for the given entity and operation.
*
......@@ -467,10 +443,11 @@ function entity_form_id(EntityInterface $entity, $operation = 'default') {
*/
function entity_form_state_defaults(EntityInterface $entity, $operation = 'default') {
$form_state = array();
$controller = drupal_container()->get('plugin.manager.entity')->getFormController($entity->entityType(), $operation);
$form_state['build_info']['callback'] = array($controller, 'build');
$form_state['build_info']['base_form_id'] = $entity->entityType() . '_form';
$form_state['build_info']['args'] = array($entity);
$controller = Drupal::service('plugin.manager.entity')->getFormController($entity->entityType(), $operation);
$controller->setEntity($entity);
$form_state['build_info']['callback_object'] = $controller;
$form_state['build_info']['base_form_id'] = $controller->getBaseFormID();
$form_state['build_info']['args'] = array();
return $form_state;
}
......@@ -489,7 +466,7 @@ function entity_form_state_defaults(EntityInterface $entity, $operation = 'defau
*/
function entity_form_submit(EntityInterface $entity, $operation = 'default', &$form_state = array()) {
$form_state += entity_form_state_defaults($entity, $operation);
$form_id = entity_form_id($entity, $operation);
$form_id = $form_state['build_info']['callback_object']->getFormID();
drupal_form_submit($form_id, $form_state);
}
......@@ -513,7 +490,7 @@ function entity_form_submit(EntityInterface $entity, $operation = 'default', &$f
*/
function entity_get_form(EntityInterface $entity, $operation = 'default', array $form_state = array()) {
$form_state += entity_form_state_defaults($entity, $operation);
$form_id = entity_form_id($entity, $operation);
$form_id = $form_state['build_info']['callback_object']->getFormID();
return drupal_build_form($form_id, $form_state);
}
......
......@@ -7,6 +7,7 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Datetime\DrupalDateTime;
......@@ -119,6 +120,9 @@ function _drupal_form_id($form_arg, &$form_state) {
// the callback object and determine the form ID.
if (is_object($form_arg) && $form_arg instanceof FormInterface) {
$form_state['build_info']['callback_object'] = $form_arg;
if ($form_arg instanceof BaseFormIdInterface) {
$form_state['build_info']['base_form_id'] = $form_arg->getBaseFormID();
}
return $form_arg->getFormID();
}
......@@ -299,7 +303,8 @@ function drupal_get_form($form_arg) {
* recommended way to ensure that the chosen key doesn't conflict with ones
* used by the Form API or other modules is to use the module name as the
* key name or a prefix for the key name. For example, the entity form
* controller classes use $form_state['entity'] in entity forms to store
* controller classes use $this->entity in entity forms, or
* $form_state['controller']->getEntity() outside the controller, to store
* information about the entity being edited, and this information stays
* available across successive clicks of the "Preview" button (if available)
* as well as when the "Save" button is finally clicked.
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Enhancer\EntityFormEnhancer.
*/
namespace Drupal\Core\Entity\Enhancer;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Drupal\Core\ContentNegotiation;
/**
* Enhances an entity form route with the appropriate controller.
*/
class EntityFormEnhancer implements RouteEnhancerInterface {
/**
* Content negotiation library.
*
* @var \Drupal\CoreContentNegotiation
*/
protected $negotiation;
/**
* Constructs a new \Drupal\Core\Entity\Enhancer\EntityFormEnhancer.
*
* @param \Drupal\Core\ContentNegotiation $negotiation
* The content negotiation library.
*/
public function __construct(ContentNegotiation $negotiation) {
$this->negotiation = $negotiation;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
if (empty($defaults['_controller']) && !empty($defaults['_entity_form']) && $this->negotiation->getContentType($request) === 'html') {
$defaults['_controller'] = '\Drupal\Core\Entity\HtmlEntityFormController::content';
}
return $defaults;
}
}
......@@ -22,31 +22,71 @@ class EntityFormController implements EntityFormControllerInterface {
*/
protected $operation;
/**
* The entity being used by this form.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* Constructs an EntityFormController object.
*
* @param string|null $operation
* (optional) The name of the current operation, defaults to NULL.
*/
public function __construct($operation = NULL) {
$this->setOperation($operation);
}
/**
* Sets the operation for this form.
*
* @param string $operation
* The name of the current operation.
*/
public function __construct($operation) {
$this->operation = $operation;
public function setOperation($operation) {
// If NULL is passed, do not overwrite the operation.
if ($operation) {
$this->operation = $operation;
}
}
/**
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::build().
* {@inheritdoc}
*/
public function build(array $form, array &$form_state, EntityInterface $entity) {
public function getBaseFormID() {
return $this->entity->entityType() . '_form';
}
// During the initial form build, add the entity to the form state for use
// during form building and processing. During a rebuild, use what is in the
// form state.
if (!$this->getEntity($form_state)) {
$this->init($form_state, $entity);
/**
* {@inheritdoc}
*/
public function getFormID() {
$entity_type = $this->entity->entityType();
$bundle = $this->entity->bundle();
$form_id = $entity_type;
if ($bundle != $entity_type) {
$form_id = $bundle . '_' . $form_id;
}
if ($this->operation != 'default') {
$form_id = $form_id . '_' . $this->operation;
}
return $form_id . '_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state) {
// During the initial form build, add this controller to the form state and
// allow for initial preparation before form building and processing.
if (!isset($form_state['controller'])) {
$this->init($form_state);
}
// Retrieve the form array using the possibly updated entity in form state.
$entity = $this->getEntity($form_state);
$form = $this->form($form, $form_state, $entity);
$form = $this->form($form, $form_state);
// Retrieve and add the form actions array.
$actions = $this->actionsElement($form, $form_state);
......@@ -57,15 +97,26 @@ public function build(array $form, array &$form_state, EntityInterface $entity)
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
}
/**
* Initialize the form state and the entity before the first form build.
*/
protected function init(array &$form_state, EntityInterface $entity) {
protected function init(array &$form_state) {
// Add the controller to the form state so it can be easily accessed by
// module-provided form handlers there.
$form_state['controller'] = $this;
$this->setEntity($entity, $form_state);
$this->prepareEntity($entity);
$this->prepareEntity();
}
/**
......@@ -73,7 +124,8 @@ protected function init(array &$form_state, EntityInterface $entity) {
*
* @see Drupal\Core\Entity\EntityFormController::build()
*/
public function form(array $form, array &$form_state, EntityInterface $entity) {
public function form(array $form, array &$form_state) {
$entity = $this->entity;
// @todo Exploit the Field API to generate the default widgets for the
// entity properties.
$info = $entity->entityInfo();
......@@ -99,7 +151,7 @@ protected function actionsElement(array $form, array &$form_state) {
$element = $this->actions($form, $form_state);
// We cannot delete an entity that has not been created yet.
if ($this->getEntity($form_state)->isNew()) {
if ($this->entity->isNew()) {
unset($element['delete']);
}
elseif (isset($element['delete'])) {
......@@ -198,9 +250,8 @@ public function submit(array $form, array &$form_state) {
$this->updateFormLangcode($form_state);
$this->submitEntityLanguage($form, $form_state);
$entity = $this->buildEntity($form, $form_state);
$this->setEntity($entity, $form_state);
return $entity;
$this->entity = $this->buildEntity($form, $form_state);
return $this->entity;
}
/**
......@@ -231,7 +282,7 @@ public function delete(array $form, array &$form_state) {
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::getFormLangcode().
*/
public function getFormLangcode(array $form_state) {
$entity = $this->getEntity($form_state);
$entity = $this->entity;
$translations = $entity->getTranslationLanguages();
if (!empty($form_state['langcode'])) {
......@@ -257,7 +308,7 @@ public function getFormLangcode(array $form_state) {
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::isDefaultFormLangcode().
*/
public function isDefaultFormLangcode(array $form_state) {
return $this->getFormLangcode($form_state) == $this->getEntity($form_state)->language()->langcode;
return $this->getFormLangcode($form_state) == $this->entity->language()->langcode;
}
/**
......@@ -282,7 +333,7 @@ protected function updateFormLangcode(array $form_state) {
* A reference to a keyed array containing the current state of the form.
*/
protected function submitEntityLanguage(array $form, array &$form_state) {
$entity = $this->getEntity($form_state);
$entity = $this->entity;
$entity_type = $entity->entityType();
if (field_has_translation_handler($entity_type)) {
......@@ -313,7 +364,7 @@ protected function submitEntityLanguage(array $form, array &$form_state) {
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::buildEntity().
*/
public function buildEntity(array $form, array &$form_state) {
$entity = clone $this->getEntity($form_state);
$entity = clone $this->entity;
// @todo Move entity_form_submit_build_entity() here.
// @todo Exploit the Field API to process the submitted entity field.
entity_form_submit_build_entity($entity->entityType(), $entity, $form, $form_state);
......@@ -323,21 +374,22 @@ public function buildEntity(array $form, array &$form_state) {
/**
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::getEntity().
*/
public function getEntity(array $form_state) {
return isset($form_state['entity']) ? $form_state['entity'] : NULL;
public function getEntity() {
return $this->entity;
}
/**
* Implements \Drupal\Core\Entity\EntityFormControllerInterface::setEntity().
*/
public function setEntity(EntityInterface $entity, array &$form_state) {
$form_state['entity'] = $entity;
public function setEntity(EntityInterface $entity) {
$this->entity = $entity;
return $this;
}
/**
* Prepares the entity object before the form is built first.
*/
protected function prepareEntity(EntityInterface $entity) {
protected function prepareEntity() {
// @todo Perform common prepare operations and add a hook.
}
......
......@@ -7,30 +7,12 @@
namespace Drupal\Core\Entity;
use Drupal\Core\Form\BaseFormIdInterface;
/**
* Defines a common interface for entity form controller classes.
*/
interface EntityFormControllerInterface {
/**
* Builds an entity form.
*
* This is the entity form builder which is invoked via drupal_build_form()
* to retrieve the form.
*
* @param array $form
* A nested array form elements comprising the form.
* @param array $form_state
* An associative array containing the current state of the form.
* @param string $entity_type
* The type of the entity being edited.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being edited.
*
* @return array
* The array containing the complete form.
*/
public function build(array $form, array &$form_state, EntityInterface $entity);
interface EntityFormControllerInterface extends BaseFormIdInterface {
/**
* Returns the code identifying the active form language.
......@@ -73,7 +55,7 @@ public function getOperation();
* @return \Drupal\Core\Entity\EntityInterface
* The current form entity.
*/
public function getEntity(array $form_state);
public function getEntity();
/**
* Sets the form entity.
......@@ -86,10 +68,8 @@ public function getEntity(array $form_state);
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
* @param array $form_state
* An associative array containing the current state of the form.
*/
public function setEntity(EntityInterface $entity, array &$form_state);
public function setEntity(EntityInterface $entity);
/**
* Builds an updated entity object based upon the submitted form values.
......
......@@ -22,7 +22,8 @@ class EntityFormControllerNG extends EntityFormController {
/**
* Overrides EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $entity) {
public function form(array $form, array &$form_state) {
$entity = $this->entity;
// @todo Exploit the Field API to generate the default widgets for the
// entity fields.
$info = $entity->entityInfo();
......@@ -65,7 +66,7 @@ protected function submitEntityLanguage(array $form, array &$form_state) {
* Overrides EntityFormController::buildEntity().
*/
public function buildEntity(array $form, array &$form_state) {
$entity = clone $this->getEntity($form_state);
$entity = clone $this->entity;
$entity_type = $entity->entityType();
$info = entity_get_info($entity_type);
// @todo Exploit the Field API to process the submitted entity fields.
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\HtmlEntityFormController.
*/
namespace Drupal\Core\Entity;
use Drupal\Core\HtmlFormController;
use Symfony\Component\HttpFoundation\Request;
/**
* Wrapping controller for entity forms that serve as the main page body.
*/
class HtmlEntityFormController extends HtmlFormController {
/**
* Overrides \Drupal\Core\HtmlFormController::content().
*
* Due to reflection, the argument must be named $_entity_form. The parent
* method has $request and $_form, but the parameter must match the route.
*/
public function content(Request $request, $_entity_form) {
return parent::content($request, $_entity_form);
}
/**
* Overrides \Drupal\Core\HtmlFormController::getFormObject().
*
* Instead of a class name or service ID, $form_arg will be a string
* representing the entity and operation being performed.
* Consider the following route:
* @code
* pattern: '/foo/{node}/bar'
* defaults:
* _entity_form: 'node.edit'
* @endcode
* This means that the edit form controller for the node entity will used.
* If the entity type has a default form controller, only the name of the
* entity {param} needs to be passed:
* @code
* pattern: '/foo/{node}/baz'
* defaults:
* _entity_form: 'node'
* @endcode
*/
protected function getFormObject(Request $request, $form_arg) {
$manager = $this->container->get('plugin.manager.entity');
// If no operation is provided, use 'default'.
$form_arg .= '.default';
list ($entity_type, $operation) = explode('.', $form_arg);
if ($request->attributes->has($entity_type)) {
$entity = $request->attributes->get($entity_type);
}
else {
$entity = $manager->getStorageController($entity_type)->create(array());
}
return $manager->getFormController($entity_type, $operation)->setEntity($entity);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Form\BaseFormIdInterface.
*/
namespace Drupal\Core\Form;
/**
* Provides an interface for a Form that has a base form ID.
*
* This will become the $form_state['build_info']['base_form_id'] used to
* generate the name of hook_form_BASE_FORM_ID_alter().
*/
interface BaseFormIdInterface extends FormInterface {
/**
* Returns a string identifying the base form.
*
* @return string|false
* The string identifying the base form or FALSE if this is not a base form.
*/
public function getBaseFormID();
}
......@@ -46,24 +46,12 @@ public function setContainer(ContainerInterface $container = NULL) {
* A response object.
*/
public function content(Request $request, $_form) {
// If this is a class, instantiate it.
if (class_exists($_form)) {
if (in_array('Drupal\Core\ControllerInterface', class_implements($_form))) {
$form_arg = $_form::create($this->container);
}
else {
$form_arg = new $_form();
}
}
// Otherwise, it is a service.
else {
$form_arg = $this->container->get($_form);
}
$form_object = $this->getFormObject($request, $_form);
// Using reflection, find all of the parameters needed by the form in the
// request attributes, skipping $form and $form_state.
$attributes = $request->attributes->all();
$reflection = new \ReflectionMethod($form_arg, 'buildForm');
$reflection = new \ReflectionMethod($form_object, 'buildForm');
$params = $reflection->getParameters();
$args = array();
foreach (array_splice($params, 2) as $param) {
......@@ -73,9 +61,34 @@ public function content(Request $request, $_form) {
}
$form_state['build_info']['args'] = $args;
$form_id = _drupal_form_id($form_arg, $form_state);
$form_id = _drupal_form_id($form_object, $form_state);
$form = drupal_build_form($form_id, $form_state);
return new Response(drupal_render_page($form));
}
/**
* Returns the object used to build the form.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request using this form.
* @param string $form_arg
* Either a class name or a service ID.
*
* @return \Drupal\Core\Form\FormInterface
* The form object to use.
*/
protected function getFormObject(Request $request, $form_arg) {
// If this is a class, instantiate it.
if (class_exists($form_arg)) {
if (in_array('Drupal\Core\ControllerInterface', class_implements($form_arg))) {
return $form_arg::create($this->container);
}
return new $form_arg();
}
// Otherwise, it is a service.
return $this->container->get($form_arg);
}
}
......@@ -7,7 +7,6 @@
namespace Drupal\aggregator;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormControllerNG;
/**
......@@ -18,7 +17,8 @@ class FeedFormController extends EntityFormControllerNG {
/**
* Overrides Drupal\Core\Entity\EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $feed) {
public function form(array $form, array &$form_state) {
$feed = $this->entity;
$period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
$period[AGGREGATOR_CLEAR_NEVER] = t('Never');
......@@ -111,7 +111,7 @@ public function validate(array $form, array &$form_state) {
* Overrides Drupal\Core\Entity\EntityFormController::save().
*/
public function save(array $form, array &$form_state) {
$feed = $this->getEntity($form_state);
$feed = $this->entity;
$insert = (bool) $feed->id();
if (!empty($form_state['values']['category'])) {
// Store category values for post save operations.
......@@ -138,8 +138,7 @@ public function save(array $form, array &$form_state) {
* Overrides Drupal\Core\Entity\EntityFormController::delete().
*/
public function delete(array $form, array &$form_state) {
$feed = $this->getEntity($form_state);
$form_state['redirect'] = 'admin/config/services/aggregator/delete/feed/' . $feed->id();
$form_state['redirect'] = 'admin/config/services/aggregator/delete/feed/' . $this->entity->id();
}
}
......@@ -8,7 +8,6 @@
namespace Drupal\custom_block;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormControllerNG;
/**
......@@ -24,7 +23,8 @@ class CustomBlockFormController extends EntityFormControllerNG {
* Fills in a few default values, and then invokes hook_custom_block_prepare()
* on all modules.
*/
protected function prepareEntity(EntityInterface $block) {
protected function prepareEntity() {
$block = $this->entity;
// Set up default values, if required.
$block_type = entity_load('custom_block_type', $block->type->value);
// If this is a new custom block, fill in the default values.
......@@ -40,7 +40,8 @@ protected function prepareEntity(EntityInterface $block) {
/**
* Overrides \Drupal\Core\Entity\EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $block) {
public function form(array $form, array &$form_state) {
$block = $this->entity;
// Override the default CSS class name, since the user-defined custom block
// type name in 'TYPE-block-form' potentially clashes with third-party class
// names.
......@@ -158,7 +159,7 @@ public function submit(array $form, array &$form_state) {
* Overrides \Drupal\Core\Entity\EntityFormController::save().
*/
public function save(array $form, array &$form_state) {
$block = $this->getEntity($form_state);
$block = $this->entity;
$insert = empty($block->id->value);
$block->save();
$watchdog_args = array('@type' => $block->bundle(), '%info' => $block->label());
......
......@@ -7,7 +7,6 @@
namespace Drupal\custom_block;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormController;
/**
......@@ -18,9 +17,10 @@ class CustomBlockTypeFormController extends EntityFormController {
/**
* Overrides \Drupal\Core\Entity\EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $block_type) {
$form = parent::form($form, $form_state, $block_type);
public function form(array $form, array &$form_state) {
$form = parent::form($form, $form_state);
$block_type = $this->entity;
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Label'),
......@@ -87,7 +87,7 @@ public function form(array $form, array &$form_state, EntityInterface $block_typ
* Overrides \Drupal\Core\Entity\EntityFormController::save().
*/
public function save(array $form, array &$form_state) {
$block_type = $this->getEntity($form_state);
$block_type = $this->entity;
$status = $block_type->save();
$uri = $block_type->uri();
......@@ -107,7 +107,7 @@ public function save(array $form, array &$form_state) {
* Overrides \Drupal\Core\Entity\EntityFormController::delete().
*/
public function delete(array $form, array &$form_state) {
$block_type = $this->getEntity($form_state);
$block_type = $this->entity;
$form_state['redirect'] = 'admin/structure/custom-blocks/manage/' . $block_type->id() . '/delete';