Access form.vars.value in Symfony subform using Twig - forms

I am working on a Symfony 2.7 WebApp and I would like to use a custom Form Widget for one of the entites. The Widgets needs to access the form.vars.value. This works fine as long as the Widget is uses within the main form. But when using the Widget in a subform, form.vars.value is empty.
The classes used within the form:
class AdressBookEntry {
// The main phone number of this contact: Type PhoneNumber
protected $mainPhoneNumber;
//...getter and setter for mainPhoneNumber
// An array of Addresses
protected $addresses;
//...getter and setter for addresses
...
}
class Address {
// The phone number of this address: Type PhoneNumber
protected $phoneNumber;
//...getter and setter for phoneNumber
...
}
class PhoneNumber {
...
}
The custom Form Types for theses classes:
// Custom FormType for AddressBookEntries
class AdressBookEntryType extends AbstractType {
...
public function buildForm(FormBuilderInterface $builder, array $options) {
// Type 'phone_number_edit' is registered in services.yml
$builder
->add('mainPhoneNumber', 'phone_number_edit', array(
'label' => '...',
...
))
->add('addresses', 'collection', array(
'label' => '...',
...
));
}
}
// Custom FormType for Address
class AddressType extends AbstractType {
...
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('mainPhoneNumber', 'phone_number_edit', array(
'label' => '...',
...
))
...;
}
}
The custom Widget for the PhoneNumberEdit
{% block phone_number_edit_widget %}
...
{{ dump(form.vars.value) }}
...
The PhoneNumberEdit for the main form (representing the AddressBookEntry) works fine. The dump statement shows the content of the assigned PhoneNumber object.
Within the Subform of the addresses collection however, the form.vars.value variable is empty. The dump shows just "".
So, how do I access form.vars.value within the subform? How can the widget recognize wether it is being uses within the main form or a subform?
UPDATE:
Some additional information as asked in the comments:
#Jeet: As described before the dump shows an empty value/string: ""
#DOZ: This is the Twig code:
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.name) }}
{{ form_widget(mainPhoneNumber) }}
<ul data-prototype"{{ _self.addressItem(form.addresses.vars.prototype)|e('html_attr') }}" >
{% for address in form.addresses %}
{{ _self.addressItem(address) }}
{% endfor %}
</u>
...
{{ form_end(form) }}
{% macro addressItem(address) %}
<li>
{{ form_widget(address.phoneNumber) }}
...
</li>
{% endmacro %}

Use value instead of form.vars.value
{% block phone_number_edit_widget %}
...
{{ dump(value) }}
...

Related

Symfony 2.8 - display certain entity value in form field widget template

I am building a custom file upload widget where I display last uploaded filename. I created FormType class and in form/fields.html.twig I added the following:
{% block custom_document_widget %}
{% spaceless %}
{# here I want to include code to display filename #}
{# display file input #}
{% set type = 'file' %}
{{ block('form_widget_simple') }}
{% endspaceless %}
{% endblock %}
I know that the value of a current field can be parsed {{ form.vars.value }}, but in the end the field is file input and does not have the value of filename that was uploaded previously.
To store uploaded filename I have $filename variable in entity and would like to display it in field widget template. How can I approach it?
In the end I had to pass the filename as an option to embedded form that represented my FileType:
$builder
->add('resumeFile', CustomDocsType::class, array(
'required' => false,
'constraints' => array(
new File(array(
'mimeTypes' => array(
'application/pdf',
),
'mimeTypesMessage' => 'mimetype',
)),
),
'filename' => $trainee->getResumeOriginal(),
))
In my CustomDocsType:
class CustomDocsType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
$view->vars = array_merge($view->vars, array(
'filename' => $options['filename']
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'filename' => null
));
}
public function getParent()
{
return FileType::class;
}
}
And now I only had to acces the filename in template:
{{ form.vars.filename }}

Symfony - Add text in generated form

