Creating YAML Form Handlers in Drupal 8

Author picture
Gareth Goodwin profile
Posted by
Gareth Goodwin
Date:

If you’re building a Drupal site, there’s a strong chance you’ll need a form of some description somewhere along the line. For that, there’s several options - from the DIY Form API, the Contact module in core or contrib modules such as Webform. With Drupal 8, a few new kids arrived on the block - entity form (or eform), and YAML Form. The latter picked up pace, and is now a feature packed module that’s probably replaced Webform.

In most cases for forms, you probably just need them to send an email to someone - someone submitting the main ‘contact us’ form, signing up for a newsletter, requesting a demo & so in. All of the aforementioned modules do this, and can do it very well.

But what if you need to do something different? Post to an API? Redirect the user based on their choices? YAML Form allows you to create handler plugins that can be attached to forms when they are created. You can provide a configuration form for the user to adjust settings for the handler per form, so no more hook_form_alters, hard coded form IDs or complex if statements!

For my example, I’ll talk about how you could possibly have the handler post the submission data to an API/remote URL.

What is a YAML Form handler?

A YAML Form handler is a Drupal Plugin. Technically, this is simply a PHP class file in a specific directory in a module. It has a special comment above the class (an annotation), which enables YAML Forms to understand it & make use of it. 

Prerequisite: A module the handler lives in

If you already have a module you can add the handler to, great - move along! If not, you’ll need to create one. I’d suggest using Drupal Console (here’s an excellent guide from @blairwadman on doing so), or there’s some information on drupal.org if you want to start from scratch.

Reference points

To better understand how to create a handler, you might want to take a look at a couple of the classes that the YAML Form module provides:

  • yamlform/src/YamlFormHandlerBase.php - The base class that our handler will extend
  • yamlform/src/Plugin/YamlFormHandler/EmailYamlFormHandler.php - The default email handler provided by the module, which extends the YamlFormHandlerBase.

The example: Posting submission data to a URL

For my example, I’m going to create a handler that will post data to a URL that can be chosen when you add the handler to a form. I’ll name the handler “POST Submission Data” - this is needed in a few places, like the filename. I’ll also refer to the module you’ve created for the handler as ‘module_name’ - replace this with your module name.

Our handler will provide a textfield for the URL the data should be posted to so that the user can configure it per form.

Create the handler PHP class

Inside of your custom module, you’ll need the following folder structure:

src/Plugin/YamlFormHandler

This is so that Drupal knows where to look for the handler - find out more here as to why.

Within this folder, you’ll need a php file for the class. This should be named the same as the class you’ll create. Based on my example handler, this will be:

PostSubmissionData.php

Inside of this file, we’ll need to declare our namespace:

<?php

namespace Drupal\module_name\Plugin\YamlFormHandler; 

Change module_name in the namespace to the name of your module. More about namespacing can be found on drupal.org.

Next, we’ll need our ‘use’ statements - other PHP classes that we require:

use Drupal\Core\Form\FormStateInterface;
use Drupal\yamlform\YamlFormHandlerBase;
use Drupal\yamlform\YamlFormSubmissionInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

Then, declare our class (PostSubmissionData). As mentioned earlier, the plugin requires an annotation - a comment that provides metadata about the class. You can read more about annotations in Drupal 8 here (or this article from @blairwadman). This annotation goes directly above the class declaration:

/**
 * Posts a YAML Form submission to a remote URL.
 *
 * @YamlFormHandler(
 *   id = "post_submission_data",
 *   label = @Translation("Post Submission Data"),
 *   category = @Translation("API"),
 *   description = @Translation("Posts the form submission to a remote URL/API."),
 *   cardinality = \Drupal\yamlform\YamlFormHandlerInterface::CARDINALITY_UNLIMITED,
 *   results = \Drupal\yamlform\YamlFormHandlerInterface::RESULTS_PROCESSED,
 * )
 */
class PostSubmissionData extends YamlFormHandlerBase {

}

Let’s break this down:

