Skip to main content

Adding custom form field to Sulu Forms

This blog post might be outdated!
This blog post was published more than one year ago and might be outdated!
· 3 min read
Stephan Hochdörfer
Head of IT Business Operations

In the last part of this blog post series we covered how to add custom logic to a Sulu form to send the form data to a 3rd party system. In this blog post, we cover how to do that only for specific forms.

In the Sulu FormsBundle the developers came up with the following approach: To determine if the event subscriber we have defined in the last blog post should be executed or not depends if a specific form element is part of the form.

In our case, this means that we add a "Hubspot" form field type which we can also use to let the user configure some default Hubspot settings, e.g. who should be the responsible person in Hubspot for the contact that gets created.

To make Sulu CMS aware of the new form field type, we need to create a new class that implements the Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface interface:

namespace App\Form\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType as TypeCheckboxType;
use Symfony\Component\Form\FormBuilderInterface;

class HubspotType implements FormFieldTypeInterface
{
public function getConfiguration(): FormFieldTypeConfiguration
{
return new FormFieldTypeConfiguration(
'app.form.dynamic.type_hubspot',
__DIR__ . '/../../../../config/form-fields/field_hubspot.xml',
'special',
);
}

public function build(FormBuilderInterface $builder, FormField $field, string $locale, array $options): void
{
$type = TypeCheckboxType::class;
$builder->add($field->getKey(), $type, $options);
}

/**
* @return mixed|string|null
*/
public function getDefaultValue(FormField $field, string $locale)
{
return $field->getTranslation($locale)?->getDefaultValue();
}
}

As you can see, in the getConfiguration() method an XML file is referenced which contains all additional properties the content editor needs to configure when the new Hubspot type is selected. In our case we want the content editor to be able to select the "Hubspot owner" of the contact that gets created:

<properties xmlns="http://schemas.sulu.io/template/template"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xi="http://www.w3.org/2001/XInclude"
xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/properties-1.0.xsd">
<xi:include
href="../../vendor/sulu/form-bundle/Resources/config/form-fields/header.xml"
xpointer="xmlns(sulu=http://schemas.sulu.io/template/template)xpointer(//sulu:property)"
/>
<property name="options/ownerId" type="single_select" mandatory="true">
<meta>
<title>sulu_form.hubspot_owner_id</title>
</meta>
<params>
<param
name="values"
type="expression"
value="service('app.form.dynamic.hubspot_owner_select').getValues()"
/>
</params>
</property>
</properties>

The property "options/ownerId" is a single select field that gets populated by the Symfony service app.form.dynamic.hubspot_owner_select. The service will fetch a list of owners directly via the Hubspot API. The service is a simple PHP class that exposes a getValues() method:

namespace App\Form\Dynamic\Helper;

use Exception;
use HubSpot\Client\Crm\Owners\Model\CollectionResponsePublicOwnerForwardPaging;
use HubSpot\Client\Crm\Owners\Model\PublicOwner;
use HubSpot\Discovery\Discovery;

class HubspotOwner
{
public function __construct(private Discovery $api)
{}

public function getValues(): array
{
$owners = [];

try {
$page = $this->api->crm()->owners()->ownersApi()->getPage();
if ($page instanceof CollectionResponsePublicOwnerForwardPaging) {
foreach ($page->getResults() as $owner) {
/* @var PublicOwner $owner */
$owners[] = [
'name' => $owner->getId(),
'title' => $owner->getEmail(),
];
}
}
} catch (Exception) {
}

return $owners;
}
}

Additionally, we need to mark the service as public in the services.yaml file:

services:
app.form.dynamic.hubspot_owner_select:
class: App\Form\Dynamic\Helper\HubspotOwner
public: true

As a last step, Sulu CMS needs to be aware of the new form type. This is done by tagging the service with the sulu_form.dynamic.type tag:

services:
app.form.dynamic.type_hubspot:
class: App\Form\Dynamic\Types\HubspotType
tags:
- { name: 'sulu_form.dynamic.type', alias: 'hubspot' }

Now, if you log into the Sulu Admin you should be able to see the new form field type in the respective dropdown element.

Still, the question is: How can we check in the event subscriber if the form data should be sent to Hubspot or not? Well, since we defined a hubspot field type and since that field needs to have the option "ownerId" set, we can check for that:

$dynamic = $event->getData();
if (!$dynamic instanceof Dynamic) {
return;
}

$ownerId = null;
$form = $dynamic->getForm()->serializeForLocale($dynamic->getLocale(), $dynamic);
foreach ($form['fields'] as $field) {
if (isset($field['options'], $field['options']['ownerId'])) {
$ownerId = (string) $field['options']['ownerId'];
}
}

When $ownerId is not null, we know we deal with a form that should send the data to Hubspot.