symfony : get object Forms - php

how I can get the object attributes that i use to build this form :
controller code
$assistance1 = new Assistance();
$assistance1->setEtudiant($etudiant1);
$form = $this->get('form.factory')->create(new AssistanceType(), $assistance1);
buildform function on the Form class
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('present', 'checkbox',array(
'required' => false,
))
;
}

You could add $this->assistance1; to AssistenceType and do new AssistenceType($assistence1); But this is not good design imo.
class AssistenceType extends AbstractType
{
private $assistence1;
public function __construct($assistence1)
{
$this->assistence1 = $assistence1;
}
public function buidlForm(FormBuilderInterface $builder, array $options)
{
$builder->add('present', 'checkbox', array(
'required' => false,
'label' => $this->assistence1->getEtudiant()
));
}
}
and use it like this
$form = $this->get('form.factory')->create(new AssistanceType($assistence1), $assistance1);

You should keep in mind that building the form is decoupled from it's data. If you need the form to change based on it's properties, you will need to implement different kinds of event listeners or subscribers. Think of it like this: you only need one builder for many forms.
To implement a listener or subscriber on the form, you will most probably be off easiest to first declare your form type as a service, then register the service you will need to alter the form's behaviour, and tag it as an event listener for the form. This is all in the docs :)
However, the Form component of Symfony tends to be one of the most complex, so don't hesitate to clarify your problem a bit more so I can assist you more effectively.

Related

Symfony 3 Changing a Submit label from a Form class inside of controller

So I have a form that is created in it's own class:
<?php
// src/AppBundle/Form/Type/CoffeeShopForm.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
...
class CoffeeShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
...
->add('save', SubmitType::class, array('label' => 'Create a coffee shop'))
->getForm();
;
}
}
And then I am using it in one controller
/**
* #Route("/admin/edit/{coffee_shop_id}")
*/
public function editCoffeeShopAction(Request $request, $coffee_shop_id)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:CoffeeShop');
$coffeeshop = $repository->find($coffee_shop_id);
$form = $this->createForm(CoffeeShopType::class, $coffeeshop);
$form->get('name')->setData('New name value');
$form->handleRequest($request);
return $this->render('AppBundle:CoffeeController:edit_coffee_shop.html.twig', array(
'form' => $form->createView(),
));
}
So in the controller as you may see I am able to change the value of the name field with $form->get('name')->setData('New name value');
My question is how may I change the label of the SubmitType field - I searched for the documentation of this thing but I can't find it, and it is really helpful if I could reuse this form since I am using it for the add form and then for the edit form and basically it is possible in Zend Framework, so I think it should be possible also in Symfony
I have stumbled across the same question, and after extensive debugging, I can say with some confidence that what you want to do is not possible, at least in Symfony 2.8 .
However, what you can do is setting another label when rendering the form:
{{ form_row(form.name, { 'label': 'Test 123'}) }}
See http://symfony.com/doc/current/reference/forms/types/text.html#label for details.

Adding choices dynamically using EventSubscriber to Symfony2 form

