Pass/bind data objects to inner/embedded Symfony2 forms - symfony

i have the following form where i would like to pass some objects to the inner forms in order to populate them with data when being edited:
public function __construct( $em, $id )
{
$this->_em = $em;
}
public function buildForm( \Symfony\Component\Form\FormBuilderInterface $builder, array $options )
{
$builder->add( 'accessInfo', new AccessInfoType( $this->_em, $options[ 'entities' ][ 'user' ] ) , array(
'attr' => array( 'class' => 'input-medium' ),
'required' => false,
'label' => false
)
);
$builder->add( 'profileInfo', new ProfileInfoType( $this->_em, $options[ 'entities' ][ 'profile' ] ) , array(
'required' => false,
'label' => false
)
);
}
public function setDefaultOptions( \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver )
{
$resolver->setDefaults( $this->getDefaultOptions( array() ) );
return $resolver->setDefaults( array( ) );
}
/**
* {#inheritDoc}
*/
public function getDefaultOptions( array $options )
{
$options = parent::getDefaultOptions( $options );
$options[ 'entities' ] = array();
return $options;
}
public function getName()
{
return 'UserType';
}
which i instantiate with the following code:
$form = $this->createForm( new UserType( $em ), null, array( 'entities' => array( 'user' => $userObj, 'profile' => $profileObj ) ) );
Once i get, via the constructor, the object containing the needed data does anyone know how could i bind that object to the form?
class ProfileInfoType extends AbstractType
{
private $_em;
public function __construct( $em, $dataObj )
{
$this->_em = $em;
$this->_dataObj = $dataObj;
}
Thanks in advanced!

I was having the same issue and fixed this with inherit_data
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'inherit_data' => true,
));
}
See also http://symfony.com/doc/current/cookbook/form/inherit_data_option.html

Inside your controller yo should get the request data
$request = $this->getRequest();
or request it through the method parameters
public function newAction(Request $request)
and then bind it to the form
$form->bind($request);
For further details have a look at http://symfony.com/doc/2.1/book/forms.html#handling-form-submissions

this works well add an attr for use the html attribute 'value' depends of the form type, maybe this can help you.
Twig
{{ form_label(blogpostform.title) }}
{{ form_widget(blogpostform.title, {'attr': {'value': titleView }}) }}
{{ form_errors(blogpostform.title) }}

Related

Symfony Sonata Admin: how get choices array from DB

How get all values parent_id from DB?
$category = $this->getSubject();
protected function configureFormFields(FormMapper $formMapper)
{
$fieldOptions = array(); //how get all value `parent_id` from DB
$formMapper->add('parent_id', ChoiceType::class, array(
'expanded' => true,
'multiple' => false,
'choices' => $fieldOptions,
'data' => $category->parent_id
));
}
If "parent" is an entity (Category, perhaps), you might want to look at EntityType. Otherwise, your code needs a lot more context. In what class or file is that snippet located?
Answer:
class CategoryAdmin extends AbstractAdmin
{
$category = $this->getSubject();
protected function configureFormFields(FormMapper $formMapper)
{
$em = $this->modelManager->getEntityManager(Category::class);
$fieldOptions = $em->getRepository(Category::class)->getChoiceParentId();
$formMapper->add('parent_id', ChoiceType::class, array(
'multiple' => false,
'choices' => array_flip($fieldOptions),
'data' => $category->parent_id
));
}
}
class CategoryRepository extends ServiceEntityRepository
{
public function getChoiceParentId()
{
$categories = $this->createQueryBuilder('c')
->select('c.id, c.name')
->getQuery()
->getResult();
$choice_parent_id = [0 => 'Empty'];
foreach ($categories as $category) {
$choice_parent_id[$category['id']] = $category['name'];
}
return $choice_parent_id;
}
}

Symfony Edit and persist multiple entities on one form

