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

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'];
//...
}

Related

Symfony 5 How to create a form using paramconverter as value for one of the field

I made a form that works with two tables joined (OneToMany, a strategy can have many strenghts), but I could only make it work with 'choice_label' for the 'strategy' field, as shown below.
Instead of the 'choice_label' which gives the user a list of strategies to choose from by titles, I want my code to retrieve the value of the paramconverter {title} used in my controller functions to identify which strategy is wanted, so that when the form is filled and sent, it automatically sets the strenght to the right strategy.
I looked into the many FormType Field of the documentation, and tried option like 'data' instead of 'choice_label' but couldn't make it work. Thank you for your help !
My FormType file :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('strenght')
->add('strategy', EntityType::class,[
'class' => Strategy::class,
'choice_label' => 'title'
])
;
}
My Controller file :
public function createUpdate(DiagnosticForce $diagnosticforce = null, Request $request, EntityManagerInterface $em)
{
if(!$diagnosticforce){
$diagnosticforce = new DiagnosticForce();
}
$form = $this->createForm(DiagnosticForceType::class, $diagnosticforce);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($diagnosticforce);
$em->flush();
return $this->redirectToRoute("frContentStrategy");
}
return $this->render('content_strategy/createForce.html.twig', [
"diagnosticforce" => $diagnosticforce,
"form" => $form->createView()
]);
}
The answer is : use the array $options inside your FormType to get the data you need, by sending it from your controller as the third argument of your createForm function, here in my controller : ['title' => $id] ; and then in your FormType you will be able to get that data just before $builder, and use it in your ->add (in this example, as the value for 'choices')
Here are my files and the solution :
FormType file :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->title = $options['title'];
$builder
->add('strenght')
->add('strategy', EntityType::class,[
'class' => Strategy::class,
'choices' => [$this->title]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => DiagnosticForce::class,
'title' => null
]);
}
Controller File :
public function createUpdate(Strategy $id, DiagnosticForce $diagnosticforce = null, Request $request, EntityManagerInterface $em)
{
if(!$diagnosticforce){
$diagnosticforce = new DiagnosticForce();
}
$form = $this->createForm(DiagnosticForceType::class, $diagnosticforce, [
'title' => $id
]);

Access form data dynamically in symfony 4

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();
...

Simple form in symfony2

I have a problem with a simple form. I have a class called JurisdictionUser() and my class form is JurisdictionUserNewType().
If I put the next code, its work fine:
$jurisdiction_user = new JurisdictionUser();
$form = $this->createFormBuilder($jurisdiction_user)
->add('name', 'text')
->add('email', 'text', array('required' => false))
->add('save', 'submit')
->getForm();
but, if I put the next code:
$jurisdiction_user = new JurisdictionUser();
$form = $this->createFormBuilder(new JurisdictionUserNewType(), $jurisdiction_user);
It give me the next error:
ContextErrorException: Catchable Fatal Error: Argument 2 passed to Symfony\Bundle\FrameworkBundle\Controller\Controller::createFormBuilder() must be of the type array, object given, called in /home/sierra/dev_env/iyc-open010/src/Radmas/BackofficeAdminBundle/Controller/UserAdminController.php on line 119 and defined in /home/sierra/dev_env/iyc-open010/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php line 175
I need help xD. My class JurisdictionUserNewType().
<?php
/**
* Created by PhpStorm.
* User: diego
* Date: 15/01/14
* Time: 16:21
*/
namespace Radmas\BackofficeAdminBundle\Form\Type;
use Radmas\BackofficeAdminBundle\Form\Transformer\CapitalToSmallLetterTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class JurisdictionUserNewType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new CapitalToSmallLetterTransformer();
$builder
->add('name', 'text');
$builder->add(
$builder->create('email', 'text', array('required' => false))
->addModelTransformer($transformer)
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Radmas\Open010Bundle\Document\JurisdictionUser'
));
}
public function getName()
{
return 'jurisdictionUserNew';
}
}
You should use
$this->createForm(new JurisdictionUserNewType(), $jurisdiction_user)
in controller, instead of createFormBuilder.

Reusable form choices in symfony2 forms

