Symfony - modifying default data in prototypes - php

So say I have a Category and a Product entity, where Category has many Product entities. My Category form builder looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('products', CollectionType::class, array(
'entry_type' => ProductType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__product_prot__'))
->add('save', SubmitType::class))
;
}
And my Product form builder looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('dateAdded', DateType::class)
;
}
What I would like to know is how to set the value of dateAdded so that whenever a new prototype is added, it displays today's date?
Some people have suggested using the Entity __construct() function to create the default, but this doesn't appear to work for prototypes. I've also found the placeholder option, but I'm not sure how to use it so that it always has today's date - i.e this doesn't appear to work:
->add('addDate', DateType::class, array(
'placeholder' => new \DateTime();
))
As the error is: Attempted to call function "DateTime" from the global namespace
Additionally, I've found the prototype_data field in the CollectionType field, but again, I'm not sure how to specify to only put data in a single field, and to make it dynamic.
Can someone tell me the best method to use - and the correct syntax?
Edit:
So my __construct() looks like:
public function __construct()
{
$this->addDate = new \DateTime();
}
Which works fine for the Category entity (if I give it a date field for testing), but not the prototyped Product entity. When I load the prototype into the form, I just get a default date of 1st Jan 2011. The prototype looks like:
{% block _category_products_entry_row %}
<li>
{{ form_row(form.name) }}
{{ form_row(form.dateAdded) }}
</li>
{% endblock %}
Interestingly, I've also found if I load the new form with a Product entity created already in the Category controller, the dateAdded field that appears due to:
{% for product in form.products %}
{{ form_row(product) }}
{% endfor %}
Has today's date as it's default value. This would suggest to me that the loading of the prototypes which is done in the same fashion as the How to Embed a Collection of Forms tutorial - is causing the issue.

To set a default value to a form field, you can use "data" property and use FormEvent to handle form on update. Here is the result:
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('dateAdded', DateType::class, array(
'data' => new \DateTime(),
))
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
if (!$product) {
return;
}
if ($dateAdded = $product->getDateAdded()) {
$form->add('dateAdded', DateType::class, array(
'data' => $dateAdded,
));
}
});
}
On PRE_SET_DATA, you can override the default value on the dateAdded field with the data you fetched.
For more info, you can visit http://symfony.com/doc/current/reference/forms/types/date.html#data and https://symfony.com/doc/current/form/events.html
Hope it helps

Related

How to modify a form on Symfony