I am trying to set the choices for a form select dynamically since the choices come from a service call. However, when the form renders in the view, the choices are not there.
I'm doing the following in the FormType
<?php
namespace My\Form\Customer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ItemReturnRequestForm extends AbstractType
{
/**
* #var EventSubscriberInterface
*/
protected $reasonsSubscriber;
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'item_return_request';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('reason', 'choice', [
'label' => 'order.returns.reason_for_return',
'required' => true,
'multiple' => false,
'expanded' => false,
'placeholder' => 'order.returns.reasons.empty',
'empty_data' => null,
]);
$builder->addEventSubscriber($this->reasonsSubscriber);
}
/**
* #param EventSubscriberInterface $reasonsSubscriber
*/
public function setReasonsSubscriber(EventSubscriberInterface $reasonsSubscriber)
{
$this->reasonsSubscriber = $reasonsSubscriber;
}
}
The FormType has a service definition which injects the EventSubscriber instance since that is also a service definition with it's own dependencies.
The EventSubscrbier looks like
<?php
namespace My\Form\EventSubscriber;
use My\Customer\ItemReturnAware;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ReturnReasonEventSubscriber implements EventSubscriberInterface
{
use ItemReturnAware;
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SET_DATA => 'getReturnReasons',
];
}
public function getReturnReasons(FormEvent $event)
{
$form = $event->getForm();
if ($form->has('reason')) {
$options = $form->get('reason')->getConfig()->getOptions();
$options['choices'] = $this->itemReturnService->getReasons();
$form->add('reason', 'choice', $options);
}
}
}
Everything seems to work fine up until this point. Using XDEBUG I can see that the EventSubscriber is being triggered. The service call sets $option['choices'] to the expected array value & the field is added successfully.
However, when the form gets rendered. it's as if the EventSubscriber had never been called.
If it makes a difference, the options array is an un-ordered numeric list.
i.e.
$options = [
10 => 'First choice',
15 => 'Second choice',
20 => 'Third choice',
];
Any ideas?
This is an ancient question, but today I found it on the top results searching for event listener to modify form choices.
In my context I have an entity programmatically created, and I redirect the user to the editAction to finish filling the fields.
I have one choice that I can apply only in this particular case, I don't want to allow my user to use it outside it.
That's why I user the POST_SET_DATA event, because I already have an entity with populated fields.
This event listener is set in the formType, inside the
public function buildForm(FormBuilderInterface $builder, array $options)
{
Here a working solution for symfony 3.4:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
// get the form from the event
$form = $event->getForm();
if ('myParticularMode' == $form->get('mode')->getData()) {
// get the field options
$options = $form->get('mode')->getConfig()->getOptions();
// add the mode to the choices array
$options['choices']['MY_PARTICULAR_MODE'] = 'myParticularMode_display_name';
$form->add('mode', ChoiceType::class, $options);
}
});
If you want to replace the choices, you can remove this:
$options = $form->get('mode')->getConfig()->getOptions();
and set a new array for choices.

symfony2 - adding choices from database

I am looking to populate a choice box in symfony2 with values from a custom query. I have tried to simplify as much as possible.
Controller
class PageController extends Controller
{
public function indexAction()
{
$fields = $this->get('fields');
$countries = $fields->getCountries(); // returns a array of countries e.g. array('UK', 'France', 'etc')
$routeSetup = new RouteSetup(); // this is the entity
$routeSetup->setCountries($countries); // sets the array of countries
$chooseRouteForm = $this->createForm(new ChooseRouteForm(), $routeSetup);
return $this->render('ExampleBundle:Page:index.html.twig', array(
'form' => $chooseRouteForm->createView()
));
}
}
ChooseRouteForm
class ChooseRouteForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// errors... ideally I want this to fetch the items from the $routeSetup object
$builder->add('countries', 'choice', array(
'choices' => $this->routeSetup->getCountries()
));
}
public function getName()
{
return 'choose_route';
}
}
You could pass the choices to your form using..
$chooseRouteForm = $this->createForm(new ChooseRouteForm($routeSetup), $routeSetup);
Then in your form..
private $countries;
public function __construct(RouteSetup $routeSetup)
{
$this->countries = $routeSetup->getCountries();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('countries', 'choice', array(
'choices' => $this->countries,
));
}
Updated (and improved) for 2.8+
Firstly you don't really need to pass in the countries as part of the route object unless they are going to be stored in the DB.
If storing the available countries in the DB then you can use an event listener. If not (or if you don't want to use a listener) you can add the countries in the options area.
Using Options
In the controller..
$chooseRouteForm = $this->createForm(
ChooseRouteForm::class,
// Or the full class name if using < php 5.5
$routeSetup,
array('countries' => $fields->getCountries())
);
And in your form..
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('countries', 'choice', array(
'choices' => $options['countries'],
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('countries', null)
->setRequired('countries')
->setAllowedTypes('countries', array('array'))
;
}
Using A Listener (If the countries array is available in the model)
In the controller..
$chooseRouteForm = $this->createForm(
ChooseRouteForm::class,
// Or the full class name if using < php 5.5
$routeSetup
);
And in your form..
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$form = $event->getForm();
/** #var RouteSetup $routeSetup */
$routeSetup = $event->getData();
if (null === $routeSetup) {
throw new \Exception('RouteSetup must be injected into form');
}
$form
->add('countries', 'choice', array(
'choices' => $routeSetup->getCountries(),
))
;
})
;
}
I can't comment or downvote yet, so I'll just reply to Qoop's answer here:
What you proposed will work unless you start using your form type class as a service.
You should generally avoid adding data to your form type object through constructor.
Think of form type class like a Class - it's a kind of description of your form. When you make an instance of form (by building it) you get the Object of form that is build by the description in form type and then filled with data.
Take a look at this: http://www.youtube.com/watch?v=JAX13g5orwo - this situation is described around 31 minute of the presentaion.
You should use form event FormEvents::PRE_SET_DATA and manipulate fields when the form is injected with data.
See: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#customizing-your-form-based-on-the-underlying-data
I got it working by calling getData on the builder
FormBuilderInterface $builder
// Controller
$myCountries = $this->myRepository->all(['continent' => 'Africa']);
$form = $this->createForm(CountriesType::class, $myCountries);
//FormType
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('pages', ChoiceType::class, [
'choices' => $builder->getData()
])
;
}

