2

i'm working with Drupal 8 and i want to combine the user form and a node creation form into my custom form.

So i've created the form skeleton :

<?php

namespace Drupal\fritr_register_friterie\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Class RegisterFriterieForm.
 *
 * @package Drupal\fritr_register_friterie\Form
 */
class RegisterFriterieForm extends FormBase {

    /**
     * {@inheritdoc}
     */
    public function getFormId() {
        return 'register_friterie_form';
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(array $form, FormStateInterface $form_state) {
        $form = [];

//        $form = \Drupal::formBuilder()->getForm();

        return $form;
    }

    /**
     * {@inheritdoc}
     */
    public function submitForm(array &$form, FormStateInterface $form_state) {

    }

}

But i don't know how to import the user_profile form and the node creation form of a custom content that i've created in the back office.

I know drupal_get_form('form_id') is replaced in drupal 8 by \Drupal::formBuilder()->getForm(class, parameter);

Maybe it's something like this : \Drupal::formBuilder()->getForm(Drupal\user\Form\UserForm::class);

But i have not find the documentation which show all the locations of form or examples.... And i have not find how to load a node creation form :/

Can you help me please ? Thanks :)

EDIT : i've found how to include my node creation form (but i don't know how to modify my fields... D8 So complex u_u) by :

$type = node_type_load('friterie');
        $node = \Drupal::entityManager()->getStorage('node')->create(array(
          'type' => $type->id(),
        ));

        // OPTIONAL - Set default values for node fields
        // $node->set('field_age', "32") ;
        $node_create_form = \Drupal::service('entity.form_builder')->getForm($node);  
        $form['friterie'] = array(
            '#type' => 'markup',
            '#markup' => render($node_create_form),
        );
        return $form;

need to find how to load user_profile form ^^

  • It can be done, though it is complicated. – Eyal Jun 7 '16 at 15:48
  • Loading and building the forms is easy. The problems lie in combining the submission and validation methods and the submission buttons. – Eyal Jun 7 '16 at 15:50
  • try \Drupal::formBuilder()->getForm('Drupal\custom\Form\NewsletterForm') where custom module name newletterform is form controller – harsh_behl_0007 Jun 7 '16 at 17:07
  • @harsh getting the inner form is the easy part, see the answer below to understand the complexity of a full solution. – Eyal Jun 7 '16 at 19:38
  • I want add the user_profile form. I know how to include custom modules form but not how to include user_profile form and a node creation form of a specific content type. – Kpone Jun 8 '16 at 7:09
4

I've created a working example of a combined form object. Please read the comments below if you want to understand how it works and the workarounds that were applied.

The gist can be found here

Note: There may still be unforeseen issues with the solution below.

<?php

namespace Drupal\sandbox\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;
use Drupal\user\Entity\User;

class ComboForm extends FormBase {

  /**
   * This constant is used as a key inside the main form state object to gather
   * all the inner form state objects.
   * @const
   * @see getInnerFormState()
   */
  const INNER_FORM_STATE_KEY = 'inner_form_state';

  const MAIN_SUBMIT_BUTTON = 'submit';

  /**
   * @var \Drupal\Core\Form\FormInterface[]
   */
  protected $innerForms = [];

  /**
   * The ComboForm constructor needs to initialize the inner form objects
   * that this form will later use.
   *
   * Because both the user and the node forms are entity forms then an entity
   * object needs to be assigned to them.
   *
   * Because the node entity has bundle, the bundle (type) must be defined
   * before the form is generated.
   *
   * @TODO If the node type is customizable then a custom controller is needed.
   *
   * @TODO should this form allow edits? If so then the form objects should
   *       be assigned with the edited entities.
   */
  public function __construct() {
    $this->innerForms['user'] = \Drupal::entityTypeManager()
      ->getFormObject('user', 'default')
      ->setEntity(User::create());

    $this->innerForms['node'] = \Drupal::entityTypeManager()
      ->getFormObject('node', 'default')
      ->setEntity(Node::create([
        'type' => 'article'
      ]));
  }

  /**
   * {@inheritDoc}
   */
  public function getFormId() {
    return implode('__', [
      'combo_form',
      $this->innerForms['user']->getFormId(),
      $this->innerForms['node']->getFormId()
    ]);
  }

