Last week we ran into a problem with a Symfony form: we had a password field in a form meant to store an FTP password (FTP & SFTP are still the defacto way to move data around in the marketing world). When that form was first submitted with the password in place it was saved just fine, but subsequent submissions where the password was empty on the frontend of our application (for obvious reasons) would erase the password. Not good: expectations were violated and errors happened.
The Symfony Form Component: Complex & Flexible
The essential idea here is that we’ll hook into the
PRE_SUBMIT event and inspect the incoming data. If the field we want to keep is empty, we can use the property access component to fetch the original value from the object/array bound to the form on creation. The property accessor is important: data bound to forms may be objects or arrays or just about anything.
Of note here is that
$event->getData() returns the data submitted to the form, while
$event->getForm()->getData() returns the original values bound to the form on creation — what you passed in as the second argument to your controllers
createForm method, for instance.
Proving it Works
It’s pretty simple to prove this works with an integration test. I wouldn’t recommend a unit test for this listener or somethign that calls
KeepValueListener::onPreSubmit directly because it’s less valuable than knowing that the listener works within the system as a whole.
Our test case will set up a form factory and provide a way to create form with the our listener added.
From here there’s two paths we need to test: submission with an empty value which should keep the original
password value bound on form creation and submission with a new value which should keep the new value around.
But What About Erasing the Value?
This listener as it stands makes this impossible, so let’s fix it! We’ll add another field the listener is aware of that tells the thing to erase the field on a truthy value.
KeepValueListener changes a bit to accept the
$clearField to the constructor. If it sees a truthy value in that field it will erase the other field by setting its data to the forms configured empty value.
And of course we need a test case to verify this.
The Form Component is Much More Powerful Than This
Even if this example doesn’t fit your use case(s), it’s a great demonstration of how flexible the form component can be. Events make data and form modification extremely powerful, but there’s also things like data transformers and modification of existing form types with type extensions.
All of the code for this blog post is available on gist.github.com.