Intro to Custom Form Handling in Statamic
Statamic's built in forms are one of the great features that make it such a compelling system to work with. Without touching the code you can create your forms and collect entries in the back end or have them sent to you via e-mail.
But what if you want some other, more advanced functionality? Maybe you want to sign the user up to your third party mailing list tool. Or you have to pull in some external data depending on their input in order to calculate and return some sort of complex result.
In this case you need to implement your own form handling. Let's see how we can achieve just that by creating a simple newsletter signup form that sends the input data to an external API, something I just had to do for a client last month.
The Signup Form
We will keep this form minimal, both to keep this example simple and to save the user from having to input too much of his personal information. So let's just stick with two fields: one required input for the e-mail address and one optional input for the user's name.
You can easily create your form using the control panel - here is how the finished blueprint will look like in the yaml file.
Once you have created the form and its blueprint, you can add it to your website and Statamic will automatically start collecting the submission data in the filesystem. But since that is not where we need the data, we will now introduce our custom form handling instead.
Listening to Statamic's Events
As a first step we need to find a way to execute our custom code every time our form is submitted by a user.
To make this process as painless as possible, Statamic follows Laravel's conventions and dispatches events whenever certain actions are taken. Have a look at the list of available events in the documentation to get an idea of all the possibilities.
Going through the list we can find two events that are potentially of interest for our use case:
FormSubmitted: this is dispatched as soon as the user submits the form, after Statamic validates the input but before it stores or emails the submitted data
SubmissionCreated: once Statamic has accepted the user's input and created a submission record in the back end, this event is triggered
Our example signup form doesn't need Statamic to store or otherwise handle the user input, since all that is handled externally, so the FormSubmitted event looks like a better fit for this scenario.
Creating an Event Listener
Now that we know the event we are looking for, we have to create a custom listener that will be triggered whenever the event occurs. Simply switch to your terminal and run
php please make:listener NewsletterFormListener --event=FormSubmitted.
This will do two things for you: first it will create a new NewsletterFormListener.php in the event listener section of your Laravel installation, which will handle our functionality. Here is what that file looks like without any modifications:
Registering the Listener
Secondly the above command will also make a change to your EventServiceProvider.php to register your new listener.
This little snippets lets your application know which class to call - your new listener - when the event occurs. That's it for the event handling - we can go right into creating the functionality we need.
Adding the Custom Form Handling
Looking at your newly created NewsletterFormListener.php you will notice it only comes with two predefined functions, a constructor and a handle method. As the name implies, we use
handle() to handle our form submission.
Accessing the Submission Data
Looking at the file you will see that the function is passed an event object
$event, which contains all of the information about the event including the data entered into the form.
Let's have a look at the data that's available to us. Since the event handler is triggered in the background, we can't just print or return the data to the user, but we can just write it to the log instead.
Here's how that output looks after adding some line breaks to make it easier to read. Note that any property with a
* in front (e.g.
*data) is protected and not directly accessible, so we will need to use getter-methods for those.
That's a lot of input at once, but for our case all we need to know is that the event object has a submission property which contains the information about both the form and the data submitted by the user.
Sending the Signup to an API
We have located our data, so now we can pick out the information we need to create the API call and sign the user up for our newsletter.
As of now our function is executed for every submission of any form on the website, which would probably cause errors once we also create other forms. But since our submission data includes the form object, we can simply check for the correct handle and filter out unwanted submissions.
Note that we are using the getter-method
handle(), since the
handle property is protected and cannot be directly accessed from outside the class. This value is simply a unique name of our form and has no connection to handling the form submission.
As for the submission data we can actually use a bit of Laravel's magic. If you call
$event->submission->name, the app knows to use the
get() method in the background to retrieve the value.
Since the exact API call can take many forms, we will just do a overly simplified one here that sends our two data points to a fictional endpoint. In the real world you would probably have to take care of authorization first and send some additional information about your mailing list along.
When anything goes wrong with the API call we can throw a ValidationException and display an error message on the frontend. Otherwise we can return a boolean to let Statamic know that our custom form handling worked as planned.
true lets the app know that you would like to also store this submission in the backend or send it out via e-mail, setting it to
false will prevent that. Since we have no use for the data after sending it to the external API, we can simply return
false and be done with it.
This is a very simple way of handling the user input, but since you can access all information this way, there is no limit as to what functionality you can implement.
This approach has its limits however, since you need to hard code the form handle and API call into you listener. For more flexibility you might want to turn this into a package instead and add a config file where the site owner can change those values as needed.