Form Parts & Functions Reference

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Yo peeps! It's time to jump into a topic that's actually, super fun! Yep, we're going to learn to bend Symfony forms to our will: controlling exactly how they render... and believe me, by the end of this course, you'll be able to render a field in whatever weird way you want to.

Grab the Course Code!

To make forms great again, let's code together! Download the code from this page and unzip it. Inside, you'll find a trusty start/ directory, which will hold the exact code that I already have here.

To get the project running, open the README.md file and follow all the amazing details there. The last step will be open a terminal, move into the project directory - mine is called aqua_note - and then start the built-in PHP web server with:

bin/console server:run

Find a browser and pull up the address - http://localhost:8000 - to find our awesome project: Aquanote!

For this tutorial, there's just one thing you need to know: our database has a genus table, which is a type of animal classification. That table holds a bunch of different types of sea animals. To manage this, we have an admin section: login with weaverryan+1@gmail.com and password iliketurtles.

The admin section lives at /admin/genus. Click edit and... here's our starting form!

Form Type Basics

And so far, our form has all the parts you'd expect: a form class: GenusFormType:

... lines 1 - 13
class GenusFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('subFamily', EntityType::class, [
'placeholder' => 'Choose a Sub Family',
'class' => SubFamily::class,
'query_builder' => function(SubFamilyRepository $repo) {
return $repo->createAlphabeticalQueryBuilder();
}
])
->add('speciesCount')
->add('funFact')
->add('isPublished', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false,
]
])
->add('firstDiscoveredAt', DateType::class, [
'widget' => 'single_text',
'attr' => ['class' => 'js-datepicker'],
'html5' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Genus'
]);
}
}

And a controller that builds the form and passes it into the template:

... lines 1 - 15
class GenusAdminController extends Controller
{
... lines 18 - 34
public function newAction(Request $request)
{
$form = $this->createForm(GenusFormType::class);
// only handles data on POST
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$genus = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($genus);
$em->flush();
$this->addFlash(
'success',
sprintf('Genus created by you: %s!', $this->getUser()->getEmail())
);
return $this->redirectToRoute('admin_genus_list');
}
return $this->render('admin/genus/new.html.twig', [
'genusForm' => $form->createView()
]);
}
... lines 60 - 85
}

This gives us a genusForm variable inside of new.html.twig:

{% extends 'admin/genus/formLayout.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>New Genus</h1>
{{ include('admin/genus/_form.html.twig') }}
</div>
</div>
</div>
{% endblock %}

But the real work is done via an included template: _form.html.twig:

{{ form_start(genusForm) }}
{{ form_row(genusForm.name) }}
{{ form_row(genusForm.subFamily) }}
{{ form_row(genusForm.speciesCount, {
'label': 'Number of Species'
}) }}
{{ form_row(genusForm.funFact) }}
{{ form_row(genusForm.isPublished) }}
{{ form_row(genusForm.firstDiscoveredAt) }}
<button type="submit" class="btn btn-primary" formnovalidate>Save</button>
{{ form_end(genusForm) }}

Let's start there.

The Form Rendering Functions

The genusForm variable is an object, but you can't just print it. Instead, Symfony gives us a bunch of form functions: each renders a different part of the form.

To get all the deets, head to Symfony.com. Click into the Documentation and then find the Reference section. This holds a wonderful page called Twig Template Function and Variable Reference. This lists all the functions we'll be using and their arguments. Let's dive into these... and then, extend the heck out of them.

Leave a comment!

This tutorial is built on Symfony 3 but form theming hasn't changed much in Symfony 4 and Symfony 5. Other than some path differences - this tutorial should work fine.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
        "stof/doctrine-extensions-bundle": "^1.2" // v1.2.2
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}