I have an entity called Objective and its property called 'weight' and I can edit one objective at a time.. and now i want to edit all weights in a single form and persist them in DB using Doctrine..
This issue helped me in bringing all objective weights on a single form..
Edit multiple entities in one form
Now I am trickling around on how to save all weight values from the form in DB with a single click of save button..
Below is my code so far..
This is the controller action:-
/**
* #Route("/mou/objectives/manage", name="objective_manage")
*/
public function manageAction(Request $request){
$objs = $this->getDoctrine()
->getRepository('AppBundle:Objective')
->findAll();
$form = $this->createFormBuilder()
->add('weight', 'collection', array(
'type' => new WeightType() ,
'allow_add' => false,
'allow_delete' => false,
'label' => false))
->add('save', 'submit')
->getForm();
$form->setData(array('weight' => $objs));
/* Till here things are fine */
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
/* **Here I need help how to persist all weight values.. maybe inside a loop.. hence This is the missing piece** */
foreach($objs as $obj){
$obj =new Objective();
$obj = $em->getRepository('AppBundle:Objective')->find($obj);
$obj->setWeight($obj);
$em->persist($obj);
}
$em->flush();
return $this->redirectToRoute('objective_manage');
}
return $this->render('keyobjective/manage.html.twig', array(
'objs' => $objs,
'form' => $form->createView()
));
}
This is the FormType:-
class WeightType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('weight','text', array('label' => false));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Objective'
//'data_class' => null
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_objective';
}
}
and this is twig template:-
{% extends 'base.html.twig' %}
{% block body %}
{{form_start(form)}}
{{form_label(form.weight,'Enter Weight')}}
{{form_widget(form.weight)}}
{{form_end(form)}}
{% endblock %}
Any suggested approach..!!
Somehow I figured this out.. after form handle request I created a loop to iterate through the number of objectives being edited and then inside of it another foreach loop which transforms the weight values received from the Form to the setWeight() method one by one:-
/**
* #Route("/mou/objectives/manage", name="objective_manage")
*/
public function manageAction(Request $request){
$objs = $this->getDoctrine()
->getRepository('AppBundle:Objective')
->findAll();
$count = count($objs);
for($i =0; $i < $count; $i++){
$objsarray = new Objective();
}
$form = $this->createFormBuilder()
->add('weight', 'collection', array(
'type' => new WeightType() ,
'allow_add' => false,
'allow_delete' => false,
'options' => array('label' => false),
))
->add('save', 'submit')
->getForm();
$form->setData(array('weight' => $objs));
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em = $this->getDoctrine()->getManager();
for($i=0; $i < count($objs); $i++){
$obj = new Objective();
$obj = $em->getRepository('AppBundle:Objective')->find($objs[$i]);
$weight = $form->get('weight')->get($i)->getData();
foreach($weight as $val){
$obj->setWeight($val);
$em->persist($obj);
}
}
$em->flush();
$this->addFlash(
'notice',
'Objective weight updated'
);
return $this->redirectToRoute('objective_manage');
}
return $this->render('keyobjective/manage.html.twig', array(
'objs' => $objs,
'form' => $form->createView()
));
}

Symfony2 Dynamic Form Modification not saving generated data