I have the following code that's working... but I think it can be done better.
(Description below).
Class Address
{
protected province;
public function getProvince()...
}
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$build->add(Province, new ProvinceType());
...
}
}
class ProvinceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$provinceList = array(... //very long list
...
$build->add(Province, 'choice', array(
'choices' => $provinceList;
));
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\Bundle\Entity\Address'
));
}
}
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$Province = array(... //very long list
...
$build->add('Address', new AddressType());
$build->add('FromProvince', new AddressType());
}
}
I have two problems with the code above:
Using this in twig PersonType form I have to do form_widget(person.Address.ProvinceType.ProvinceType) to use it. This just looks so wrong.
To validate the province I have to go one method deeper than I should have to.
In the end I just want to be able to validate fields that make sense such as:
Acme\Bundle\Entity\Person:
property:
provinceBorn:
- NotBlank: ~ //Wish to reuse province list here for straight-forward validation.
Address:
Valid: ~
Acme\Bundle\Entity\Address:
property:
province:
- NotBlank: ~ //As well as here.
To shorten the path to your ProvinceType, you should maybe define it as a base widget that would extend Symfony's choice type (see the doc on this). The best you'd get here would be something like {{ form_widget(person.address.province) }}.
To make choices reusable, it would be smart to extract your ProvinceType into a service (see Symfony's doc on how to do this) and pass the list of provinces as a parameter into the ProvinceType's __construct method (that would be defined in your bundle's services.yml). That way you would be able to extract your provinces into an external storage.
On validation, keep in mind that the YAML you've supplied here has mostly nothing to do with Form component, it's about your entity. So, duplicating NotBlank constraints actually makes sense, because you're not linking Person's provinceBorn property to an Address entity, you're saving a separate field.
Though, if you define a custom field type, you can make it required by default by moving the constraint into the Type you extracted to a service. Such constraint can be defined like this:
<?php
class ProvinceChoiceType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'constraints' => [
new NotBlank(['message' => 'Title is required']),
],
]);
}
...
I have done the something similar in this way (I'll use your example):
Class Address
{
protected province;
public function getProvince()...
}
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$provinceList = array(... //very long list
...
$build->add('province', 'choice', array(
'choices' => $provinceList, 'empty_value' => null, 'required' => true,
));
...
}
}
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$Province = array(... //very long list
...
$build->add('address', new AddressType());
$build->add('fromProvince', new AddressType());
}
}
And you send the data:
...
$form = $this->createForm(new PersonType(), $entity);
return array(
'form' => $editForm->createView(),
);
And use this in Twig as below:
{{ form_widget(form.address.province) }}
Finally, I think your validation are correct, but if you need something more specific, you could use the getters method as in the Symfony documentation is specified, in the validation section http://symfony.com/doc/current/book/validation.html#getters
Extended answer from before with explanations.
Inheriting form
http://symfony.com/doc/current/cookbook/form/inherit_data_option.html
Code
With your code it should look something like this
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$build->add(Province, new ProvinceType(), [
'inherit_data' => true
// this is an alterrnative for having it bellow inside configure options
// use one or another, what suit you better
]);
...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'inherit_data' => true
]);
}
// in sf > 2.7 use public function configureOptions(OptionsResolver $resolver)
}
class ProvinceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$provinceList = array(... //very long list
...
$build->add(Province, 'choice', array(
'choices' => $provinceList;
));
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\Bundle\Entity\Address',
'inherit_data' => true
));
}
}
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$Province = array(... //very long list
...
$build->add('Address', new AddressType());
$build->add('FromProvince', new AddressType());
}
}
This you addressType inherits fields from provinceType, and PersonType inherits from both addressType (including its inherited fields from provinceType).
Template
So it should be possible to do this inside template
{{ form_row(form.Province)}}
Validation
The best way would be to so validation constrains on your relations with the Valid constrain.
This will force the validations on the children also
http://symfony.com/doc/current/reference/constraints/Valid.html
The other option is setting cascade_validation on the form, but this wont forward your validation groups if any.
Either way, you would then need define validation only on each entity once.

symfony2 using validation groups in form

I have a Entity with a property:
/**
* #var string $name
*
* #Assert\NotBlank(groups={"foobar"})
* #ORM\Column(name="name", type="string", length=225, nullable=false)
*/
private $name;
The Form:
class MyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => '...',
'validation_group' => array('foobar'),
);
}
public function getName()
{
...
}
}
In the Controller I bind the Request and call $form->isValid()
But how to define the validation_group?
From inside your FormType class you can define the validation groups associated to that type by setting your default options:
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('group1', 'group2'),
);
}
I had exactly the same problem. I solved it that way ...
// Entity
$employee = new Employee();
// Form creation
$form = $this->createForm(new EmployeeForm(), $employee, array('validation_groups'=>'registration'));
I hope that helps!
When building the form in the controller, add a 'validation_groups' item to the options array:
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('foobar'),
))->add(...)
;
It is described in the forms page of the symfony2 book: http://symfony.com/doc/current/book/forms.html#validation-groups
For me, on symfony 2.1, i solved it by adding 'Default' in validation_groups like:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('Default', 'registration')
));
}
I made a little blog post related to this problem: http://marcjuch.li/blog/2013/04/21/how-to-use-validation-groups-in-symfony/
In this post I’m going to show how to use validation groups in symfony with the example of an order form which should offer the possibility to use separate billing and shipping adresses. This includes 3 steps:
Group validation contstraints for the shipping related form fields together
Determine which validation contraints are applied, depending on the
checkbox value in the submitted form
Copy data from non-shipping fields to shipping fields if checkbox is
not selected
You can also define validation groups dynamically:
// MyCustomType.php
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return array('person');
}
return array('company');
},
));
}