@YamlFormHandler(
Declares the type of this plugin.
id = "post_submission_data"
This is the ID/machine name of the plugin, which must be unique.
label = @Translation("Post Submission Data")
The name for this handler, which is displayed in the list of handlers for the user to choose from.
category = @Translation("API")
The category of this handler, which is displayed when choosing handlers. This is similar to the ‘package’ value in module .info.yml files.
description = @Translation("Posts the form submission to a remote URL/API.")
The description for this handler, providing more information to the user.
cardinality = \Drupal\yamlform\YamlFormHandlerInterface::CARDINALITY_UNLIMITED
Cardinality is how many times this handler can be added to a form. CARDINALITY_UNLIMITED means the handler can be added many times, or CARDINALITY_SINGLE means only one can be added.
results = \Drupal\yamlform\YamlFormHandlerInterface::RESULTS_PROCESSED,
A flag to say the the handler processes the form submissions. RESULTS_IGNORED declares that the handler doesn’t process results.

Because we’re extending the YamlFormHandlerBase class, some of the methods required to create a handler have been handled for us (for example the __construct()). We’ll need to override a few of these though:

DefaultConfiguration

This method will provide the default configuration for this handler. For this example, we’ll set the default API endpoint to be ‘http://example.com/post-submission-data’.

/**
 * {@inheritdoc}
 */
public function defaultConfiguration() {
  return [
    'submission_url' => 'http://example.com/post-submission-data',
  ];
}

buildConfigurationForm

When a user adds a handler to their form, they’ll be given a form to configure the settings for the handler. This method returns the form that they see.

/**
 * {@inheritdoc}
 */
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
  $form['submission_url'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Submission URL'),
    '#description' => $this->t('The URL to post the submission data to.'),
    '#default_value' => $this->configuration['submission_url'],
    '#required' => TRUE,
  ];
  return $form;
}

One important part is the default value - this needs to get the value from the current configuration for the handler. If a handler is being added to a form, it will use the value from our DefaultConfiguration method. If a handler is being edited, it will use the current value.

submitConfigurationForm

When the user submits the handler configuration form, this function is called. You do have to manually assign the values from the configuration form to be handler configuration - this doesn’t happen automatically.

/**
 * {@inheritdoc}
 */
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
  parent::submitConfigurationForm($form, $form_state);
  // This is required to save the configuration.
  $this->configuration['submission_url'] = $form_state->getValue('submission_url');
}

Clear caches, and give it a try!

You’ll need to clear the caches on your site for it to pick up the new plugin we’ve just made. Once done, create/edit a YAML Form & go to the ‘Emails / Handlers’ tab. From here, click ‘Add handler’ and you’ll see your new handler as an option:

YAML Form handler select

Clicking ‘Add handler’ next to the ‘Post submission data’ will show the configuration form. YAML Form automatically adds the Title + Machine name field:

YAML Form handler configuration form

Once saved, you can see the handler is added to the form:

YAML Form handler listing

So that’s given us a handler that a user can add & configure on a form. Next up is actually reacting to a form submission.

Available methods for reacting to form submissions

If you look at the YamlFormHandlerInterface class, you can see the methods available & their descriptions. The following methods are currently available, which allow you to interact/react to form submissions at different points:

  • alterElements
  • alterForm
  • validateForm
  • submitForm
  • confirmForm
  • preCreate
  • postCreate
  • postLoad
  • preSave
  • postSave
  • preDelete
  • postDelete

For my example, I’m going to use postSave - once the submission has been saved in Drupal, then we’ll post it to our API. At the start of the call, we’ll check if the submission is completed as YAML Form supports saving as draft.

/**
 * {@inheritdoc}
 */
public function postSave(YamlFormSubmissionInterface $yamlform_submission, $update = TRUE) {
  // We do not want to post the data if a submission is not completed.
  $is_completed = ($yamlform_submission->getState() == YamlFormSubmissionInterface::STATE_COMPLETED);
  if (!$is_completed) {
    return;
  }
  // Get an array of the values from the submission.
  $values = $yamlform_submission->getData();
  // Get the URL to post the data to.
  $post_url = $this->configuration['submission_url'];
  // Use Guzzle to post the data, as JSON.
  $params = [
    'json' => $values,
  ];
  $guzzle = new Client();
  try {
    $result = $guzzle->post($post_url, $params);
    // React to the result.
    // ...
  } catch (RequestException $e) {
    // Error handling.
  }
  // Set a message to confirm that postSave() has been called.
  drupal_set_message($this->t('PostSubmissionData::postSave() has been called.'));
}

Conclusion

And that’s it - by submitting a YAML Form, it will post an array of the data (as JSON) from the submission to the URL chosen when adding the handler. This is a simple example, but should show you how you can create the form presented to the user when they add the handler, save options entered, then react to a submission & make use of the handler configuration.

YAML Form is becoming Webform

Recently, the YAML Form & Webform modules have agreed that YAML Form should become the 8.x-5.x version of Webform, so the module is currently in the process of changing name. It’s possible that the code samples here may change - I’ll update it if so!