I'm going crazy because if I choose a client from an entity field, it correctly populate the second entity field called proposals. Then I choose the proposal dynamically generated, but when I save the form it saves the form correctly but without filling the proposal field. I followed the Symfony Tutorial about the Dynamic Forms which can be found here
This is my FormType code:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('client', 'entity', array(
'class' => 'AppBundle\Entity\Client',
'property' => 'name',
'label' => 'Client:',
'empty_value' => '',
'required' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.name', 'ASC');
},
));
$formModifier = function (FormInterface $form, Client $client = null) {
$proposals = null === $client ? array() : $this->em->getRepository('AppBundle:Proposals')->findBy(
array('client'=>$client->getId()),
array('id' => 'DESC'));
$form->add('proposal', 'entity', array(
'class' => 'AppBundle\Entity\Proposal',
'choice_label' => 'subject',
'placeholder' => '',
'choices' => $proposals,
'label' => 'Proposal',
'required' => false
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$client = null;
$data = $event->getData();
if(!empty($data)) {
$client = $data->getClient();
}
$formModifier($event->getForm(), $client );
}
);
$builder->get('client')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$client = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $client);
}
);
This is the Prenotazione Entity, the one who belong the form.
class Prenotazione {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="prenotazioni")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
/**
* #ORM\OneToOne(targetEntity="Proposal", inversedBy="prenotazione")
* #ORM\JoinColumn(name="proposal_id", referencedColumnName="id")
*/
private $proposal;
public function getId() {
return $this->id;
}
public function setProposal(\AppBundle\Entity\Proposal $proposal = null)
{
$this->proposal = $proposal;
return $this;
}
public function getProposal() {
return $this->proposal;
}
public function setClient(\AppBundle\Entity\Client $client = null)
{
$this->client = $client;
return $this;
}
public function getClient()
{
return $this->client;
}
}
Where am I wrong ?
Are you sure your proposals query is correct?
$proposals = null === $client ? array() : $this->em->getRepository('AppBundle:Proposals')->findBy(
array('client'=>$client->getId()),
array('id' => 'DESC'));
Shouldn't this be either array('client_id' => $client->getId()), or array('client' => $client),?
Try checking the actual content of $proposals by adding a dump($proposals) just below and looking up the result in the symfony profiler.

Modifying Symfony Forms html attributes / overriding a single attribute

I am trying to figure out how to modify html attributes on the fly with Symfony2 forms.
The situation is a case where a default placeholder is used most of the time, but occasionally, the developer needs to write a custom message.
My Form type looks like this:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use My\AwesomeBundle\Transformer\ProcedureCodeTransformer;
class ProcedureType extends AbstractType
{
private $em;
private $user;
public function __construct($em, $securityContext)
{
$this->em=$em;
$this->user=$securityContext->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ProcedureTransformer( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
public function getParent() {
return 'hidden';
}
public function getName() {
return 'procedure';
}
}
The default render then has "Select Procedure" for the data-select2-placeholder element and javascript is used to display it. This is then used in a more complex type:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProcedureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('biller', 'billers', array( 'placeholder' => 'New Text'));
[...]
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults( array(
'data_class' => 'My\AwesomeBundle\Entity\Procedure'
) );
}
public function getName(){
return 'my_awesomebundle_proceduretype';
}
}
I would like 'New Text' to be placed into the data-select2-placeholder html attribute. However, if I call the builder like this:
$builder->add('procedure',
new ProcedureCodeType()),
array( 'attr' => array('data-select2-placeholder' => 'New Text') )
);
The entire html attribute array is replaced. this is not surprising. Is there a function in the form builder that has eluded me to add or modify a single html attribute?
Unfortunately, there is no way to modify a single attr the way you would want it. This is the closest and most simple solution I could come with:
What you have to do in your situation is to use callback functions in your options.
For each default attribute in your form types, instead of a normal value, you can use a callback function that accepts a single input representing your options (Symfony class Symfony\Component\OptionsResolver\Options). The function is called when building the form and the return value is then used as the value of the option.
That way, you can build the resulting option depending on the other provided options. An example:
use Symfony\Component\OptionsResolver\Options;
class ProcedureType extends AbstractType
{
...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options) {
$default = array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
);
return array_replace($default, $options['new_attr']);
};
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => $attr,
'new_attr' => array(),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
That way, what you have to modify will be new_attr.
You might want to create a new class that resolves the nested options for you.
<?php
namespace My\AwesomeBundle\Form\Attr;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class ProcedureCodeAttr
{
/**
* #var array
*/
protected $options;
/**
* #param array $options
*/
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
* #param OptionsResolverInterface $resolver
*/
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
));
}
/**
* #return array
*/
public function toArray()
{
return $this->options;
}
/**
* #param array $options
* #return array
*/
public static function resolve(array $options = array())
{
$attr = new static($options);
return $attr->toArray();
}
}
Then in your form type class you'd use it like so
$builder->add('procedure', new ProcedureCodeType()), array(
'attr' => ProcedureCodeAttr::resolve(array(
'data-select2-placeholder' => 'New Text'
))
));
Symfony 4.4 and above now support nested options https://symfony.com/doc/4.4/components/options_resolver.html#nested-options
Instead of defining a nested array, create a callback with OptionsResolver.
For example, a custom form type exists with the following defaults:
class CustomType extends AbstractType {
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('attr', function (OptionsResolver $attrResolver) {
$attrResolver->setDefaults([
'class' => 'form-control-lg',
'placeholder' => 'My Default Placeholder',
'autocomplete' => 'off',
]);
});
$resolver->setDefaults([
'label' => 'Name',
'help' => 'Enter a name',
]);
}
....
}
Attr is now a callback, not an array, now in any forms that use it just defined the attr properties like normal.
class MyFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', CustomType::class, [
'label' => 'My Form',
'help' => 'My Form Help',
'attr' => [
'placeholder' => 'My Form Placeholder',
],
])
....
}
The generated form will have the class: 'form-control-lg' and the label: 'My Form Placeholder'.