  /**
   * The build form needs to take care of the following:
   *   - Creating a custom form state object for each inner form (and keep it
   *     inside the main form state.
   *   - Generating a render array for each inner form.
   *   - Handle compatibility issues such as #process array and action elements.
   *
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form['#process'] = \Drupal::service('element_info')->getInfoProperty('form', '#process', []);
    $form['#process'][] = '::processForm';

    foreach ($this->innerForms as $key => $inner_form_object) {
      $inner_form_state = static::createInnerFormState($form_state, $inner_form_object, $key);


      // By placing the actual inner form inside a container element (such as
      // details) we gain the freedom to alter the wrapper of the inner form
      // with little damage to the render element attributes of the inner form.
      $inner_form = ['#parents' => [$key]];
      $inner_form = $inner_form_object->buildForm($inner_form, $inner_form_state);
      $form[$key] = [
        '#type' => 'details',
        '#title' => $this->t('Inner form: %key', ['%key' => $key]),
        'form' => $inner_form
      ];

      $form[$key]['form']['#type'] = 'container';
      $form[$key]['form']['#theme_wrappers'] = \Drupal::service('element_info')->getInfoProperty('container', '#theme_wrappers', []);
      unset($form[$key]['form']['form_token']);

      // The process array is called from the FormBuilder::doBuildForm method
      // with the form_state object assigned to the this (ComboForm) object.
      // This results in a compatibility issues because these methods should
      // be called on the inner forms (with their assigned FormStates).
      // To resolve this we move the process array in the inner_form_state
      // object.
      if (!empty($form[$key]['form']['#process'])) {
        $inner_form_state->set('#process', $form[$key]['form']['#process']);
        unset($form[$key]['form']['#process']);
      }
      else {
        $inner_form_state->set('#process', []);
      }

      // The actions array causes a UX problem because there should only be a
      // single save button and not multiple.
      // The current solution is to move the #submit callbacks of the submit
      // element to the inner form element root.
      if (!empty($form[$key]['form']['actions'])) {
        if (isset($form[$key]['form']['actions'][static::MAIN_SUBMIT_BUTTON])) {
          $form[$key]['form']['#submit'] = $form[$key]['form']['actions'][static::MAIN_SUBMIT_BUTTON]['#submit'];
        }

        unset($form[$key]['form']['actions']);
      }
    }

    // Default action elements.
    $form['actions'] = [
      '#type' => 'actions',
      static::MAIN_SUBMIT_BUTTON => [
        '#type' => 'submit',
        '#value' => $this->t('Save'),
        '#validate' => ['::validateForm'],
        '#submit' => ['::submitForm']
      ]
    ];

    return $form;
  }

  /**
   * This method will be called from FormBuilder::doBuildForm during the process
   * stage.
   * In here we call the #process callbacks that were previously removed.
   *
   * @param array $element
   *   The form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @param array $complete_form
   * @return array
   *   The altered form element.
   *
   * @see \Drupal\Core\Form\FormBuilder::doBuildForm()
   */
  public function processForm(array &$element, FormStateInterface &$form_state, array &$complete_form) {
    foreach ($this->innerForms as $key => $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $key);
      foreach ($inner_form_state->get('#process') as $callback) {
        // The callback format was copied from FormBuilder::doBuildForm().
        $element[$key]['form'] = call_user_func_array($inner_form_state->prepareCallback($callback), array(&$element[$key]['form'], &$inner_form_state, &$complete_form));
      }
    }

    return $element;
  }

  /**
   * {@inheritDoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    /** @var \Drupal\Core\Form\FormValidatorInterface $form_validator */
    $form_validator = \Drupal::service('form_validator');
    foreach ($this->innerForms as $form_key => $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $form_key);

      // Pass through both the form elements validation and the form object
      // validation.
      $inner_form->validateForm($form[$form_key]['form'], $inner_form_state);
      $form_validator->validateForm($inner_form->getFormId(), $form[$form_key]['form'], $inner_form_state);


      foreach ($inner_form_state->getErrors() as $error_element_path => $error) {
        $form_state->setErrorByName($form_key . '][' . $error_element_path, $error);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    /** @var \Drupal\Core\Form\FormSubmitterInterface $form_submitter */
    $form_submitter = \Drupal::service('form_submitter');

    foreach ($this->innerForms as $key => $inner_form) {
      $inner_form_state = static::getInnerFormState($form_state, $key);

      // The form state needs to be set as submitted before executing the
      // doSubmitForm method.
      $inner_form_state->setSubmitted();
      $form_submitter->doSubmitForm($form[$key]['form'], $inner_form_state);
    }
  }

  /**
   * Before returning the innerFormState object, we need to set the
   * complete_form, values and user_input properties from the main form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The main form state.
   * @param string $key
   *   The key used to store the inner form state.
   * @return \Drupal\Core\Form\FormStateInterface
   *   The inner form state.
   */
  protected static function getInnerFormState(FormStateInterface $form_state, $key) {
    /** @var \Drupal\Core\Form\FormStateInterface $inner_form_state */
    $inner_form_state = $form_state->get([static::INNER_FORM_STATE_KEY, $key]);
    $inner_form_state->setCompleteForm($form_state->getCompleteForm());
    $inner_form_state->setValues($form_state->getValues() ? : []);
    $inner_form_state->setUserInput($form_state->getUserInput() ? : []);
    return $inner_form_state;
  }

  /**
   * After the initialization of the inner form state, we need to assign it with
   * the inner form object and set it inside the main form state.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The main form state.
   * @param \Drupal\Core\Form\FormInterface $form_object
   *   The inner form object
   * @param string $key
   *   The key used to store the inner form state.
   * @return \Drupal\Core\Form\FormStateInterface
   *   The inner form state.
   */
  protected static function createInnerFormState(FormStateInterface $form_state, FormInterface $form_object, $key) {
    $inner_form_state = new FormState();
    $inner_form_state->setFormObject($form_object);
    $form_state->set([static::INNER_FORM_STATE_KEY, $key], $inner_form_state);
    return $inner_form_state;
  }

}
  • it's exactly what i need ! Thanks !!! I just need to modify it to have another display format ^^ Thanks ! – Kpone Jun 8 '16 at 7:17
  • It's hard to understand how works your code, did you use a tutorial to did that ? Because i try to understand it to remove the accordion system and show immediately the forms ^^ – Kpone Jun 8 '16 at 7:33
  • I created a module for drupal 8 that provides an entity reference widget that injects the references entity form into the main entity form (entity_reference_form). So I was aware of the potential problems. – Eyal Jun 8 '16 at 7:54
  • How i can directly remove your accordion system and show directly the form ? And where can i edit the form to remove some fields for an example ? – Kpone Jun 8 '16 at 11:31
  • Your original question has been answered. If you have questions about this code then we can continue on a chat thread chat.stackexchange.com/rooms/40881/… – Eyal Jun 8 '16 at 11:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.