Introduction and project setup
In this recording I'm going to show you a way of submitting forms in Symfony which means that we don't have to write a load of HTML and we don't have to do a lot of validation and conditionals. We use something called a form type, and by using that it means a lot of the boilerplate stuff is handled for us automatically. So I think you're going to find this one really useful as forms is something that we do day in, day out when working on web applications.
I'll start by creating a new Symfony project: symfony new forms-demo. I'm also saying that I want to use Symfony version 5.1. So that's created. I'm going to move into the forms-demo folder and I'll start by requiring a couple of packages. I'm going to require the MakerBundle because we're going to create entities using an automated way of doing it, and I'm also going to require Doctrine. So I could have said composer require maker doctrine and imported them both at once, but I've done it with two calls.
Now that I've got the MakerBundle I can create entities from the command line doing this: symfony console make:entity, and I'm going to create a Subscriber entity. So I'll have a firstName property which is a string, leave the default field length and it's not nullable. Then I'm going to have a lastName field, same except that one will be nullable. And my third and final property will be email which again is a string default field length and that will not be nullable. Hit enter again and that completes that.
Reviewing the entity and creating migrations
So let's go over and check out the Subscriber entity, and there it is in all its glory. As you can see it's got id, firstName, lastName; it's also got an email field. Everything good there. So the Subscriber is going to be the basis of what we're working on here and when we persist from the form that's what will be created in the database. So I need to create a migration file with symfony console make:migration and there is my SQL to create subscriber in the database. And then all I need to do is say symfony console doctrine:migrations:migrate and that will migrate the database and create that table.
Prior to starting recording I created an empty database. I decided not to include that in this recording because this recording is all about showing you how to submit forms and handle the submission of forms etc. If you do need some pointers on setting up a database just check out my other recordings; I've done stuff on Doctrine and also other recordings on how to set up a Symfony project.
Okay, as you can see here my Subscriber entity has been successfully created. I should have saved my Subscriber table.
I'm now going to require a couple of things: I'm going to get a form package and a validator package. So the form package which is actually the Symfony form package and that is what is going to make most of the magic happen, and I'm also pulling in symfony/validator which I'll use to validate the form fields.
And so with that done I'm now going to create something called a Form Type for my Subscriber. So it's symfony console make:form and I'm calling this SubscriberFormType; that's the first argument and the second argument is the entity name which is Subscriber. And what that does is it creates this SubscriberFormType class which is a special class which creates a form based on the fields found in the Subscriber class. So if you can see in the configureOptions method I'm setting a default for the data_class and I'm setting that to the Subscriber class, and as you can see I've already got three fields there from my Subscriber: firstName, lastName, and email.
What that will do is build a form using the fields from my Subscriber entity and it's quite intelligent: it looks at the annotations in your Subscriber entity and it knows which kind of fields and which kind of inputs to use, for example text, email, numeric, also textareas. It's very good and as you'll see shortly it saves you having to write a lot of HTML which for someone like me that's a good thing.
So what I'm doing here is I'm saying for the email field I'm specifying that I would like that to be an EmailType, and I'm going to also add a submit button which I do like. And then that pretty much completes this for the time being; we'll add some more bits later.
But now I'm going to create a controller. I'm just going to add a method so that I can render a form. So I'll call this SubscriberController and it extends AbstractController because we'll be using some of the methods from the AbstractController class and I'll just call this show as in show form. So the first thing I want to do is new up a Subscriber: Subscriber equals new Subscriber; and then what I want to do is create a form instance.
And so like I say we can use methods on the AbstractController to do that: so form equals this->createForm. The first argument is the name of the class in string format for your form type, so SubscriberFormType::class, and so this will, as you will see, return a form instance. For the second argument we need to pass in the Subscriber. Okay, great. Now we have a form instance we need to figure out a way of rendering it on a web page.
In order to do that I'm going to return an HTTP response but in order to actually render the form on the web page we're going to need Twig. So back to the terminal where I composer require twig. So that's all installing. Then what we need to do is we need to inject Twig into our controller method: quite confusingly we actually use a class called Environment which comes from the Twig package. What I'm still going to call the variable twig, so I say twig->render and the first argument is the name of the Twig file that I'm going to be using and I'll call this show.html.twig. The second argument is an array which contains all the variables that you want to pass to the Twig file and one of those of course is the form which I will call subscriberForm and the way that I get the HTML from this form instance is to call the createView method. Great stuff.
When we install Twig we got this templates folder. We now need to add a subscriber folder to the templates folder and in there I will create my show.html.twig file. So here we are: I'll start off by extending the base template which is base.html.twig and then what I need to do is add a body block which is what will house our form. So it's block body and then I'll just end that block and we'll go and check out where this body block comes from so here we have an empty placeholder. By adding this block to our show.html.twig file it means that we can drop stuff into here.
If you're not really familiar with Twig don't worry about it: I'm using the very basics here and I'll probably do a Twig lesson at some point in the future to cover more of this stuff. But all I'm doing is I'm just adding a heading "Subscribe to my newsletter" and then in order to render the form I use this form method and I just pass in the subscriberForm variable.
Then what I want to do is start my server. So the way I do that is with symfony server:start -d to run it in detach mode which means it hands the terminal back to me. And I'm actually going to need annotations in order to create a route for this also, so composer require annotations. Then I need to go over to my controller and on my show method I'll delete all this stuff and I'm just going to say @Route and I'll just call it show. Visit that URL in your browser and you should see these three fields: firstName, lastName, email and a submit button.
Validation with Symfony Validator
Okay, so now let's go to the Subscriber entity and this is how we do validation: we just simply add the required Assert classes to each of the fields. So to this first one we add NotBlank because we're saying that the firstName has to have a value. I've already said that lastName can be nullable so we'll leave that one. We'll add another NotBlank to the email field and we'll also add an assertion to say this needs to be an Email and that will handle all of your validation.
What I need to do now is handle the submission of the form. So the first thing I'm doing is I'm injecting the Request into my show method, that's HttpFoundation Request, and then I'm saying form->handleRequest. I'll just go and see what this does: it says it inspects the given request and calls submit if the form has been submitted. Let's go and see what submit does, and so it says it submits data to the form, so the data which has been submitted via the fields in the front end is now being handled by our form object.
So what we're going to do is we're going to check that the form is submitted and we're going to check that the form is valid, i.e. the form fields pass validation, and if we do then we want to persist the Subscriber to the database.
Persisting data and demoing validation
So we're going to need our EntityManager, so I'm injecting EntityManagerInterface. I don't need to start setting values to fields like firstName, lastName, email etc because that has already automatically happened; the SubscriberFormType has done that for us. So all I need to do is persist and flush and then what I'll do is I'll just return a different response to say yep the form's submitted okay and this Subscriber entity has been created.
So just any kind of success message which means anything to you. I'm just going to say "Subscriber number" and then I'll get the id of the Subscriber using subscriber->getId. I'm just saying that's been created. Then what I want to do is go over to the browser and just fill in the form fields. So I don't actually need a last name; I'm going to leave that out to illustrate that. FirstName: John, email: [email protected], submit, and there we go: "Subscriber number one has been created." Back to my database admin tool, give that a refresh and there you go one record as we expect.
Now what I want to do is try and demonstrate that the validation works that we added to the Subscriber entity so I'm going to try and make the email field fail. But problem is that HTML5 is stopping me. Let's give this a go and there we go: "This value is not a valid email address" and where that comes from is Symfony's email validation that we added to our Subscriber entity.
Now what I want to do is add some security to this form because at the moment it is susceptible to cross-site request forgery. So what I've pulled in there is composer require symfony/security-csrf and that little package prevents cross-site request forgery. The way I include it in the form is by adding some extra configuration in the configureOptions method of the SubscriberFormType, so csrf_protection is set to true and then I need csrf_field_name and I will give that a full name of _token which is quite common, and then my last one is csrf_token_id and I'll call that subscriber_item. Then what I need to do is go back to the browser and I'll just show you this.
So look at the page source and what we've added there with those lines is this hidden field here and that contains our CSRF token. Form security is very important so make sure you don't miss that step. This step's probably a bit less important but it's going to make our form look a bit more professional. Symfony comes with a load of form themes and we're going to use a Bootstrap one just to make things a little more symmetrical and just make it look a bit more professional than what we've currently got: bootstrap_4_horizontal_layout.html is the template we're using for our form. Give it a refresh; there we go, looks much better already.
I've only really touched on it there; this isn't a design class but if you want to read more about it you can go to the address on your screen where it'll tell you how to customize your Bootstrap form theme. The final topic I'm going to cover is unmapped fields. At the moment all the fields on our form — firstName, lastName, email — all map to a field on the Subscriber entity or the Subscriber table. I'm going to show you how to handle how to add fields which don't actually map to ones on the table. The way I do that is by adding a third argument to the add method which takes an array of options, so I'm saying mapped is false and I'm also going to say required is false.
Over to the controller: what I want to do is grab the data which is submitted by that form field. So the way you do that is you say form->get and then you pass in the name that you gave to the field and then you just say getData. So I'll store that in an agreedTerms variable and initially before I use this I'm just going to dump it out and see what we get back. So we can use this dd method which comes from the Symfony VarDumper. Great for dumping out variables. We'll just fill in the form and we'll leave agreedTerms unchecked and so that's saying it gives us a false value. We'll give it a check and submit it and it's saying it gives us a true, so we're going to get back true or false and we can use this if we add it to our conditional which we created.
So saying the form has to be submitted, it has to be valid, and the user has to have agreed terms in order to create this record. Okay, so fill in the fields, agree terms, submit and there you go: subscriber number two created. Back in my database admin tool I've given it a refresh and I've now got two records in the database and so that's as far as I'll go with this one. Forms is quite a big subject so there's loads of other stuff you can do such as adding attachments etc. and I may revisit this subject sometime in the future.