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!

  • 2017-08-16 Fran

    Fortunately I was able to change the structure of the phone table, but I have the same problem with another embedded form, it's exactly the same error. Sorry if the explanation is very long, but I have not been able to solve it for many hours! Thank you so much for your help anyway!

    I have a user form related to an entity:
    Https://puu.sh/xbFKZ/e24d7b...
    The entity: https://puu.sh/xbGhL/5049e4...
    The user is related to the Organization:
    Https://puu.sh/xbGlN/1f57a6...
    Reverse relation: https://puu.sh/xbGmO/e0cb8c...

    In this way an organization has a contact user.

    In the new organization view/controller I have a form:
    Https://puu.sh/xbGJh/854eb7... (and is linked with data_class
    The form is nested to the UserOrganizationContact form, so that an organization can be registered along with the contact user:
    Https://puu.sh/xbGR0/efd928...

    I get this form and send it to twig, which shows it correctly. The only problem is when I want to validate it, otherwise I correctly create the contact entity!
    Https://puu.sh/xbH9m/da3173...

    Finally, when I apply the Valid () annotation in Entity / Organization, on the contact relation column, the following error occurs, only in the submit form:
    Https://puu.sh/xbHpR/fd0399...

    If I delete the Valid annotation, it works correctly and creates the Organizationcontact entity:
    Https://puu.sh/xbHsd/dc399d...

    Thank you for your time, this courses are helping me a lot!

  • 2017-08-16 weaverryan

    Yo Fran!

    Hmmm. Do you mean the @Valid annotation? Can you post a screenshot of the full stacktrace... I'm not sure exactly where that error is coming from. And also post your 2 form classes if you can. And you do absolutely have data_class => Phone::class inside PhoneFormType, right? I'm sure it's some small details we've missed!

    Cheers!

  • 2017-08-16 Fran

    Yes, thank you! I know is a little bit weird, im trying to convince my boss that we can change that about the Phone's list.
    I have one last question! What about validation inside PhoneFormType? When i try to apply the Validate() annotation for embedded forms, y have an error saying:
    "Expected argument of type "AppBundle\Entity\Phone", "array" given
    I think that's because the 3 fields (area code, country code and phone) are passing as an array? Im mapping the PhoneFormType just like the other forms, with the data_class

    Thank you so much for your help!

  • 2017-08-15 weaverryan

    Yo Fran!

    Ah, now I think I understand :). There are sort of 2 answers:

    1) I don't love the way your relationship is setup, but you may have a good reason :). With this relation, it means that one Organization can have many Phone numbers. Is that correct? It's even a bit more confusing because it seems like you want to create a form where the user is entering just *one* Phone number, not many. But, sometimes this is exactly what you want :).

    2) If this *is* exactly what you want, then here is what you should do:

    A) Add these 2 new methods to your Organization entity:


    public function getMainPhone()
    {
    return $this->phones->first();
    }

    public function setMainPhone(Phone $phone)
    {
    if ($this->phones->contains($phone)) {
    return;
    }

    $this->phones[] = $phone;
    // make sure to set the owning side of the relation
    $phone->setOrganization($this);
    }

    B) Create a PhoneFormType class with the fields you need on it: areaCode, countryCode, phoneNumber (make sure to set the data_class to the Phone entity)

    C) In OrganizationFormType (or however you call it), add a new mainPhone field:


    $builder
    // ...
    ->add('mainPhone', PhoneFormType::class)

    That should be it! You've now tricked the form system into thinking that your Organization has *one* Phone (mainPhone). When you fill out the 3 fields, it will create a new Phone and call setMainPhone(). It will even work if you edit an organization.

    Let us know if this helps!

    Cheers!

  • 2017-08-14 Fran

    Sorry, im not explaining it well.
    I'm using Doctrine´s ORM but i don´t need to fill a select box with number's from another Entity, i just need to display 3 input´s (Area Code - Country code - Phone Number) that will save data in another table (Phone's table) The Organization has a relation oneToMany with the Phone's Entity (with Doctrine Annotation)
    These 3 fields are the only that not belong to my class related to the form, so i don't know how to show them properly.

    Thank you so much for your replies and your time!

  • 2017-08-14 Diego Aguiar

    Oh, so you are not using Doctrine's ORM ?
    In that case you would have to do it manually, add 3 "not mapped" fields to you Form, and create a new Phone instance, setting up those values manually.

    You may find these links helpful:
    Form mapped option: http://symfony.com/doc/curr...
    Form without data class: https://symfony.com/doc/cur...

    Cheers!

  • 2017-08-14 Fran

    Hey, thank you for the reply. But if I have to just print 3 number field types that belongs to other tables I have to use the EntityType too? What I need to do is that the fields phone - area code and country code get saved in the Phone number's column, with the FK of the organization (I'm not the owner of the database so unfortunately I can´t change that)

  • 2017-08-14 Diego Aguiar

    Hey Fran!

    When your entity has a relationship to other entity, in your FormType, you have to add that field as an EntityType, so you will see a menu with a list of phones. You can read more about EntityType's here: http://symfony.com/doc/curr...

    Have a nice day!

  • 2017-08-12 Fran

    HI! This is an amazing course!

    I have a question, what about form binding against an object with oneToMany relationship?

    I have an Organization Entity that haves a telephone column. The column is related to a table of phones with area code, country code and FK of the organization.

    When i print the entire form just for a test, i have an error saying that the columns of the Phones table doesnt exist. Should i create a new Object Phone before the createForm function, and then pass it as an argument? I dont know if in that case the phone will be related to the Organization.

    Thank you!

  • 2017-08-01 Moises Cano

    Sorry about that. I should have taken that part out. I have it commented out because i'm not using it right now.

  • 2017-08-01 Victor Bocharsky

    Hey Moises,

    Hm, I wonder how your exceptions() method looks like. Does it really return an array of entities instead of a single entity? Could you show its code? Btw, entity type has "multiple" option, probably you want to set it to "true":


    $builder
    ->add('trcode', EntityType::class, [
    'class' => ExceptionCode::class,
    'multiple' => true,
    'query_builder' => function (ExceptionRepository $repo) {
    return $repo->exceptions();
    }
    ])

    Cheers!

  • 2017-07-27 Moises Cano

    I'm going to ask it a different way. Here is my FormType code.


    namespace AppBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\Extension\Core\Type\NumberType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;

    class ExceptionFormType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder
    ->add('usrid')
    ->add('trcode')
    ->add('cola', NumberType::class,
    ['scale' => 2])
    ->add('colb', NumberType::class,
    ['scale' => 2]
    )
    ->add('colc', NumberType::class,
    ['scale' => 2])
    ->add('cold')
    ->add('fringe', ChoiceType::class,
    array(
    'choices' => array(
    'No' => 0,
    'Yes' => 1
    )
    ));

    }

    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults([
    'data_class' => 'AppBundle\Entity\Exception'
    ]);
    }

    public function getBlockPrefix()
    {
    return 'app_bundle_exception_form_type';
    }

    I want to fetch more than one row. When I look at the debug tool i see this sql.


    SELECT
    t0.usrid AS usrid_1,
    t0.trcode AS trcode_2,
    t0.cola AS cola_3,
    t0.colb AS colb_4,
    t0.colc AS colc_5,
    t0.cold AS cold_6,
    t0.dept AS dept_7,
    t0.fringe AS fringe_8,
    t0.seq AS seq_9,
    t0.id AS id_10
    FROM
    exception t0
    WHERE
    t0.usrid = ?
    LIMIT
    1

    How do i remove 'LIMIT 1' ?

  • 2017-07-19 weaverryan

    Hey Moises Cano!

    Can you post some of your code? I don't fully understand what you mean by "binding my form to an entity that will return more than one row"? If you want to edit a product, for example, you are editing just one product at a time. Are you trying to somehow edit more than one object on the same form?

    Cheers!

  • 2017-07-19 Moises Cano

    I am binding my form to an entity that will return more than one row. When I look at the debug tool my entity query has a 'LIMIT 1' clause. How do I remove this?

  • 2017-03-04 weaverryan

    Sweet - good debugging!

  • 2017-03-04 Jelle Schouwstra

    Thanks, but I`ve found the solution in the controller I tried pass extra data about the object which was not needed and caused the error

  • 2017-03-04 weaverryan

    Hey Jelle Schouwstra!

    Ah, super glad you're happy! Well, happy except for this error :). And it *is* a strange error! What does the code look like for your `ChoiceType::class` field? Are you possibly passing it a choices option explicitly? And if so, is it possible that some of the objects you're passing haven't been saved to the database yet?

    Let me know - I'm sure we can debug it!

    Cheers!

  • 2017-03-03 Jelle Schouwstra

    Awesome tutorials, well worth the subscription!

    What can I do against this error?

    "Entities passed to the choice field must be managed. Maybe persist them in the entity manager?"

  • 2016-10-31 Daan Hage

    He Ryan,

    Thanks for your assistance. It helped alot. I think it had something to do with adding Assert\NotBlank().
    the -vvv tip helped alot too!

  • 2016-10-30 weaverryan

    Yo Daan!

    My answer on your other comment will address this too, fortunately :). When you copy the relationship code for the subFamily into your Genus entity, your migrations will suddenly want to generate a sub_family_id column. Sorry for sneaking that in!

    Cheers!

  • 2016-10-29 Daan Hage

    He Ryan,

    BEcause of this clip I got a bit further with the fixtures load. However I now get the following error:

    [Doctrine\DBAL\Exception\InvalidFieldNameException]
    An exception occurred while executing 'INSERT INTO genus (name, species_count, fun_fact, is_published, first_dis
    covered_at, sub_family_id) VALUES (?, ?, ?, ?, ?, ?)' with params ["Eumetopias", 807, "Recusandae asperiores acc
    usamus nihil repellat vero omnis voluptates.", 1, "1854-01-29", 37]:
    SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sub_family_id' in 'field list'

    Do you know why this happens?

  • 2016-10-11 Victor Bocharsky

    Hey Boban,

    Yes, nice tip! This is a new feature for PHP 5.5 and it's a very nice to have an autocomplete for FQCN.

    Cheers!

  • 2016-10-10 Boban Acimovic

    'data_class' could be set to AppBundle\Entity\Genus::class also.

  • 2016-10-03 Victor Bocharsky

    Hey Maksym,

    That's a good tool and definitely in some cases you can prefer to use it instead, but it *always* generates both setters and getters. However, for example, some entities could not have setters, or probably in some cases you don't need both setters /getters. So, in some cases, better to add them manually instead of remove unnecessary methods after this tool.

    IMO, in PhpStorm I can add setters/getters quicker, i.e. I don't need to switch to the console and back to the IDE :p

    Cheers!

  • 2016-10-02 Maksym Minenko

    Why don't you use doctrine:generate:entities instead of Phpstorm autocompletion?

  • 2016-06-09 Raphael Schubert

    Hello Weaverryan!!! Thanks... i do not have it installed... i`ll provide and test... will post here when finishe...

  • 2016-06-09 weaverryan

    Hey Raphael!

    Hmm, that's very interesting. The MoneyType is *supposed* to look up the number format by looking at the currency that you pass as an option. It's possible that the issue is because you don't have the "intl" PHP extension installed. Can you see if you see "intl" in the list, when you run this command?


    php -m

    This command displays the modules installed in your PHP :).

    Cheers!

  • 2016-06-09 Raphael Schubert

    Hello there, i just cant configure symfony to use moneytype with BRL and grouping... It knows its R$ (BRL) but needs to format inputed number as 000,000,000.00 but the correct is 000.000.000,00

    how can i solve this? Thanks!!!