creating types on the fly for collection type

Is it possible to create FormType on the fly and avoid manually creating new form type class.
Currently I have to do this:
MyController.php
$builder = $this->createBuilder(new ParentEntity());
$builder
->add('parentfield1')
->add('parentfield2');
->add('children', 'collection', array('type' => new ChildType());
ChildType.php
class ChildType extends \Symfony\Component\Form\AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('childname1')
->add('childname2');
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'ChildEntity',
);
}
public function getName() {
return 'childentity';
}
}
I would like to avoid creating ChildType class manually and do something like this instead:
MyController.php
$childBuilder = $this->createBuilder(new ChildEntity());
$childBuilder
->add('childfield1')
->add('childfield2');
// how to create FormType from builder???
$childType = $childBuilder->??????;
$builder = $this->createBuilder(new ParentEntity());
$builder
->add('parentfield1')
->add('parentfield2');
->add('children', 'collection', array('type' => $childType);
I tried to get the type with:
$childBuilder->->getFormConfig()->getType();
but that did not work. I get error:
The form's view data is expected to be an instance of class Namespace\Bundle\Entity\ChildEntity, but is an instance of class Doctrine\Common\Collections\ArrayCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\Common\Collections\ArrayCollection to an instance of Namespace\Bundle\Entity\ChildEntity.
Currently the best I came up with is to create custom FormType that accepts array of fields as constructor but is there a better way?

Symfony2 form renders one field with same name

I have settings that I store in my database. There is an entity class for these settings. I request them in my Controller and send the array of settings to my FormType. But in the buildForm() I can only render one setting. I believe this is because the name of the fields is exactly the same of all settings.
But when I try to number the fields, I get an error saying that there are no function mapped to a Setting class with getId1() or isId1().
Here is the code snippet:
protected $allSettings;
public function __construct(array $allSettings)
{
$this->allSettings = $allSettings;
}
public function buildForm(FormBuilder $builder, array $options)
{
foreach($this->allSettings as $setting) {
$id = $setting->getId();
$builder->add('id'.$id, 'hidden')->setData($setting);
}
}
Did you try to use the collection type?
Retrieve your array of settings in your controller then, create a collection form like this:
$form = $this->createForm('collection', $yourArray, array(
'type' => 'your settings_type'
));
'your_settings_type' should be the name of your FormType used to edit a settings entity.
Maybe quelque chose de ce genre:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('key', 'text');
$builder->add('value', 'text');
}
Of course, in your case, it's probably not key & value.
Just remember one thing: don't use ids. You don't need. FormComponent is an object oriented form framework. Let it do it's job with objects. :)

Categories