I'd like to do something quite simple, but I can't figure out how to manage it. I have a form:
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
There are several text field in it. I'd like to "insert" some text (like <p>my Text</p>) between two text fields (let's say between name text field and description text field). Form are generated with form builder tool. I've tried something like:
$builder
->add('stuffName') // works well (text field 1)
->add('addedText', 'text', array('label' => 'my Text')) // Trouble here!
->add('stuffDescription'); // works well (text field 2)
But it generates a text field (and not a simple text). I don't care if the text is set in the form builder or directly in twig template... As long as it is between my two text fields. Any idea?
Thanks a lot!
Symfony forms contain only form fields. Any additional content you want has to be added by the template.
This means you'll have to output the form field-by-field. Your form, for example might look like this:
{{ form_start(form) }}
{{ form_row(form.stuffName) }}
<p>Your Text</p>
{{ form_row(form.stuffDescription) }}
{{ form_end(form) }}
For more more information on how you can customize form rendering, please see the forms chapter in the Symfony documentation.
The keyword in this question is generated.
Let's assume, that you build a form generator in Symfony. You have entities like Form, Fields and Fields Items (it's options for select box or buttons for radio button field).
So you have this entities and you create a service to create a form from the data. In the service you build the form ($this->buildedForm - generated form, $page->getFormData() - put the data to the constructed form):
$this->buildedForm = $this->formFactory->create(
'form',
$page->getFormData(),
['action' => '/page/formview/' . $task->getId()]
);
foreach($fields as $field) {
$fieldBuilderMethod = 'construct' . ucfirst($field->getType()) . 'Field';
if (method_exists($this, $fieldBuilderMethod)) {
$this->$fieldBuilderMethod($field);
}
}
return $this->buildedForm;
And you have methods for each type like (examples for Symfony 2):
private function constructInputField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'text',
[
'label' => $field->getName(),
]
);
}
private function constructTextareaField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'textarea',
[
'label' => $field->getName(),
]
);
}
You can now create your custom form type to paste a text in the generated form (it could be placed in the form folder of your bundle and retrieved with namespace "use"):
private function constructSimpletextField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
new SimpletextType(),
[
'label' => $field->getName(),
'data' => $field->getPlaceholder(),
]
);
}
What in this custom field?
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'simpletext';
}
}
And the whole magic comes out in the template. For your custom form type you need to make a custom theme (see https://symfony.com/doc/2.7/form/form_customization.html#form-customization-form-themes). And there:
{% block simpletext_label %}{% endblock %}
{% block simpletext_widget %}
<p>{{ form.vars.data }}</p>
{% endblock %}
{% block simpletext_errors %}{% endblock %}
See, no label, no errors (it just a text) and only text in the field widget. Very handy for generated forms with dynamic template.
EDIT - Symfony 5
In Symfony 5, this solution became simplier. The form customization doesn't changes, and the php code became like this:
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getBlockPrefix(): string
{
return 'simpletext';
}
}
It's used like this :
public function buildForm(FormBuilderInterface $builder, array $options): void {
/* … */
$builder->add('anykey', SimpleTextType::class, [
'data' => "Type your text here",
]);
/* … */
}
Here a sample code which would be self explain
{{ form_start(form, { 'attr': { 'class': 'form-horizontal form-bordered'} }) }}
<div class="form-group">
<div class="col-md-3 ">
{{ form_label(form.User, 'Label text', { 'attr': {'class': 'control-label'} }) }}
</div>
<p>You are free to add whatever you want here</p>
<div class="col-md-9">
{{ form_widget(form.User, { 'attr': {'class': 'form-control'} }) }}
</div>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
In any case, the symfony documentation is pretty clear and well-explain about this point.

Symfony 2.7 Form Entity type render multiple properties in form

