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!

  • 2018-02-07 Diego Aguiar

    Hey Vasili Repin

    Victor is on vacations right now, if you could translate your question to English, I would be happy to help you :)

  • 2018-02-07 Vasiliy Repin

    1.$product->getDescriptions->add($description); Усть только функция. getDescriptions(). Выдает ошибку Error: Call to a member function add() on a non-object.
    2. Форма создается только со свойствами из Product а из Descriptions - 0, пишет просто Descriptions без полей для ввода.

  • 2018-02-05 Victor Bocharsky

    Добрый день,

    ОК, теперь я вижу, тогда, если не вдаваться в подробности вашей реализации, ваша ProductType форма должна быть следующей:


    $builder
    ->add('productName')
    ->add('price')
    ->add('descriptions', CollectionType::class, [
    'entry_type' => ProductDescriptionType::class,
    ]);

    Где ProductDescriptionType - это форма для "RevprodBundle\Entity\Description" сущности, которая в свою очередь:


    $builder
    ->add('smallDesc')
    ->add('bigDesc');

    В первой форме вы должны указать в configureOptions() "'data_class' => Product::class,", а для второй формы "'data_class' => Description::class,"

    Если вы реализуете это для добавления а не для редактирования, то вам нужно будет немного модифицировать новый Product объект, т/е/ в контроллере нужно сделать:


    $product = new Product();
    $description = new Description();
    $product->getDescriptions->add($description);

    $form = $this->createForm(ProductType::class, $product);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
    dump($product, $description);
    // Здесь уже $product & $description должны быть заполнены отправленными данными
    }

    Удачи!

  • 2018-02-02 Vasiliy Repin

    namespace RevprodBundle\Entity;

    //use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;

    /**
    * @ORM\Entity(repositoryClass="RevprodBundle\Repository\ProductRepository")
    * @ORM\Table(name="product")
    */

    class Product
    {
    /**
    * @ORM\Column(type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;

    /**
    * @ORM\Column(type="string", length=127) /* length?
    */
    private $productName;

    /**
    * @ORM\Column(type="decimal", scale=3)
    */
    private $price;

    /**
    * @ORM\Column(type="string", length=50)
    */
    private $model;

    /**
    * @ORM\Column(type="datetime")
    */
    private $dateCreated;

    /**
    * @ORM\Column(type="boolean")
    */
    private $isPublished = true;

    /**
    * @ORM\OneToMany(targetEntity="RevprodBundle\Entity\Description", mappedBy="product")
    */
    private $descriptions;

    /**
    * @ORM\Column(type="string", length=127)
    */
    private $nameAdressLine;

    /**
    * @return mixed
    */

    //Experiment
    /*public function getDescription()
    {
    return Description::class;
    }*/


    public function getNameAdressLine()
    {
    return $this->nameAdressLine;
    }

    /**
    * @param mixed $nameAdressLine
    */
    public function setNameAdressLine($nameAdressLine)
    {
    $this->nameAdressLine = $nameAdressLine;
    }


    /**
    * @return // Nado vernut' cifru
    */
    public function getId()
    {
    return $this->id;
    }

    /**
    * @return mixed
    */
    public function getProductName()
    {
    return $this->productName;
    }

    /**
    * @param mixed $productName
    */
    public function setProductName($productName)
    {
    $this->productName = $productName;
    }

    /**
    * @return mixed
    */
    public function getPrice()
    {
    return $this->price;
    }

    /**
    * @param mixed $price
    */
    public function setPrice($price)
    {
    $this->price = $price;
    }

    /**
    * @return mixed
    */
    public function getModel()
    {
    return $this->model;
    }

    /**
    * @param mixed $model
    */
    public function setModel($model)
    {
    $this->model = $model;
    }

    /**
    * @return mixed
    */
    public function getDateCreated()
    {
    return $this->dateCreated;
    }

    /**
    * @param mixed $dateCreated
    */
    public function setDateCreated($dateCreated)
    {
    $this->dateCreated = $dateCreated;
    }

    /**
    * @param mixed $descriptions
    */

    //Нельзя использовать setDescriptions()!!!!


    /**
    * @return mixed
    */
    public function getisPublished()
    {
    return $this->isPublished;
    }

    /**
    * @param mixed $isPublished
    */
    public function setIsPublished($isPublished)
    {
    $this->isPublished = $isPublished;
    }

    /**
    * @return mixed
    */
    public function getDescriptions()
    {
    return $this->descriptions;
    }

    }

    Description.php:


    namespace RevprodBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
    * @ORM\Entity
    * @ORM\Table(name="description")
    */
    class Description
    {
    /**
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    * @ORM\Column(type="integer")
    */
    private $id;

    /**
    * @ORM\Column(type="string")
    */

    private $smallDesc;

    /**
    * @ORM\Column(type="text")
    */

    private $bigDesc;

    /**
    * @ORM\ManyToOne(targetEntity="RevprodBundle\Entity\Product", inversedBy="descriptions")
    * @ORM\JoinColumn(nullable=false)
    */
    private $product;

    /**
    * @return mixed
    */
    public function getId()
    {
    return $this->id;
    }

    /**
    * @return mixed
    */
    public function getSmallDesc()
    {
    return $this->smallDesc;
    }

    /**
    * @param mixed $smallDesc
    */
    public function setSmallDesc($smallDesc)
    {
    $this->smallDesc = $smallDesc;
    }

    /**
    * @return mixed
    */
    public function getBigDesc()
    {
    return $this->bigDesc;
    }

    /**
    * @param mixed $bigDesc
    */
    public function setBigDesc($bigDesc)
    {
    $this->bigDesc = $bigDesc;
    }

    /**
    * @return mixed
    */
    public function getProduct()
    {
    return $this->product;
    }

    /**
    * @param mixed $product
    */
    public function setProduct(Product $product)
    {
    $this->product = $product;
    }

    /*public function __toString()
    {
    return $this->getSmallDesc();

    }*/
    }
  • 2018-02-02 Vasiliy Repin

    /**
    * @ORM\OneToMany(targetEntity="RevprodBundle\Entity\Description", mappedBy="product")
    */
    private $descriptions;

    это связь к Descriptions

  • 2018-01-31 Victor Bocharsky

    Добрый день Василий,

    Вы говорили что у вас между Product и Description есть связь, скажите тогда как я из Product могу получить Description на уровне сущностей, т/е/ как называется свойство в Product которое содержит Description? Хотя это немного странно что Description - это свойство у Product а отдельная сущность. Можете показать свойства сущностей Product и Description чтобы я мог понять какая между ними связь?

  • 2018-01-30 Vasiliy Repin

    Спасибо! Все сделал как Вы сказали...
    Но, печаль... Не работает((( Форма-то появилась, почти как надо, только добавилось название Description перед блоком форм из класса Description.
    Пишет ошибку: Neither the property "description" nor one of the methods "getDescription()", "description()", "isDescription()", "hasDescription()", "__get()" exist and have public access in class "RevprodBundle\Entity\Product".
    обращается не к классу Description из формы DescriptionFormType а к классу Product и ищет методы, что выше. Что можно сделать? Создать метод в Product и сослаться на Description?

  • 2018-01-30 Victor Bocharsky

    Добрый день Василий,

    Думаю у вас немного неправильная форма, вместо того чтобы прописывать data_class для каждого свойства вам нужно один раз прописать data_class в configureOptions(). Это должна быть проблема что у вас вместо объекта Product возвращается ассоциативный массив, так как система не знает к объекту какого класса мапить данные.

    Еще одна проблема которую я вижу у вас в форме - это то что вы пытаетесь задействовать одну форму для нескольких сущностей и делаете это немного не так. Если у вас в Product есть свойства price & description, и при условии что вы пишете "'data_class' => Product::class" в configureOptions(), то в форме вы должны написать именно эти поля:


    $fornBuilder
    ->add('price')
    ->add('description', DescriptionType::class);

    т/е/ вам нужно создать DescriptionType форму которая будет именно для Description класса, т/е/ в ней вы пропишете "'data_class' => Description::class" в configureOptions() а также добавите только те поля которые касаются именно Description


    $fornBuilder
    ->add('smallDesc');

    И тогда у вас получится правильная форма в форме которая будет одновременно обновлять несколько сущностей

    Удачи!

  • 2018-01-29 Vasiliy Repin

    В ProductFormType
    ->add('price', null, ['data_class' => Product::class]);
    ->add('smallDesc', TextType::class, ['data_class' => Description::class]);
    и другие из другого класса.
    В
    public function configureOptions(OptionsResolver $resolver) ничего не пишу, вообще не использую её и setDefaults. Если прописать setDefaults(array(
    // 'data_class' => Product::class)), то только один класс-сущность автоматически присваевается свойствам объекта. Когда её не использую, форма на разных классах дает
    $form->getData() ассоциативный массив. Из которого еще сложнее вытащить данные и присвоить разным классам. По-другому не получается... Что я делаю не так? Спасибо!!!

  • 2018-01-26 Victor Bocharsky

    Добрый день,

    То что разные сущности в одной форме - это не проблема. Так а зачем вы так делаете? После того как вы сделали "$form->handleRequest($request);" все данные, отправленные в форме, уже должны быть в сущностях, поэтому после "$form->handleRequest($request);" можно сразу делать "$em->flush();" для того чтобы сохранить. Ну точнее, лучше сначала проверить что форма валидная и действительно была отправлена, т/е/ вот так:


    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
    $em->flush();
    }

    Т/е/ то что я вам хочу сказать уже в нескольких комментариях это то что вам не нужно вызывать getData() вручную.

    Удачи!

  • 2018-01-24 Vasiliy Repin

    Да, именно это. Я делаю $form->handleRequest($request) , но у меня разные сущности в одной форме.
    Потом
    $product->setPrice($form->get('price')->getData());
    $description->setSmallDesc($form->get('smallDesc')->getData());
    $description->setBigDesc($form->get('bigDesc')->getData());
    $image->setImage($form->get('Image')->getData());
    а вотом сохраняю в БД.
    Но, это быдлокодинг... есть может другой вариант достать из формы данные, построенной на разных сущностях?

  • 2017-12-29 Victor Bocharsky

    Чтобы реализовать автоматическое вытаскивание данных из формы смотрите handleRequest(). Т/е/ когда вы заполнили и отправили форму - на сервере вам нужно сделать $form->handleRequest($request) и данные которые были отправлены автоматически заполнят свойства сущности. Или я неправильно понимаю о каком автоматическом вытаскивании данных из формы идет речь?

  • 2017-12-28 Vasiliy Repin

    В том-то и проблема, это я знаю как сделать, а вот как привязать автоматическое вытаскивание из формы данных - неизвестно. Все пытаюсь создать форму для добавления новых данных, созданную на разных классах.

  • 2017-12-28 Victor Bocharsky

    Добрый день,

    Можно и так, вручную доставать через getData(), но удобней чтобы данные автоматически привязывались когда вы отправили форму перед тем как ее обрабатывать через $form->handleRequest($request). Если же речь идет о генерировании формы с начальными данными, когда речь идет о форме для редактирования а не добавления - то как я уже говорил начальные данные можно прокинуть при создании формы, т/е/ $this->createForm(ProductForm::class, $product); где $product - и есть сущность с начальными данными. Если бы посмотрели курс который вам скинул - нашли бы :)

    Вот ссылка еще раз для более подробного описания: https://knpuniversity.com/s...

    Удачи!

  • 2017-12-27 Vasiliy Repin

    да, пока похоже, что мой случай.
    т.е. извлечь из формы вот так:
    $product->setPrice($form->get('price')->getData()); а потом в БД. - это было первое, что я придумал. И подумал, что это жуткие костыли. Может есть другой более годный вариант?
    Почему нельзя при создании формы (адд) просто присвоить её поля разным классам?
    Что-то я не нашел ничего общего с моим и https://knpuniversity.com/s....
    Спасибо!

  • 2017-12-21 Victor Bocharsky

    Добрый день,

    Начинать с формы добавления будет немного сложнее чем с формы редактирования. Композитный класс скорее всего не нужен если есть главная сущность, а все остальные сущности являются дочерними, т/е/ связаны с главной сущностью по схеме один к одному, многие к одному или многие ко многим. Если же это ваш случай - супер, осталось определить главную сущность и создать дня нее форм тайп. В форм тайпе сначала пропустите поля которые являются отдельными сущностями, т/е/ создайте форму которая будет содержать только простые поля типа "input type=text" или "textarea", например где вы заполняете описание, стоимость, дату, и т/д/. После того как эта простая форма будет готова - добейтесь того чтобы ее отрендерить на странице, а после этого чтобы когда вы отправляете форму - обработайте ее так чтобы информация писалась в БД. Если все работает, дайте мне знать и после этого можно будет переходить на второй этап.

    Кстати о формах, думаю будет полезным этот курс: https://knpuniversity.com/s... . Также есть https://knpuniversity.com/s... - где мы строим более сложные формы, по сути именно такие как вам надо. Думаю он должен автоматически ответить на большинство ваших вопросов, но рекомендую его только после "Symfony Forms: Build, Render & Conquer!".

    Удачи!

  • 2017-12-20 Vasili Repin

    Спасибо за ответ!
    На данном этапе я хочу создать форму для добавления новой записи, а не для редактирования. В форме много полей, поля группами принадлежат разным объектам. Допустим, заполняем продукт, его название, стоимость, дата производства, большое и малое описание и картинка к нему или несколько картинок. Т.е. форма относится к разным классам-сущностям. Отношения уже потом. Сейчас нужно всего лишь создать форму, чтобы потом в БД она разошлась по разным таблицам. Не нашел подробной инструкции как это сделать. Слышал про композитный класс, но не знаю как это сделать, да и кажется, что это лишнее... Спасибо Вам за помощь!

  • 2017-12-18 Victor Bocharsky

    Добрый день,

    Из нашей команды русский понимаю только я, поэтому для вопросов на английском у вас есть шанс получить ответ быстрее :)

    Теперь вы меня запутали окончательно с этим примером, так как речь вроде шла о том что у вас Product относится к Image и Description как один-ко-многим, но из вашего примера я вижу что вы пытаетесь создать возможность редактировать сразу несколько Product а Image к ним вообще не относится. Или вы поменяли требования? Давайте пойдем по другому пути, можете показать вашу сущность Product с теми свойствами которые вы хотите видеть в форме но у вас не получается отобразить? И опишите как вы себе видите конечную форму, а я попробую помочь.

    Что вы имеете ввиду что нужно передать несколько аргументов в $this->createForm(ProductForm::class, $product)? Симфони формы могут привязываются только на одну сущность, которая является главной. Все дочерние сущности должны привязываться автоматически если у вас правильно написанный form type, т.е. если у вас есть Product который связан как один-ко-многим к Image, то в createForm() вы просто передаете объект Product. Но прежде чем создать форму и передать в нее Product, вы можете модифицировать его, например, добавить в него несколько картинок, но все это зависит от ваших целей. Вот очень упрощенный пример:

    $product->addImage(new Image());
    $product->addImage(new Image());
    $product->addImage(new Image());
    $this->createForm(ProductForm::class, $product);

    Т/е/ вы передаете объект Product сразу с 3-мя картинками, и если у вас есть коллекция картинок в форме - то вы должны будете увидеть отображение этих трех картинок в форме. Как видите, мне не нужно передавать в форму еще отдельно картинки так как они являются дочерними по отношению к Product.

    Также, эта статья может быть полезна для вас: http://symfony.com/doc/curr... - там как раз идет речь о том как встраивать коллекции в формы. Кстати, для правильной реализации добавления / удаления нужно будет поработать с JavaScript: http://symfony.com/doc/curr... . Поэтому я рекомендую вам сначала сделать возможность редактирования уже существующей информации в БД, так будет проще начать и будет понятнее.

    Удачи!

  • 2017-12-15 Vasili Repin

    Я вижу, вы русский знаете. Уже кучу времени потратил, но никак не могу решить эту задачу. У меня есть 3 эти сущности: Продукт, Картинки, Описание. И мне нужна одна форма, чтобы заполнить все это, просто форма с несколькими полями и кнопкой для загрузки картинок, это для страницы /new. Встраивание форм попробовал, что-то выдает только название (Products и image) без самих форм. Уже не знаю, что делать. Вроде простая задача, часто используется, а реализации нормальной нет( Может есть какой нормальный вариант?
    ->add('products', CollectionType::class, ['entry_type' => ProductFormType::class])
    ->add('image', CollectionType::class, ['entry_type' => ImageFormType::class])
    Да и что делать, если в $this->createForm(ProductForm::class, $product) нужно передать несколько аргументов? Спасибо за помощь!

  • 2017-12-13 Diego Aguiar

    Hey Vasili Repin

    First, you have to create a new class, just like an entity but this one won't be stored in the DB, i.e. won't be managed by doctrine. This new class will contain all the data you want to show in your form, so, if you are going to show a product, it will need a product property. Keep doing the same for any other field that you want to show

    Then, you have to create a FormType for this new class, adding all properties to it, and any field that represents an object, you will have to embed it's FormType or build it in there. For this step you can watch how Ryan does it in this video: https://knpuniversity.com/s...

    And finally, when you submit your form, you can get your product object from the form data like this


    // in some controller.php
    public function createAction(Request $request)
    {
    $form = $this->createForm(yourDataModelClass::class);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid() {
    $dataModelObject = $form->getData(); // this is an instance of your new class
    $product = $dataModelObject->getProduct(); // you need to add accessor methods to the class
    ...
    }
    ...
    }

    Cheers!

  • 2017-12-13 Vasili Repin

    Please tell me how to do this? How to create a new model class, which will hold a reference to all related entitits?
    Thank you!

  • 2017-12-12 Diego Aguiar

    Hey Vasili Repin

    You can achieve it by using embedded forms or by creating a new model class, which will hold a reference to all related entities. Maybe our tutorial about managing collections can give you some ideas, because in there, we show how to work with embedded forms: https://knpuniversity.com/s...

    Cheers!

  • 2017-12-12 Vasili Repin

    Hello!
    I have a problem and I can't to solve it at this teme :(
    How to do the form with 2-3 related entities for populating data. Not uses sub-forms
    Yes, I searched all sources but don't find a good solution. Maybe temporary solution is to make composite class of these Entities. But it's hardcoding. And a solution is made 2-3 forms =)...
    I need 1 form with 2-3 related entities ( Description, Image is many-to-one to Product) in one form. I need for first moment to do this without subforms.
    This is a question:
    https://stackoverflow.com/q...

  • 2017-12-11 Victor Bocharsky

    Hey Matija,

    You call invalid method name: call setDefaults() instead of setDefault() - note "s" in the end :)

    Cheers!

  • 2017-12-11 Matija Baskarad

    Hi there.
    Somehow the implementation for *configureOptions()* in *GenusFormType.php* brings out following error:


    Type error: Too few arguments to function Symfony\Component\OptionsResolver\OptionsResolver::setDefault(), 1 passed in ~/aqua_note/src/AppBundle/Form/GenusFormType.php on line 25 and exactly 2 expected.

    Here is my GenusFormType.php:


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

    My Symfony version is 3.3, is it because of that?

  • 2017-12-07 Diego Aguiar

    Yes!
    And you can use that same technique for when editing entities

  • 2017-12-07 Patrick Vale

    Hey Diego Aguiar

    Thanks - that works great, and lets me set some other things on that object first too, which is a bonus!

  • 2017-12-07 Diego Aguiar

    Hey Patrick Vale

    As you discovered there are many ways to set default values to a form, but in your case, I would go with the option of passing an object to the form when creating it in the controller, something like this:


    // some controller.php

    public function someAction(Request $request) {
    $mailshot = new Mailshot();
    // the second option is for setting default data to the form, if it's bounded to an entity, you should pass an instance of that entity
    $form = $this->createForm(MailshotFormType::class, $mailshot)
    ....
    }

    Cheers!

  • 2017-12-07 Patrick Vale

    Hi Ryan, thanks for the tutorial!
    I have a question about binding default values to a form. I have a form which is bound via

    data_class

    to an Entity. One of the properties of this Entity ('held', boolean) is set to true:

        
    /**
    * @ORM\Column(type="boolean")
    */
    private $held = true;


    What I would like to achieve is that when I create a new instance of this form, bound to this entity, the held checkbox should be ticked by default.
    Looking at the docs, I saw there were various ways to override the default data passed into a form, and that it's possible to supply default data by using the

    data

    option when setting the form's resolver defaults


    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults([
    'data_class' => Mailshot::class,
    'data' => new Mailshot(),
    ]);
    }


    Doing this, it does set the default option correctly. However, I'm not sure if I should need to do this? Reading the docs, I wasn't sure if the form ought to have the default value set by just binding the data_class to an entity.
    I'm a bit confused!

    Thanks again,
    Patrick

  • 2017-11-27 Victor Bocharsky

    Hey Vasili,

    From the error I see Symfony can't find proper methods. Please, make sure you do "$this->add('descriptions', ...)" in ProductForm, and this form's data_class is set to "Product::class", not "Description::class". Because from error I see that Symfony trying to find those methods in "RevprodBundle\Entity\Description" entity which is incorrect if I understand you right. And the error makes sense because Description entity does not have getDescriptions() method, right? :)

    Cheers!

  • 2017-11-24 Vasili Repin

    Thank for response! I saw links as you give...
    Yes, in Entity: Description and Image are related to Product with many-to-one. I need 1 form with clear fields for filling this with my data from ...admin/new. This is 3 entities with many fields (producr->name, product->price, description->smallDescription, image->linkBigImage...and others) in one form. And I need populating this form manually.


    $this
    ->add('descriptions', CollectionType::class, ['entry_type' =>DescriptionFormType::class]

    where DescriptionFormType have...


    $builder
    ->add('smallDesc', TextType::class, ['data_class' => Description::class])
    ->add('bigDesc', TextareaType::class ,['data_class' => Description::class])

    doesn't work with next error:


    -Neither the property "descriptions" nor one of the methods "getDescriptions()", "descriptions()", "isDescriptions()", "hasDescriptions()", "__get()" exist and have public access in class "RevprodBundle\Entity\Description".

    But all getters and setters is in Entity...

  • 2017-11-24 Victor Bocharsky

    Hey Vasili,

    I think I got what you mean, since your entities are related to each other like one-to-many, it's easy to do with sub-forms. You need to create a form for each entity you want to see in your final form. For example, you have 3 entities: Product, Description, and Image. So as I understand, Description and Image relate to Product like many-to-one, i.e. Product can have many Description and Image objects. Then you need to create forms for those entities, i.e. ProductForm, DescriptionForm, and ImageForm. So if your ProductForm is your final form, you need to include those 2 sub-forms to it, but using Collection type:


    $productFormBuilder
    ->add('descriptions', CollectionType::class, [
    'entry_type' => DescriptionType::class,
    ])
    ->add('images', CollectionType::class, [
    'entry_type' => ImageType::class,
    ])
    // ... other fields you need to see in ProductForm
    ;

    Then you'll be able to do just:
    $form = $this->createForm(ProductForm::class, $product);

    And all the sub-forms also will be populated with data. Anyway, I hope you got the idea, CollectionType is something you need, see https://symfony.com/doc/cur...

    Also, see our screencast about it for an example: https://knpuniversity.com/s... . And in general, this course has a lot of interesting information about working with collections: https://knpuniversity.com/s...

    Cheers!

  • 2017-11-24 Vasili Repin

    Super tutorials! Great thank you!
    And I have a question...
    I have a 2-3 Entities (tables in Database) and those entities are related one-to-many. I need 1 form (for ../admin/new) for populating data to database. I need 2-3 entities in one form. How can I do this? What is the best way?
    I used this code it's works:
    $product->setPrice($form->get('price')->getData());
    $product->setModel($form->get('model')->getData());
    $product->setDateCreated($form->get('dateCreated')->getData());
    $description->setSmallDesc($form->get('smallDesc')->getData());
    $description->setBigDesc($form->get('bigDesc')->getData());

    But in $form = $this->createForm(DescriptionFormType::class, $product); i need more objects...for next work of code.
    but it's not allowing to do like this:
    $description = new Description;
    $image = new Image;
    $form = $this->createForm(DescriptionFormType::class, $product, $description, $image);

    And I need to do this without any bundles.
    Plese tell me what I need to do? Thank you. Sorry for bad English.

  • 2017-09-05 Diego Aguiar

    Yeah, it's strange, maybe the cache is messing up something? or, I'm not sure about this but you may need to have a setter for that field "setSubFamily()"

  • 2017-09-05 Mohammad Althayabeh

    Thanks @DieDiego Aguiar
    The following actually solved the issue, But I wonder why would I need to do that and it worked without specifying the class in the video.


    $builder->add('subFamily', EntityType::class, array(
    'class' => SubFamily::class,
    ...
    ));
  • 2017-09-05 Diego Aguiar

    Oh, that error is my fault, you need to specify which class is related to.


    $builder->add('subFamily', EntityType::class, array(
    'class' => SubFamily::class,
    ...
    ));


    There are other useful options you can define, if you want to know more, you can read this section of the documentation http://symfony.com/doc/curr...

    Cheers!

  • 2017-09-05 Mohammad Althayabeh

    I did remove the the whole database and recreated it. I have not changed anything about Doctrine settings. I wonder if this is related to the version of Symfony, since I am using the 3.3

    if I add

    $builder->add('name')
    ->add('subFamily', EntityType::class)

    then I get error The required option "class" is missing. on the mentioned line of the following method

     
    public function newAction(Request $resquest)
    {
    $form = $this->createForm(GenusFormType::class); <--
    ...
    }
  • 2017-09-05 Diego Aguiar

    This is weird indeed, makes me wonder a few fundamental things

    Did you update the DataBase schema ? $ php bin/console doctrine:schema:update --force
    Did you change anything about how Doctrine reads Entity metadata?
    What happen if you define the second argument as EntityType?


    $builder->add('name')
    ->add('subFamily', EntityType::class)
  • 2017-09-05 Mohammad Althayabeh

    Yes I did. Below the class code of the formType


    class GenusFormType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder->add('name')
    ->add('subFamily')
    ->add('speciesCount')
    ->add('funFact')
    ->add('isPublished')
    ->add('firstDiscoveredAt');
    }

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

    }

    public function getBlockPrefix()
    {
    return 'app_bundle_genus_form_type';
    }
    }
  • 2017-09-05 Diego Aguiar

    Hey Mohammad Althayabeh

    Did you bind your Form to an entity class? Something like this:


    // YourFormType class
    ...

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

    If you did, could you show me the code of you Form, please? So, I can help you debugging

    Cheers!

  • 2017-09-05 Mohammad Althayabeh

    Hi Victor,
    No, I have only $builder->add('name')
    ->add('subFamily')
    ->add('speciesCount')
    ...

    and the ORM looks like this:

    /**
    * @ORM\ManyToOne(targetEntity="AppBundle\Entity\SubFamily")
    * @ORM\Column(nullable=false)
    */
    private $subFamily;

    Thanks

  • 2017-09-05 Victor Bocharsky

    Hey Mohammad,

    Thanks! Hm, that's weird. Did you specify any type as the second argument for subFamily field ? Or, do you have only:

    $builder
    ->add('subFamily')
    // other fields...

    And how does your Genus::$subFamily ORM definition looks like?

    Cheers!

  • 2017-09-05 Mohammad Althayabeh

    Amazing tutorial!

    For some reason subFamily field does not render as select type, instead it is normal text field. Am I missing something?

  • 2017-08-18 Fran

    Yes, thank you for your help!

  • 2017-08-17 weaverryan

    Hmmm, it's very weird. The error is actually coming from the UniqueEntity validator that the PUGMultiUserBundle provides. And it's quite confusing. Above your entity, you have @UniqueEntity(fields={"email"}...). But, looking at the validator, for some reason, it looks like it expects fields to be a string only. Look, it's clearly concatenating your "fields" like a string: https://github.com/PUGX/PUG.... I don't know this bundle, but this looks like a possible bug to me!

    Try this: just do @UniqueEntity(fields="email"...). So, just make fields be a string. I think this is allowed, and it may fix the issue.

    Cheers!

  • 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!!!