how to dynamically set cascade validation of form from controller

My form looks like this :
class CpanelRetailerForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('email', 'email', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('addUser', 'checkbox', array(
'label' => 'Add User account',
'required' => false,
'mapped' => false
))
->add('user',new CpanelUserForm());
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Acme\TestBundle\Entity\Retailer',
//'cascade_validation' => true
));
}
public function getName() {
return 'retailer';
}
}
I want to dynamically set this line from controller depending on whether addUser field is checked or unchecked.
cascade_validation' => true
Here is my controller code:
$form = $this->createForm(new CpanelRetailerForm(), new Retailer());
$form->
if ($this->getRequest()->isMethod('POST')) {
$form->bind($this->getRequest());
if ($form->get('addUser')->getData()) {
// then set the cascade_validation to true here
}
}
How can I do this inside controller?
My attempt :
added this line in my form class:
$builder->addEventListener(
FormEvents::POST_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$addUser = $form->get('addUser')->getData();
$validation = false;
if ($addUser) {
$validation = true;
}
$resolver = new OptionsResolver();
$resolver->setDefaults(array(
'cascade_validation' => $validation
));
$this->setDefaultOptions($resolver);
}
);
This didnot work for me. Although I receive data in $addUser, cascade_validation is not added
How can I do this inside controller?
You can´t! Thats the simple answer. Lets take a look at following simple form class:
class TestType extends AbstractType {
/**
* #var boolean
*/
private $myOption;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->myOption = false;
$builder
->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) {
dump('formEvents::PRE_SET_DATA');
})
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
dump('FormEvents::POST_SET_DATA');
})
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
dump('FormEvents::PRE_SUBMIT');
})
->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) {
dump('FormEvents::SUBMIT');
})
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
dump('formEvents::POST_SUBMIT');
})
->add('name', TextType::class)
->add('send', SubmitType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired(array(
'my_option'
));
$resolver->setDefaults(array(
'my_option' => $this->setMyOption()
));
}
/**
* #return bool
*/
public function setMyOption() {
dump($this->myOption);
return $this->myOption;
}
}
Lets take in how you render and handle a form inside a Controller:
public function formAction(Request $request) {
$form = $this->createForm(TestType::class);
dump('calledCreateForm');
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
dump('finished');
dump($form->getData());
die();
}
return $this->render('#TestPra/Test/test_form.html.twig', array(
'form' => $form->createView()
));
}
After submitting the form you get the following output order:
$this->setMyOption() > null
FormEvents::PRE_SET_DATA
FormEvents::POST_SET_DATA
calledCreateForm
FormEvents::PRE_SUBMIT
FormEvents::SUBMIT
FormEvents::POST_SUBMIT
finished
The first thing allways gets called is configureOptions and because you don´t have any data of the filled form before calling handleRequest there is no way to change the options of the allready created form without manipulating Symfonys form component.

Resources