so I'm creating an e-commerce website using symfony and twig. Right now The client can enter manually the quantity he wants of a specific product, but i want that quantity to not exceed the stock we have of that product. So for example if we have 5 chairs, i want him to choose between 1 and 5.
For that i created a dropdown :
<div class="field select-box">
<select name="quantity" data-placeholder="Select your quantity">
{% for i in 1..produit.stock %}
<option value="{{ i }}">{{ i }}</option>
{% endfor %}
</select>
</div>
I want to use the selected value to put it inside a form,, or maybe find another way to set the quantity asked without using the form or even just change the way the form looks because right now it only takes input. Should i generate another form ?
Hopefully i was clear.
Here's what my formType looks like :
class ContenuPanierType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('quantite');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ContenuPanier::class,
]);
}
}
and here's a bit of code from the controller that creates a cart, add the products and all the information regarding it (quantity, user it belongs to, date)
if(is_null($updateContenuPanier)) {
$contenuPanier = new ContenuPanier();
$contenuPanier->setDate(new Datetime());
$contenuPanier->setPanier($panier);
$contenuPanier->setProduit($produit);
$contenuPanier->setQuantite($request->get("contenu_panier")["quantite"]);
}
This can be done with the ChoiceType. You just need to determine your maximum allowed value and configure the appropriate options.
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// access the entity
$entity = $builder->getData();
// your custom logic to determine the maximum allowed integer value based on $entity
$max = $entity->getStockOnHandMethodName();
$builder->add('quantite', ChoiceType::class, [
'choices' => range(1, $max),
'choice_label' => function ($choice) {
// use the value for the label text
return $choice;
},
// prevent empty placeholder option
'placeholder' => false,
]);
}
I'm almost certain you will still need to verify that the submitted quantity is still valid in your controller action which handles the form submission.
Have a look at Form Input Types here. You can specify the options. This field in particular will vary for each product (Product A = 5 in stock, Product B = 10 in stock).
I doubt a select field is the best here, maybe just use a simple <input type="number">, but that's just my thought on this.
$builder->add('quantite', ChoiceType::class, [
'choices' => [ // instead of this, do a loop to create your options
'Apple' => 1,
'Banana' => 2,
'Durian' => 3,
],
)
```

How to make sure that a Symfony form does not contain unnecessary elements?

I am a beginner in Symfony, so this question might be simple for those who are more experienced in this framework.
I am building forms and I have several possible types of items in forms. At this moment these are:
text
html
image
However, in the future there will be many more items. Previously, the project generated all items into their place (text, html and image) and hidden those which are not needed for the specific form item (maximum one is needed). However, I intend to avoid adding items I do not need. Since I do not know at the point buildForm of an arbitrary item is running whether it is text, html or image, so at this point all of them are added (I know this is counter-intuitive, but this is a code I try to refactor):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('layoutTypeInput', TextType::class);
$builder->add('blockTypeOutput', EntityType::class, array(
'class' => 'MyPageBundle:BlockTypeOutput',
'choice_label' => 'titleHu',
'required' => false,
'placeholder' => 'Válassz blokk kimenetet!',
'empty_data' => null,
));
$builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));
$builder->add('html', TextareaType::class, $this->getBlockTypeOptions('html'));
$builder->add('image', ImageSelectType::class, $this->getBlockTypeOptions('image'));
$builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPostSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
Now, I have a function which removes the unnecessary elements of a form:
private function removeUnnecessaryItems(\Symfony\Component\Form\FormInterface $form, $key)
{
$keys = ['text', 'html', 'image'];
foreach ($keys as $k) {
if ($key !== $k) $form->remove($k);
}
}
And inside onPostSetData I call it like this:
$this->removeUnnecessaryItems($form, $inputObject->getLayoutTypeIdText());
and finally, in the twig I determine what should be generated into the form:
{% for ioLayoutBlock in form.ioLayoutBlocks %}
<div class="row">
<div class="col-xs-12 col-md-3">
{{ form_errors(ioLayoutBlock.layoutTypeInput) }}
{{ioLayoutBlock.layoutTypeInput.vars.label}}
</div>
{{ form_widget(ioLayoutBlock.layoutTypeInput, {'attr' : {'class':'hidden'}}) }}
<div class="col-xs-12 col-sm-6 col-md-5">
{{ form_errors(ioLayoutBlock.blockTypeOutput) }}
{{ form_widget(ioLayoutBlock.blockTypeOutput, {'attr' : {'class':'blockTypeOutput'}}) }}
</div>
<div class="col-xs-12 col-sm-6 col-md-4">
{% if ioLayoutBlock.text is defined %}
{{ form_errors(ioLayoutBlock.text) }}
{{ form_widget(ioLayoutBlock.text, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_text' }}) }}
{% elseif ioLayoutBlock.html is defined %}
{{ form_errors(ioLayoutBlock.html) }}
{% if layout.layoutType.name == 'userHTML' %}
<div class="input-group ioLayoutBlock_html hidden">
<a class="input-group-addon myAdminForm" target="_blank" data-my-href="page/{{ page.id }}/wysiwyg/{{ layout.id }}"><span class="glyphicon glyphicon-pencil"></span></a>
{{ form_widget(ioLayoutBlock.html, {'attr':{'class':'uniqueInput wysiwyg' }}) }}
</div>
{% else %}
{{ form_widget(ioLayoutBlock.html, {'attr':{'class':'hidden uniqueInput wysiwyg ioLayoutBlock_html' }}) }}
{% endif %}
{% elseif ioLayoutBlock.image is defined %}
{{ form_errors(ioLayoutBlock.image) }}
{{ form_widget(ioLayoutBlock.image, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_image' }}) }}
{% endif %}
</div>
</div>
{% endfor %}
and if I load the page, everything is shown correctly, but unfortunately, when I try to submit the form, it gives the error of
This form should not contain extra fields.
as many times as many form items I have. If I comment out the call on removeUnnecessaryItems inside onPostSetData and subsequently remove the conditionals from the twig, like:
{% if ioLayoutBlock.text is defined %}
then everything works, but that's how it worked before the refactor. Ideally I would like to avoid adding so many unnecessary things at buildForm, but I do not know how can I load up any meaningful data there to determine the type of the item. Alternatively I would like to ensure that the items are successfully removed at the events where I do know their type, without the form errors on submit I described above. So, my question is: How can I avoid generating all kinds of unnecessary stuff into my form without being blocked by submit errors?
My approach would be to give a parameter to your form
$form = $this->createForm(DynamicType::class, $user, [
'layout_type_id' => $layoutTypeIdText,
]),
Then adds the fields depending of the param
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->layout_type = $options['LayoutTypeId'];
// [...]
if ($this->layout_type !== 'text' )
$builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));
// [...]
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// [...]
'layout_type_id' => null,
]);
}
With this approch, the good part is you don't to duplicate your logic in twig, the form get the fields it needs, so you can just render the form using
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
For your specific case you need to avoid adding the items at buildForm, to handle the default empty values at onPreSubmit (as the transformers will not be called if the items are not added at buildForm) and to add the effective items at onPreSubmit.
You were correct using form listeners, they are the way to go for your use case.
I see 2 sub scenarii:
the form data is coming from the Model, you fill it inside the controller
a new item is added to the form, client-side (via the prototype).
I'm getting from your example code that users can't add new items dynamically. If you want to do so, the code is just slightly different.
Remains the first scenario. The trick is not to remove, but to add forms inside the PRE_SET_DATA listener:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('layoutTypeInput', TextType::class);
$builder->add('blockTypeOutput', EntityType::class, array(
'class' => 'MyPageBundle:BlockTypeOutput',
'choice_label' => 'titleHu',
'required' => false,
'placeholder' => 'Válassz blokk kimenetet!',
'empty_data' => null,
));
$builder->addEventListener(FormEvents:: PRE_SET_DATA, array($this, 'onPreSetData'));
// ...
}
public function onPreSetData(FormEvent $event)
{
$data = $event->getData(); // This contains model data (ie., from controller)
$form = $event->getForm();
$type = 'image'; // Read type from your model
$formType = $this->getFormTypeForType($type);
$builder->add($type, formType, $this->getBlockTypeOptions($type));
}
private function getFormTypeForType($type)
{
switch ($type) {
case 'image':
return ImageSelectType::class;
// ...
default:
// Up to you, you can decide on setting a default type or enforcing that the type is correct
throw new \RuntimeException('Unsupported type');
}
}
With that code, you can keep the same Twig.
I'm not sure on what you are trying to do with layoutTypeInput and blockTypeOutput. Maybe we are answering just partially here, don't hesitate on posting the full use case.

Symfony 2.8 - forms fields depending on user status

I am wondering how to solve this problem:
I have form with 4 fields. I want 4th field to be dependent on user status (logged or unlogged). For logged user I will get ID from session but unlogged user should provide username manually.
I dont know which option should i use. Inherit_data, two form types (two much duplicated code) or validation groups based on the submitted data. Any ideas?
Ok, There are several ways to achive that.
Take a Look at FormEvents. In your case it would be FormEvents::PRE_SET_DATA and then read about dynamic forms
I personly prefer to do following
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder->add( ... )
//$builder->add( ... )
//$builder->add( ... )
//each-event with own method, but of cource it can be a callback like in a tutorial above
$builder->addEventListener(FormEvents::PRE_SET_DATA, array(this, 'onPreSetData');
}
and in the same class there is a method onPreSetData
public function onPreSetData ( FormEvent $formEvent )
{
$form = $formEvent->getForm();
$user = $formEvent->getData();
//pseudo-code
if( $user->isLoggedIn() )
{
$form->add('user', HiddenType::class, array(
));
}
else
{
$form->add('user', TextType::class, array(
'label' => 'Username',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'please enter username...')),
// new YourCustomValidator(array('message' => 'not enough minerals...')),
)
));
}
}
I personally think a more elegant solution is to pass the user to the form builder from your controller. It's been covered in this answer here:
how to check the user role inside form builder in Symfony2?
you can create form by individual fields like
{{ form_row(form.username) }}
{{ form_row(form.email) }}
{{ form_row(form.phone) }}
{% if(app.user) %}
//your conditional field
{% endif%}
By this way, you have to create submit button as well as csrf token if there
I hope this will quite helpful :)

Put css inside creation of form in symfony2

I have a question. So I have this form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$aColors = array();
foreach(Colors::$aNewsColors as $k=>$v){
$aColors['<div style="background-color:'.$v['textCategoryColor'].'">'.$v['name'].'</div>'] = $k;
}
$builder
->add('title', TextType::class)
->add('image',FileType::class, array('data_class' => null))
->add('document',FileType::class, array('data_class' => null))
->add('content',TextareaType::class)
->add('color_id',ChoiceType::class, array('choices' => $aColors,'choices_as_values' => true))
->add('is_top_news',CheckboxType::class)
->add('has_gallery',CheckboxType::class)
->add('save',SubmitType::class, array('label'=> 'Save'))
;
}
The problem is in this color_id, because in the form a get the select box like this :
<option value="1"><div style="background-color:rgb(174,252,202)">Green</div></option>
<option value="2"><div style="background-color:rgb(12,2,43)">Red</div></option>
So the background-color is not interpreted. Can you help me please ?
First things first. The background color is not interpreted by the browser because it's set on a <div> inside an <option>.
With that said, I agree with other people, you should use a javascript library. Things won't necessarily look pretty with a <select> tag.
But your question needs an actual answer, and it turns out that even if you use a javascript library, the approach I'll suggest will help you.
To actually answer your question, make use of 'choice_attr'
$aColors = array();
$choiceAttr = array();
foreach(Colors::$aNewsColors as $k=>$v){
$aColors[$v['name']]=$k;
$choiceAttr[$v['name']]=array(
'style'=>'background-color: ' . $v['textCategoryColor']. ';',
);
}
$builder
->add('title', TextType::class)
->add('image',FileType::class, array('data_class' => null))
->add('document',FileType::class, array('data_class' => null))
->add('content',TextareaType::class)
->add('color_id',ChoiceType::class, array(
'choices' => $aColors,
'choices_as_values' => true,
'choice_attr'=>$choiceAttr,
))
->add('is_top_news',CheckboxType::class)
->add('has_gallery',CheckboxType::class)
->add('save',SubmitType::class, array('label'=> 'Save'))
;
This should output
<option value="1" style="background-color:rgb(174,252,202);">Green</option>
<option value="2" style="background-color:rgb(12,2,43);">Red</option>
And if you do decide to go for a javascript library, you are not limited to setting a "style" in 'choice_attr'. In addition to the "style" attribute, you can use 'choice_attr' to set the "class" attribute, or any attribute you can come up with (E.g. "data-color-value").
You will need to use js.
I think you can loop the div and give a data-color attribute with the necessary information (id, color ...) and retrieve js side
When you click on the select you recovered the value (which is the id) and you course all the div element of the data attribute and find the id.
In view example.html.twig
{% for color in textCategoryColors %}
<div data-color="{color | json_encode}"></div>
{% endfor %}

Symfony2 editForm datetime object in formbuilder but I need it to be a string

For starters, I'm new to symfony2 -> coming from Laravel and ZF1 I have to admit it is not what I expected. I had a lot of struggle with simple tasks and by the moment of writing this twig editing is still a pain.
So scenario:
I have a invoice form with 2 datetime fields in it, I really hate that ugly 3 select field that is generated so I included my jQuery datepicker.
http://tinypic.com/r/eg5kyg/8
For my initial insertion of data because of the datetime I needed to create my form with a input type "text" :
$form=$this->createFormBuilder($invoice)
->add('date_ref','text', array(
'required'=> true))
->add('date_sent','text', array(
'required' => true ))
In this way that ugly 3 select datepicker is not shown and my users can select a date in an elegant way.
All good until the edit form:
I don't know why I didn't managed to get this done as i initially wished :
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$form=$this->createFormBuilder($invoice)
->add('date_ref','text', array(
'required'=> true))
->add('date_sent','text', array(
'required' => true ))
->add('value','integer',array('required'=>true))
->add('nr_ref','text',array('required'=>true))
So i wanted to create a form from a entity with his properties but in the view
<div class="form-group">
{{ form_label(theForm.date_ref, 'Date Refferenced', { 'label_attr': {'class': 'control-label col-md-5'} }) }}
{{ form_errors(theForm.date_ref) }}
<div class="col-md-6">
{{ form_widget(theForm.date_ref,{'attr': {'class': 'form-control', 'data-provide':'datepicker'}}) }}
</div>
</div>
Was not working so I couldn't create my Edit Form as my first form with the date_ref input with a "text" value.
I searched and found out that creating a form from a entity data you need to create a "Type" so I created my "InvoiceType"
which has :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('is_paid')
/* not working
->add('date_ref', 'date', array('input' => 'datetime',
'format' => date('y-M-d'),
'widget' => 'single_text'))
*/
->add('date_ref') //this is a datetime obj which results in those 5 selects
->add('date_sent')
->add('value')
->add('nr_ref')
->add('name_provider')
;
}
$invoice = $em->getRepository('AppBundle:Invoice')->find($id);
$form = $this->createForm(new InvoiceType(), $invoice);
But because this method creates a form from the entity, if I change the type from the buildForm() function .. I get an error that he expects a Object of Datetime instead of a string ("text").
So now I am stuck at making the Edit Form .. that would be a type, 1 input Text and pre-populated with the date from the Database(something like 2014/02/02) using datepicker and when you click on it, it should appear as in the create form.
Like I said I am new, I learned a lot of things that I normally done other ways in other MVC and I struggle a lot with this datetime Object and symfonys 3 select datetime.
http://tinypic.com/r/28bz5h4/8
<- This looks awful
Thanks a lot Khalid Junaid, after I looked up the post you have mentioned I finally got it working as i wanted:
Solution:
->add('date_ref', 'date' ,array(
'widget'=> 'single_text',
'format'=>'d/M/y'))
->add('date_sent','date' ,array(
'widget'=> 'single_text',
'format'=>'d/M/y'))

Categories