I had this working previously but it stopped working with Symfony 2.7
What I want is to render an expanded/multiple entity choice list such that I display multiple custom properties. The goal is to list the choices as:
{name} - {description} More info
So I created a custom form type with "entity" as parent so I could customize the form rendering
<?php
namespace Study\MainBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ScholarshipEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setAttribute('dataType', $options['dataType']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'required' => false,
'dataType' => 'entity'
));
}
public function getAllowedOptionValues(array $options)
{
return array('required' => array(false));
}
public function getParent()
{
return 'entity';
}
public function getName()
{
return 'scholarship_entity';
}
}
I render the type as follows (it was just based off of the Twitter Bootstrap bundle template):
{% block scholarship_entity_widget %}
{% spaceless %}
{% if expanded %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default(''))}) %}
{% set label_attr = label_attr|merge({'class': (label_attr.class ~ ' ' ~ (widget_type != '' ? (multiple ? 'checkbox' : 'radio') ~ '-' ~ widget_type : ''))}) %}
{% if expanded %}
{% set attr = attr|merge({'class': attr.class|default(horizontal_input_wrapper_class)}) %}
{% endif %}
{% for child in form %}
{% if widget_type != 'inline' %}
<div class="{{ multiple ? 'checkbox' : 'radio' }}">
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ form_widget(child, {'horizontal_label_class': horizontal_label_class, 'horizontal_input_wrapper_class': horizontal_input_wrapper_class, 'attr': {'class': attr.widget_class|default('')}}) }}
{{ child.vars.label.name|trans({}, translation_domain) }}
- {{ child.vars.label.description }}
More Information
</label>
{% if widget_type != 'inline' %}
</div>
{% endif %}
{% endfor %}
{{ block('form_message') }}
{% if expanded %}
{% endif %}
{% else %}
{# not being used, just default #}
{{ block('choice_widget_collapsed') }}
{% endif %}
{% endspaceless %}
{% endblock %}
Finally, I use my custom type in another form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('scholarships', new ScholarshipEntityType(), array(
'class' => 'StudyMainBundle:Scholarship',
'query_builder' => function(EntityRepository $er) use ($options) {
return $er->findAllByOfferingQueryBuilder($options['offering']);
},
'choice_label' => 'entity',
'multiple' => true,
'expanded' => true,
'label' => 'financial.scholarships'
))
;
}
The "property" I'm rendering is just the entity itself:
/**
* Scholarship
*
* #ORM\Table(name="scholarship")
* #ORM\Entity(repositoryClass="Study\MainBundle\Repository\ScholarshipRepository")
*/
class Scholarship
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* Get the Entity object for form rendering
*
* #return \Study\MainBundle\Entity\Scholarship
*/
public function getEntity()
{
return $this;
}
}
Unfortunately, it looks like my trick which was passing the entire Entity to Twig and letting me access properties is no longer working. There is some change where the label is rendered as a string (I changed 'property' to 'choice_label' above for 2.7, if that matters).
Error:
Catchable Fatal Error: Object of class Study\MainBundle\Entity\Scholarship could not be converted to string
Stack Trace:
1. in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251 +
2. at ErrorHandler ->handleError ('4096', 'Object of class Study\MainBundle\Entity\Scholarship could not be converted to string', '/var/project/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php', '251', array('choice' => object(Scholarship), 'key' => '0', 'label' => object(Closure), 'values' => array('2'), 'index' => array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), 'attr' => null, 'isPreferred' => array(), 'preferredViews' => array(), 'otherViews' => array(), 'value' => '2', 'nextIndex' => '2'))
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 251 +
3. at DefaultChoiceListFactory ::addChoiceView (object(Scholarship), '0', object(Closure), array('2'), array('Symfony\Bridge\Doctrine\Form\Type\DoctrineType', 'createChoiceName'), null, array(), array(), array())
in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php at line 185
Is there another way to achieve this?
I was thinking about the following (but don't know exactly how to do these or if it's worth looking into any of them):
transformers
custom type that derives from Choice and does what I want (maybe from a bundle)
using the choice list factory somehow
passing the entity as some additional field instead of the label (maybe the new 'choice_attr'?)
If I understood correctly the problem, you should implement the __toString() function in your entity, that will format the string you want to print in the Choice list for you entity.
For example:
function __toString() {
return sprintf("%s - %s", $this->type, $this->description);
}
Try to use the method AbstractType::buildView(FormView, FormInterface, array). There you can access the variables that get passed to the template.
I used it for a DaterangeType to declare separate ids and names for two date fields:
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['full_name_0'] = $view->vars['full_name'] . '[0]';
$view->vars['full_name_1'] = $view->vars['full_name'] . '[1]';
$view->vars['id_0'] = $view->vars['id'] . '_0';
$view->vars['id_1'] = $view->vars['id'] . '_1';
}
You can then access these values as standard twig variables.

