Formidable – A Different Approach to Forms

Introduction

For many years, I’ve been using Zend_Form from Zend Framework 1, ZendForm from Zend Framework 2 and also a few other form libraries. With the advent of Zend Framework 3 and more type hinting options in PHP 7 I started to wonder if there is a way to handle forms in a nicer way. I got a little sick of libraries trying to dictate the resulting HTML or just making it really hard to create custom HTML.

So I did what I always do when I’m in this position; I look around different frameworks, even from other languages, to see how others solved the problem. After a few days of research, I ended up liking the approach of the Play Framework a lot, specifically the one in their Scala implementation. The first thing I did was of course learning to read Scala, which took me a little while because the syntax is quite different than what I was used to. Then I was able to understand the structure and how things worked, so I could start writing a PHP library based on that, named Formidable.

How it works

Formidable works similar to the form libraries you are already familiar with, yet it is slightly different. There is no mechanism in place to render any HTML, although it comes with a few helpers to render generic input elements, but those are mostly for demonstration to build your own renderers on. Also, every object within Formidable is considered immutable, so when passing around a form object, you can be sure that it’s just for you and nothing else modified it.

A form object always has a mapping assigned, which takes care of translating values between the input (usually POST) and a value object. There is no magic going on to hydrate entities directly, but everything goes through those value objects. The mappings are also responsible for validating your input, but offer no filter mechanism. Before I started writing this library, I analyzed all of my prior projects and discussed with other developers, and the only real pre-validation filtering we ever did was always just triming the input, which also became a default in Formidable.

I won’t go into detail about how you build forms with Formidable, as that topic is explained in detail in the Formidable documentation. Instead, I’m going to tell you about how to use the resulting forms properly.

Using Formidable forms

Let’s say we have a form for blog entries, which would mean that we’ll have a value object taking the title and the content from the form, and also being responsible for actually creating blog entries from itself and updating existing ones:

Example value object

final class BlogEntryData
{
    private $title;
    private $content;
     
    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content = $content;
    }
     
    public static function fromBlogEntry(BlogEntry $blogEntry) : self
    {
        return new self(
            $blogEntry->getTitle(),
            $blogEntry->getContent()
        );
    }
     
    public function createBlogEntry(int $creatorId) : BlogEntry
    {
        return new BlogEntry($creatorId, $this->title, $this->content);
    }
     
    public function updateBlogEntry(BlogEntry $blogEntry) : void
    {
        $blogEntry->update($this->title, $this->content);
    }
}

As you can see, our value object has all the logic nicely encapsulated to work with the actual blog entry. Now let’s see how our middleware for creating blog entries would look like:

Example create middleware

use DASPRiDFormidableForm;
use PsrHttpMessageServerRequestInterface;
 
final class CreateBlogEntry
{
    private $form;
     
    public function __construct(Form $form)
    {
        $this->form = $form;
    }
 
    public function __invoke(ServerRequestInterface $request)
    {
        if ('POST' === $request->getMethod()) {
            $form = $this->form->bindFromRequest($request);
             
            if (!$form->hasErrors()) {
                $blogEntryData = $form->getValue();
                persistSomewhere($blogEntryData->createBlogEntry(getUserId()));
            }
        } else {
            $form = $this->form;
        }
         
        return renderViewWithForm($form);
    }
}

The update middleware requires a bit more work, since we have to work with an already existing blog entry, but it will mostly look the same to our create middleware:

Example update middleware

use DASPRiDFormidableForm;
use PsrHttpMessageServerRequestInterface;
 
final class UpdateBlogEntry
{
    private $form;
     
    public function __construct(Form $form)
    {
        $this->form = $form;
    }
 
    public function __invoke(ServerRequestInterface $request)
    {
        $blogEntry = getBlogEntryToEdit();
             
        if ('POST' === $request->getMethod()) {
            $form = $this->form->bindFromRequest($request);
             
            if (!$form->hasErrors()) {
                $blogEntryData = $form->getValue();
                $blogEntryData->update($blogEntry);
                persistSomewhere($blogEntry);
            }
        } else {
            $form = $this->form->fill(BlogEntryData::fromBlogEntry($blogEntry));
        }
         
        return renderViewWithForm($form);
    }
}

Rendering

As I wrote earlier, Formidable is in no way responsible for rendering your forms. What it does give you though is all the field values and error messages you need to render your form. By itself it doesn’t tell you which fields exist on the form, so your view does need to know about that. Again, the documentation gives you a very good insight about how you can render your forms with helpers, but here is a completely manual approach to it, to illustrate how Formidable works at the fundamental level:

Example form HTML

<form method="POST">
    <?php if ($form->hasGlobalErrors()): ?>
        <ul class="errors">
            <?php foreach ($form->getGlobalErrors() as $error): ?>
                <li><?php echo htmlspecialchars($error->getMessage()); ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
 
    <?php $field = $form->getField('title'); ?>
    <label for="title">Title:</label>
    <input type="text" name="title" id="title" value="<?php echo htmlspecialchars($field->getValue()); ?>">
    <?php if ($field->hasErrors()): ?>
        <ul class="errors">
            <?php foreach ($field->getErrors() as $error): ?>
                <li><?php echo htmlspecialchars($error->getMessage()); ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
     
    <?php $field = $form->getField('content'); ?>
    <label for="title">Content:</label>
    <textarea name="title" id="title"><?php echo htmlspecialchars($field->getValue()); ?></textarea>
    <?php if ($field->hasErrors()): ?>
        <ul class="errors">
            <?php foreach ($field->getErrors() as $error): ?>
                <li><?php echo htmlspecialchars($error->getMessage()); ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif; ?>
     
    <input type="submit">
</form>

As I said, this is a very basic approach with a lot of repeated code. Of course you are advised to write your own helpers to render the HTML as your project calls for it. What I personally end up doing most of the time is writing a few helpers which wrap around the helpers supplied by Formidable and have them wrap the labels and other HTML markup around the created inputs, selects and textareas. There is a big advantage to decoupling presentation from the form library which you may already appreciate if you’ve wrestled with other popular libraries which bake in assumptions about how to markup the output.

Final words

I hope that this blog post gave you a few insights on Formidable and made you hungry to try it out yourself. It currently supports PHP 7.0 and up, and I like to get feedback when you see anything missing or something which can be improved. As written on the Github repository, there is still a small part missing to make it fully typehinted, which are generics in PHP.

I’ve created an RFC together with Rasmus Schultz a while back, but we are currently missing an implementer, which is why the RFC is somewhat on hold. If you know something about PHP internals, feel free to hop in to make generics a reality for us!

I really have to thank Soliant Consulting at this point, who sponsored the development time to create Formidable!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top