Zend Framework 2 Forms, Factories, InputFilters and Hydration

It's probably difficult to recall the "thing" that prompts us into crafting the best forms possible, or especially to neophytes, what a clean form might be. Personally, I'd seen a really clean example in a GitHub repo and then mentally compared it with what I'd coded...then...lightbulb! "Damn, that's how that's done!"

In a framework like ZF2 where there's a lot of boilerplate code at play, cleanliness is truly godliness. So can forms be beautiful? I think so.

I've struggled with forms and ZF2 for some time. In hindsight, I even documented my struggle along the way. I think I've had time to distill the outcome; and I'll propose a blanket approach in what follows. Its benefit is that if you need to do 'something', you'll know precisely where it needs to get done.

By adopting this, we on a handshake agree that: forms should be built by factories, that input filters should be separated from form construction logic, and that annotation builders are pure evil (had to squeeze that last one in!).

Some Code...

Consider a form that needs to store tax settings (TaxForm). The contract is:

It has to be aware of country codes; good opportunity to show how to separate dependencies via Factory.

I also use Doctrine, and need it to spit out a Tax object.

When a country is selected, the 'state' select is populated.

When a country is submitted (on Form post) I always want it to repopulate the "state" select.

Finished Form

I'll spare all the gory field details, only including country and state; important take-away is that the constructor and init, are separate. I needed the translator in here too, the TranslatorAwareTrait really comes in handy.

So, barring our completion of the InputFactory and Doctrine Entity, the "Form" side of things is done. Let's take a look at InputFilters, which are what you should be configuring to validate your forms.

Don't do getInputFilter right inside the form, you'll be thankful when you need your validator down the road (e.g., zf-content-validation, or Apigility). They're good outside of forms! You'll see!

InputFilter and Factory

Input filters are specifications for filtering and validating. Remember that filters run before validators - that'd been a source of hair loss for me when I was starting out.

The factory's need is arguable if your filter only uses baked in filters/validators. I have admittedly skipped factories time and again. Quite often though you'll need to do some Entity-based validation, for example, make sure that another Tax with a same name doesn't exist in the database (or another user with the same email, etc.). I'm using Doctrine, so I need the Factory specifically to inject the Entity Manager's Tax repository into my actual InputFilter. So:

Very basic, but the one big gotcha to note is that when you run an input filter factory, the service locator that you get is not the main service locator. I needed to run ->getServiceLocator() on the passed $serviceLocator to get the main SL (which can then, get me my repository).