Buy

Binding Forms to Objects: data_class

Open GenusFormType and find the configureOptions() method. Add $resolver->setDefaults(). Pass that an array with a single key called data_class set to AppBundle\Entity\Genus:

27 lines src/AppBundle/Form/GenusFormType.php
... lines 1 - 8
class GenusFormType extends AbstractType
{
... lines 11 - 19
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Genus'
]);
}
}

Do nothing else: refresh to re-submit the form.

Tip

You can also use a newer syntax in PHP for this:

'data_class' => Genus::class

Most editors will auto-complete this for you!

Boom! Now we have a brand-new Genus object that's just waiting to be saved. Thanks to the data_class option, the form creates a new Genus object behind the scenes. And then it sets the data on it.

Earlier, when we got back an associative array, these field names - name, speciesCount and funFact – could have been anything:

27 lines src/AppBundle/Form/GenusFormType.php
... lines 1 - 8
class GenusFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('speciesCount')
->add('funFact')
;
}
... lines 19 - 25
}

But as soon as you bind your form to a class, name, speciesCount and funFact need to match property names inside of your class:

134 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 23
private $name;
... lines 25 - 34
private $speciesCount;
... lines 36 - 39
private $funFact;
... lines 41 - 132
}

Actually, that's kind of a lie. These properties are private, so the form component can't set them directly. In reality, it guesses a setter function for each field and call that: setName(), setSpeciesCount() and setFunFact():

134 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 67
public function setName($name)
{
$this->name = $name;
}
... lines 72 - 90
public function setSpeciesCount($speciesCount)
{
$this->speciesCount = $speciesCount;
}
... lines 95 - 100
public function setFunFact($funFact)
{
$this->funFact = $funFact;
}
... lines 105 - 132
}

Technically, you could add a form field call outOnAMagicalJourney as long as you had a public method in your class called setOutOnAMagicalJourney().

Form Field Guessing!

Head back to your browser, highlight the URL and hit enter. This just made a GET request, which skipped form processing and just rendered the template.

Let's add a few more field we need: like subFamily:

30 lines src/AppBundle/Form/GenusFormType.php
... lines 1 - 8
class GenusFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('subFamily')
->add('speciesCount')
->add('funFact')
... lines 18 - 19
;
}
... lines 22 - 28
}

Hey, we're even getting auto-complete now: PhpStorm knows Genus has a subFamily property!

Also add isPublished - that should eventually be a checkbox - and firstDiscoveredAt - that will need to be some sort of date field:

30 lines src/AppBundle/Form/GenusFormType.php
... lines 1 - 8
class GenusFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('subFamily')
->add('speciesCount')
->add('funFact')
->add('isPublished')
->add('firstDiscoveredAt')
;
}
... lines 22 - 28
}

Cool, try it out!

Huge error!

Catchable Fatal Error: Object of class SubFamily could not be converted to string

Okay: that's weird. What's going on?

Until now, it looked like Symfony renders every field as an input text field by default. But that's not true! There's a lot more coolness going on behind the scenes!

In reality, the form system looks at each field and tries to guess what type of field it should be. For example, for subFamily, it sees that this is a ManyToOne relationship to SubFamily:

134 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 25
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\SubFamily")
* @ORM\JoinColumn(nullable=false)
*/
private $subFamily;
... lines 31 - 132
}

So, it tries to render this as a select drop-down of sub families. That's amazing, because it's exactly what we want.

But, it needs to be able to turn a SubFamily object into a string so it can render the text for each option in the select. That's the source of the error.

To help it, add a public function __toString() to the SubFamily class:

45 lines src/AppBundle/Entity/SubFamily.php
... lines 1 - 10
class SubFamily
{
... lines 13 - 39
public function __toString()
{
return $this->getName();
}
}

Refresh again!

Look at this! A free drop-down with almost no work. It also noticed that isPublished should be a checkbox because that's a boolean field in Doctrine:

134 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 41
/**
* @ORM\Column(type="boolean")
*/
private $isPublished = true;
/**
* @ORM\Column(type="date")
*/
private $firstDiscoveredAt;
... lines 51 - 132
}

And since firstDiscoveredAt is a date, it rendered it with year-month-day drop-down boxes. Now, those three boxes are totally ugly and we'll fix it later, but isn't it cool that it's guessing the right field types?

Fill out the form again with super-realistic data and submit. Woh! One more error:

Neither the property isPublished nor one of the methods getIsPublished() exist and have public access in class Genus

Remember how every form field needs a setter function on your class? Like name and setName()? Every field also needs a getter function - like getIsPublished() or one of these other variations.

This was my bad: when I set this up, I added an isPublished property, a setIsPublished() method, but no getter! I'll use the "Code"->"Generate" menu - or command+N - to generate that getter:

139 lines src/AppBundle/Entity/Genus.php
... lines 1 - 11
class Genus
{
... lines 14 - 115
public function getIsPublished()
{
return $this->isPublished;
}
... lines 120 - 137
}

Refresh! It dumps the Genus object of course, but check out the subFamily field! It's not the SubFamily ID - the form field took the submitted ID, queried the database for the SubFamily object and set that on the property. That's HUGE.

We're ready to save this!

Leave a comment!