January 25, 2012

Taming TYPO3 4.6's forms: Implementing a Webhook PostProcessor

At the OOP 2012 I am going to talk about integrating different cloud services by using Webhooks. Webhooks are HTTP callbacks that are triggered by certain events (http://www.webhooks.org).

For one of my live demos I want to integrate a contact form, rendered by the TYPO3 CMS, with Highrise, Zendesk and Dropbox. The trigger is a user, filling out and submitting the contact form. While it would have been trivial to directly send the web form to the integration script, I choose a different path. From TYPO3 4.6 on, the form extension allows configuration of so-called post processsors. These are PHP classes instantiated and invoked after the user has submitted the form without any validation errors.

The default post processor is the mail post processor (class tx_form_System_Postprocessor_Mail). Its purpose is to send all form data via email to the configured recipient. Unfortunately, this is the only post processor available at the moment. What I need is a post processor that sends all data as a POST request to another configured URL, the place where my integration logic is located. By the way, this approach comes much closer to the idea of (Web)hooks. The hook simply extends standard form functionality.

During implementation some obstacles have occured:
1. All post processors must be placed in a certain directory of the form extension
2. You have to deal with a complex domain model (cannot work with the request directly)
3. The post data was encrypted with "multiform/post-data"

1. In the following excerpt of class tx_form_System_Postprocessor (file typo3/sysext/form/Classes/System/Postprocessor/Postprocessor.php) which is part of the form extension, you can see how post processors are looked up, instantiated and processed.

Because the form extension is an extbase extension, the full qualified class names reflect the extension's folder structure. Hence, my Webhook post processor (tx_form_System_Postprocessor_Webhook) must be located in the same folder. This is generally not desireable, because in order to extend the extension I must change it. Real extensibility is planned for TYPO3 4.7 as described in a ticket (http://forge.typo3.org/issues/32701).

2. The form extension is based on extbase, the new (TYPO3 5 way) framework for extensions. It comes with a domain driven design approach, resulting in each extension having the domain model in its center. All requests are automatically mapped to the domain model. This is generally a good thing. But In my example, the principally simple task "resend the form data somewhere else" ended up in long investigation of how the domain model ticks. These are my findings:
  • The model used for the form definition in the backend is tx_form_Domain_Model_Form. It contains all field definitions including their names, types and validation rules.
  • The same structure is later reused when the user submits the form; the actual values are integrated.
  • Each field type is handled differently and has it's own domain class: 
    • normal input - tx_form_Domain_Model_Element_Textline, 
    • select - tx_form_Domain_Model_Element_Select, 
    • textarea - tx_form_Domain_Model_Element_Textarea
  • Not only the real form fields are part of the model, but also container elements like fieldsets.
This results in the following snippet (tx_form_System_Postprocessor_Webhook). It only handles those field types correctly that are used in the demo, namely input, select and textarea elements.

3. After overcoming the above mentioned hurdles, I found out that my POST request sent via CURL is encoded "multiform/post-data". The script handling the web hook has to deal with this fact. I have not changed the encoding because it is generally possible to resend uploaded files also.

To sum up, it is now possible to not only send submitted form data via mail, but also via another POST request. More and more services should offer Webhooks in order to bring powerful and flexible integration scenario to life.