gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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.
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!
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 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.
Hey Daniel,
We locked dependencies on Symfony 3.1 for this course, so if you want to code along with this tutorial - you have a few options:
- Download course code, go to the start/ directory and manually upgrade dependencies to Symfony 4.x - but most probably you will faced some BC breaks that you will need to fix;
- Or, start with the new symfony 4 project and copy/paste parts of codes from this tutorial to it, i.e. you'll need to migrate all the features we have on the beginning of this tutorial.
Probably, the first one is easier if you want to code along with us on our project. The 2nd option is good if you already have a Symfony 4 project and want to practice this course on it.
Well, there's the 3rd option, but you'd need to wait. We're going to release the same topic for Symfony 4 track, but I can't tell you any estimations for now, probably it can takes months.
Cheers!
The forms are going to be HUUUUGE!
No political preference or allegiance in any direction intended - just having some fun :)
Whats the best practice naming convention for Form/* files?
Iam *confused*, some forms are named NAMEForm.php and some are NAMEFormType.php
When should I use *FormType.php and when Form.php?
Hey Mike P.!
That's a good question, I haven't found documentation about form naming standards, but here in Knp, we like to put inside "src/AcmeBundle/Form/Type/" all forms binded to an entity, and name them as "EntityNameType.php". If your form is not binded to an entity, then it can live at the root Form folder
I hope it helps you :)
Cheers!
Good answer thanks!
One question regarding it, you wrote: "we like to put inside "src/AcmeBundle/Form/Type/" all forms binded to an entity, and name them as "EntityNameType.php"."
So LoginForm and RegistrationForm are binded to the Entity User ('data_class' => User::class,) so they have to be in /Form/Type/LoginFormType.php and /Form/Type/UserRegistrationType.php as by this rule, do I understand that correctly?
Yes, exactly that!
Also, you could name your UserRegistrationType as RegistrationType, unless you have multiple registrations and need to be more specific.
// composer.json
{
"require": {
"php": "^7.1.3",
"symfony/symfony": "3.4.*", // v3.4.49
"doctrine/orm": "^2.5", // 2.7.5
"doctrine/doctrine-bundle": "^1.6", // 1.12.13
"doctrine/doctrine-cache-bundle": "^1.2", // 1.4.0
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.23.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.9.0
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.47
"nelmio/alice": "^2.1", // v2.3.6
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
Is there a way I can use the course code in symfony 4 so I can go through the tutorial?