Access form data dynamically in symfony 4 - forms

I have created following simple form in symfony 4.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('email', EmailType::class)
->add('message',TextareaType::class)
;
}
I access these data from a controller method like following:
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
$formdata = $form->getData();
$concatenatedData = implode (",", array("Name: ".$formdata['name'], "E-mail: ".$formdata['email'], "Message: ".$formdata['message']));
Now i need to add more fields in the form. In that case, I need to loop through the $formdata and get those values in $concatenatedData. I am new in symfony. Can anyone give me any hints how to do that dynamically in symfony 4 ?
Many thanks in advance.

create a class that you need after defining in form class
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('price');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Content::class,
));
}
}
Follow
$content = new Content();
$form = $this->createForm(ContactType::class, $content);
$form->handleRequest($request);
$formdata = $form->getData();
$content->getName();
$content->getOther();
...

Related

Symfony3: How to translate FormErrors added in eventListeners?

Let say I have this form:
//...
class BananaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('flavor');
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
if ($form->get('flavor')->getData() === null) {
$form->addError(new FormError('form.error.flavor_missing'));
}
}
}
}
Even though the message form.error.flavor_missing is defined both in messages.yml and validators.yml, it's not displayed.
Any idea how to translate it? I hope I won't have to inject the translator service in every form using this kind of code in order to solve this.
why not use Validation Constraints
use Symfony\Component\Validator\Constraints as Assert;
// ...
class BananaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('flavor', YourType::class, array(
'constraints' => array(
new Assert\NotNull(), // or any other
),
));
}
}
You can use a custom message
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('flavor', YourType::class, array(
'constraints' => array(
new Assert\NotNull(array(
'message' => 'form.error.flavor_missing'
)),
),
));
}
If you open your dev environment, you should see the missing string. Taking a look at them, you should see the domain and the expected messages file.

symfony,when the form does not have this field

class FormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('a')
->add('b')
->add('c')
->add('d');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' =>'entity'
));
}
}
html form field:
<form>
<input name="a" value="a">
<input name="b" value="b">
</form
When I submit the form, the c,d is set to null.
When the form does not have this field, I do not want to update c and d :)
insted of this
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('a')
->add('b')
->add('c')
->add('d');
}
use this
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('a')
->add('b')
->add('c', TextType::class , array('mapped' => false))
->add('d', TextType::class , array('mapped' => false));
}
Update your buildForm function like this. In The Symfony FormType, by default all fields are required.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('a')
->add('b')
->add('c',TextType::class, ['required' => false,'empty_data' => null])
->add('d',TextType::class, ['required' => false,'empty_data' => null] );
}
If you want to persist the entity in database c and d must be nullable as well

Symfony - How to call the Service Container object from form events?

How to call the Service Container object from form events?
I created a form where the webmaster can create books. I want to create a Sylius Product when an Book is created through this form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'attr' => array('class'=>'block1')
))
->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'onPreSetData')
)
;
}
public function onPreSetData(FormEvent $event)
{
$book = $event->getData();
$form = $event->getForm();
$productFactory = $this->container->get('sylius.factory.product');
}
For that purpose, I need to reach 'sylius.factory.product' service as explained there: http://docs.sylius.org/en/latest/book/products/products.html
$productFactory = $this->container->get('sylius.factory.product');
I can access it from any Controller, but unfortunately I can't access it from the BookType class I defined.
Here is the error returned by Symfony when I try to access it from the buildForm() or onPreSetData() function:
Notice: Undefined property: FrontendBundle\Form\BookType::$container
Pass the service, to the form, via the options
Actually this process is very well explained in Symfony Documentation: http://symfony.com/doc/current/form/form_dependencies.html
So I updated the way I was calling my form like that:
$editForm = $this->createForm($form, $item, array( //WITH SYLIUS
'sylius_factory_product' => $this->get('sylius.factory.product')
));
Instead:
$editForm = $this->createForm($form, $item); //WIHOUT SYLIUS
Plus, I adapted the configureOptions function like that:
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'FrontendBundle\Entity\Book'
))
->setRequired('sylius_factory_product')
;
}
Instead:
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'FrontendBundle\Entity\Book'
))
;
}
You can now access your service in the buildForm function like so:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$productFactory = $options['sylius_factory_product'];
//...
}

Select or create an entity

I usually find answers to solve my problems after some research, but that is not the case today.
Let's suppose I have 2 entities, "Task" and "TaskCategory", each task has one category.
I want to let my users not only attribute an existing category to their tasks but also create new ones.
Here's the code I have so far:
<?php
// TaskCategoryType.php
class TaskCategoryType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
;
}
// Some stuff...
and
<?php
// TaskType.php
// Some stuff...
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Add some fields there
->add(
'category',
TransformableEntityType::class,
['class' => 'AppBundle:TaskCategory']
)
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$task = $event->getData();
$form = $event->getForm();
if (empty($task['category']['id']) && !empty($task['category']['name'])) {
$task['category']['id'] = null;
$event->setData($task);
$form->remove('category');
$form->add('category', TaskCategoryType::class);
}
})
;
}
// Some stuff...
It works fine for task creation, but when I edit an existing task, it edit the associated TaskCategory instead of creating a new one.
I was trying to force the creation of a new TaskCategory with : $task['category']['id'] = null; but it does not work.
Thanks for your help I'm really stuck :(
EDIT: I forgot to mention that I'm using this form only as an API that's why I have only one 'category' field, otherwise I would have used another 'category_new' field.
Well it seems I finally found something working, it's not pretty, I'm not pretty happy with how it's done but at the moment I have not found any alternative.
If you have a cleaner solution I'll be happy to learn.
<?php
class TaskType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Some fields...
->add(
'category',
TransformableEntityType::class,
['class' => 'AppBundle:TaskCategory']
)
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$task = $event->getData();
$form = $event->getForm();
if (empty($task['category']['id']) && !empty($task['category']['name'])) {
// If there is no category id but there is a category
// name it means it is a new category. so replace
// the custom entity field with this simple field
$form->remove('category');
$form->add('category', TaskCategoryType::class, ['allow_extra_fields' => true]);
}
})
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
$catForm = $event->getForm()->get('category');
if ($catForm->has('name') && $catForm->get('name')->getData() !== "") {
// If there is a category name it means
// it is a new category.
$category = new TaskCategory(); // Create new category
$category->setName($catForm->get('name')->getData());
$task = $event->getData()->setCategory($category);
$event->setData($task);
}
})
;
}
Considering you have cascade-persist enabled for your association from Task to TaskCategory. I guess the best work around will be, to use an autocompleter which should work as tag based.
The Transformer should check with database if given TaskCategory is available, else create a new one.

cannot get $this->generateUrl() work from inside AbstractType class

I cannot get $this->generateUrl() work, but it's work from my controller, or should I define 'setAction' using another way ?
class UserLoginType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('POST')
->setAction($this->generateUrl('fos_user_security_check'))
->add('username', 'text')
->add('password', 'password')
->add('save', 'submit');
}
public function getName()
{
return 'user_login';
}
}
If you are building your form in a separate class and not in your controller, you should pass the action to the form type like this:
// In your controller
$form = $this->createForm(new FormType(), $object, array(
'action' => $this->generateUrl('target_route'),
'method' => 'POST',
));
The AbstractType does not contain any method generateUrl(), that's why you can't set the action in the type directly. You can find details here: http://symfony.com/doc/current/book/forms.html#changing-the-action-and-method-of-a-form
Yes, in the Form class, function buildForm , put this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('POST')
->setAction( $options["data"]["action"])
->add('name', TextType::class, array('label' => 'Nombre'))
;
}