symfony 2 form display default value

I'm working with Symfony 2.0.14 and I would like to display the default value in my form template.
Well a FormType is bound to an entity, when I want to add extra field, I know the option property_path = false allow to add non-entity fields, right ?
When I m in the opposite case, I want to set an entity field without a form field.
Ok I just have to give a default entity to "createForm".
Howewver how can I render it in my template form ?
Controller code :
public function newAction(Request $request)
{
$game = new Game();
$local = new Role();
$visitor = new Role();
$local->setType('LOCAL');
$visitor->setType('VISITOR');
$game->addRole($local);
$game->addRole($visitor);
$form = $this->createForm(new GameType(), $game);
GameType code :
public function buildForm(FormBuilder $builder, array $options){
$builder->add('teams', 'collection', array( 'type' => new RoleType()));
}
RoleType code :
public function buildForm(FormBuilder $builder, array $options){
$builder->add('type', 'text'); // <= I would like read only for end-User
$builder->add('score', 'integer');
form template :
{% for role in form.teams %}
<li>
<div class="role-team">
{{ role.type }} {# WRONG way, how to do ? #}
{{ form_row(role.score) }}
</div>
</li>
{% endfor %}
If you want just to display your entity field value (without passing the entire entity to the view) you can print it with:
{{ form.vars.value.type }}
(assuming your role entity has type property).
EDIT: i realized you're inside the loop. Try figuring out the right property path using:
{% for role in form.teams %}
{% debug role %}
{% endfor %}

How to deal with Form Collection on Symfony2 Beta?

I have an entity User and an entity Address. There is a relation One-to-Many between User and Address :
class User
{
/**
* #orm:OneToMany(targetEntity="Address")
*/
protected $adresses;
[...]
}
I have a class AddressType, and class UserType :
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('addresses', 'collection', array('type' => new AddressType()));
}
[...]
}
In my controller, I build form with :
$form = $this->get('form.factory')->create(new UserType());
... and create view with :
return array('form' => $form->createView());
I display form field in my twig template with :
{{ form_errors(form.name) }}
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
[...]
Okay. Now, how to display fields for one or more addresses ? (it's no {{ for_widget(form.adresses.zipcode) }} nor {{ for_widget(form.adresses[0].zipcode) }} ...)
Any ideas ?
This is how I did it in my form template:
{{ form_errors(form.addresses) }}
{% for address in form.addresses %}
<div id="{{ 'address%sDivId'|format(loop.index) }}" class="userAddressItem">
<h5> Address #{{ loop.index }}</h5>
{{ form_errors(address) }}
{{ form_widget(address) }}
</div>
{% endfor %}
And I have a small action bar, driven by jQuery, that lets the user add and remove addresses. It is a simple script appending a new div to the container with the right HTML code. For the HTML, I just used the same output has Symfony but with updated index. For example, this would be the output for the street input text of the AddressType form:
<input id="user_addresses_0_street" name="user[addresses][0][street]" ...>
Then, the next index Symfony will accept is 1 so the new input field you add would look like this:
<input id="user_addresses_1_street" name="user[addresses][1][street]" ...>
Note: The three dots are a remplacement for required="required" maxlength="255" but could change depending on your needs.
You will need more HTML code than that to add a whole new AddressType to the DOM of the browser but this give you the general idea.
Regards,
Matt
I should top that up with the fact that if you want to dynamically add fields, you need to set the key 'allow_add' to true in your collection field in UserType :
...
$builder->add('addresses', 'collection', array(
'type' => new AddressType(),
'allow_add' => true
));
Just spent hours trying to figure out what was missing, and at the time i'm writing the doc does not mention this yet. Hope it'll help fellow developers.