I know there's no clean way to do this after the form has been built using FormEvents however is there a way to mainpulate the options passed to a form using FormTypeExtensionInterface::buildForm before it has been completely built?
e.g: I will use this to set multiple options to specific values when another option is set in the form e.g: when the option "helper" is set true set the "label" option to "helper" and set "disabled" option to true
So what you can do is pass the option to your form when you create it. For example, in your controller:
$form = $this->createForm(new YourFormType(), null, array('helper' => true));
Then in your buildForm function:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('myfield', null, array(
'label' => ($options['helper']) ? 'helper' : 'mylabel',
'disabled' => ($options['helper']) ? true : false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'helper' => false,
));
}
The only thing is, this is a global option for the entire form. Did you mean that you want this option for every individual field?
Related
I don't know if you experience this kind of scenario. I use mongodb in symfony. I'm doing the document to connect it to mongodb. I create collection name Product and the fields under the product are
- id
- name
- price
I add a Form Type for validation when submitting a form using AJAX.
class ProductType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', TextType::class)
->add('price', TextType::class);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(
array(
'data_class' => 'Acme\StoreBundle\Document\Product',
'csrf_protection' => false
)
);
}
}
But I want to add a field that doesn't include from the ProductType. When the time I do that. I get an error.
"This form should not contain extra fields."
This is my ajax for submitting a form
public updateCompleted(id:string,name:string,price:string,selected:string){
let data = {name:name,price:price,selected:selected};
let _data = {product: data};
return $.ajax({
url:this.setCompletedUrl(id),
type: "PATCH",
data:_data,
async:false
});
}
The other field that I'm talking about is the selected. I want to check it in my backend the selected string that client choose. But it seems symfony want me to add this field into my document Product. Is there other way to do it?
Add your new field to FormType. In options, set 'mapped' => false. So that the field doesn't need to be in your document Product.
You can get post value of the new field through Form object.
Hope that helps.
Try to use 'mapped' option of the form field:
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('price', TextType::class);
->add('selected', TextType::class, [
'mapped' => false
])
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\StoreBundle\Document\Product',
'csrf_protection' => false
)
);
}
}
In this case selected wouldn't be set to Product object, but selected value you can find in Request object.
I have form witch uses custom non-mapped subform.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('subtype', 'my-subtype');
}
}
The subform is consisting of multiple fields and I need to perform additional check on both of them together. The Callback constraint is perfect for the job. However I can not find a way how to add this constraint on the subform as a whole.
So far I have tried to set the Callback in setDefaultOptions() or set it with setAttribute() in buildForm() but the callback is not evaluated.
Currently I am just adding the Callback to one of the fields:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('field1', 'text')
->add(
'field2', 'text',
array(
'constraints' => array(
new Callback(array(
'methods' => array(array($this, 'validateMyType'))
))
)
));
}
public function validateMyType($data, ExecutionContextInterface $context) {
// Validation failed...
$context->addViolationAt('subtype', "mySubtypeViolation");
return;
}
This however prevents me to add the violation on the whole subtype. What ever I use in addViolationAt() the violation is always added to field which hosts the Callback constraint.
I'm surprised you can't add the Callback in setDefaultOptions(), because I just tested this and this works. That's definitely how I would have done it at first.
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => new Callback([$this, 'test'])
]);
}
public function test($data, ExecutionContextInterface $context)
{
return;
}
And test method was executed (I checked using debugger).
Firstly I had typo in the configuration so the callback method was not triggered. Secondly the error-bubbling was automatically set so the error was added to the whole form. So only what I needed was to manually disable it.
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults([
'error_bubbling' => false, // Automatically set to true for compound forms.
'constraints' =>
array(
new Callback(array(
'methods' => array(array($this, 'validateMyType'))
))
)]);
}
Than the violation is added like for any other callback:
public function validateFacrMembership($data, ExecutionContextInterface $context) {
$context->addViolation("invalidValueMessage");
}
I have a PersonType form and then I have LegalPersonType and NaturalPersonType forms and both extends from PersonType since they have a common field on that form (mapped at Entity level). For example, this is the code for NaturalPersonType.php
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Tanane\FrontendBundle\DBAL\Types\CIType;
use Tanane\FrontendBundle\Form\Type\PersonType;
class NaturalPersonType extends PersonType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('identification_type', 'choice', array(
'label' => 'Número de Cédula',
'choices' => CIType::getChoices()
))
->add('ci', 'number', array(
'required' => true,
'label' => false,
'attr' => array(
'maxlength' => 8,
))
)
->add('lives_in_ccs', 'checkbox', array(
'label' => false,
'required' => false,
'value' => 1,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\NaturalPerson'
));
}
public function getName()
{
return 'natural_person';
}
}
Then at SaveFormController/orderAction() I'm doing this:
$order = new Orders();
$orderForm = $this->createForm(new OrdersType(array($type)), $order, array('action' => $this->generateUrl('save_order')));
But any time I try to render the form I get this error:
Neither the property "nat" nor one of the methods "getNat()", "nat()",
"isNat()", "hasNat()", "__get()" exist and have public access in class
"Tanane\FrontendBundle\Entity\Orders".
Relationship are at Entity level, how I fix that error?
Thanks in advance
1st possible solution
Following suggestions from user here I change, in OrderType.php Form my code to this:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Tanane\FrontendBundle\DBAL\Types\SFType;
class OrdersType extends AbstractType {
/**
* #var string
*/
protected $register_type;
public function __construct($register_type)
{
$this->register_type = $register_type;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// here goes $builder with default options remove for see less code
if ($this->register_type[0] == "natural")
{
$builder->add('nat', new NaturalPersonType(), array(
'data_class' => 'Tanane\FrontendBundle\Entity\NaturalPerson'
));
}
elseif ($this->register_type[0] == "legal")
{
$builder->add('leg', new LegalPersonType(), array(
'data_class' => 'Tanane\FrontendBundle\Entity\LegalPerson'
));
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\Orders',
'render_fieldset' => FALSE,
'show_legend' => FALSE
));
}
public function getName()
{
return 'orders';
}
}
I've fixed by adding 'mapped' => FALSE on each new FormType I add in OrdersType but I don't know if this is the right. Also, if I'm defining the data_class here, and NaturalType will never be access directly just trough OrdersType should I remove the default options from that form or should I leave them there? How can I fix the problem now? What I'm missing?
This is not a direct answer to your question but maybe could solve some problem before that happens...
I don't remember to have seen it's possible to extend a form like this instead of extend AbstractType, but as explained in the docs, if you have common fields to share between different types of forms you should use the native framework modularity offered by inherit_data.
If you need something more specific (some special methods to execute on some field) you can create a new field type or extend an existing one using AbstractTypeExtension.
EDIT:
I don't know exactly why you are using this approach (that I never used in my projects) but IMO PersonType, NaturalPersonType and LegalPersonType should be only "FormType/FieldType" initialized with inherit_data (and not entities like in your code) that contains the fields related to their use, while OrdersType should be composed with the block of forms needed to the type of person who fills it and with data_class setted on the UNIQUE entity that store the data outputted by the form.
I have a simpleform:
public function buildForm(FormBuilderInterface $builder, array $option){
$builder
->setMethod('POST')
->add('isdigital', 'choice', array(
'choices' => array('0' => 'no', '1' => 'yes'),
'expanded' => true,
'multiple' => false,
'data'=> 0
));
}
I populate this form passing in an array key value, without using doctrine entities.
$this->createForm(new PricingType(), $defaultData);
The attribute 'data' should set the value only for the first time, instead overrides the value passed with the array.
If I remove the 'data' attribute, the radio button actually displays the value passed in the array.
Is there any way I can set the default value only for the first time?
In the entity for the data class that related to PricingType add a __construct():
__construct(){
$this->isdigital = 0;
}
Now in your controller when you create the $defaultData item which is form the entity Pricing
$defaultData = new Pricing();
This will have the default value you want and you do not need to have the 'data' => 0 line in your form class.
The only solution I found is You will need to add a form Event Listener POST_SET_DATA to dynamically set the default value if the values aren't set.
For eg:
use Symfony\Component\Form\FormEvents; //Add this line to add FormEvents to the current scope
use Symfony\Component\Form\FormEvent; //Add this line to add FormEvent to the current scope
public function buildForm(FormBuilderInterface $builder, array $option){
//Add POST_SET_DATA Form event
$builder->addEventListener(FormEvents::POST_SET_DATA,function(FormEvent $event){
$form = $event->getForm(); //Get current form object
$data = $event->getData(); //Get current data
//set the default value for isdigital if not set from the database or post
if ($data->getIsdigital() == NULL){ //or $data->getIsDigital() depending on how its setup in your entity class
$form->get('isdigital')->setData(**YOUR DEFAULT VALUE**); //set your default value if not set from the database or post
}
});
$builder
->setMethod('POST')
->add('isdigital', 'choice', array(
'choices' => array('0' => 'no', '1' => 'yes'),
'expanded' => true,
'multiple' => false,
//'data'=> 0 //Remove this line
));
}
Please Note: The above code is not tested, but was rewritten to fit the questions scenario.
I am creating form classes for my forms, but cannot figure out how to 'extend' them.
For example, I have a CustomerType form class, and an EmailType form class. I could add the EmailType directly into my CustomerType
$builder->add('emails', 'collection', array(
'type' => new EmailType(),
'allow_add' => true,
'by_reference' => false
));
but I'd prefer to do this in the controller, so that my CustomerType form class contains only customer information. I feel this is more modular and reusable, since sometimes I want my user to be able to edit only Customer details, and others both Customer details as well as Email objects associated with that customer. (For example, in the first case when viewing a customer's work order, and in the second when creating a new customer).
Is this possible? I'm thinking something along the lines of
$form = $this->createForm(new CustomerType(), $customer);
$form->add('emails', 'collection', ...)
in my controller.
You could pass an option (say "with_email_edition") to your form when it's created that would tell if the form should embed the collection or not.
In the Controller:
$form = $this->createForm( new CustomerType(), $customerEntity, array('with_email_edition' => true) );
In the form:
Just add the option in the setDefaultOptions:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'with_email_edition' => null,
))
->setAllowedValues(array(
'with_email_edition' => array(true, false),
));
}
and then check in the "buildForm" the value of this option,and add a field based on it:
public function buildForm(FormBuilderInterface $builder, array $options)
{
if( array_key_exists("with_email_edition", $options) && $options['with_email_edition'] === true )
{
//Add a specific field with $builder->add for example
}
}