Symfony2 embedded forms rendering in a twig template - php

I'm new to Symfony and twig templates. The problem I have is that I can't figure it out how to render the embedded form's fields separately in a twig template. I tried to search for it but probably others used a bit different forms and those examples didn't work for me.
My forms:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', new UserType());
$builder->add(
'terms',
'checkbox',
array('property_path' => 'termsAccepted')
);
$builder->add('Register', 'submit');
}
public function getName()
{
return 'registration';
}
}
and
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', 'text');
$builder->add('name', 'text');
$builder->add('email', 'email');
$builder->add('password', 'repeated', array(
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password',
'invalid_message' => 'Neteisingai pakartotas slaptažodis.',
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Keliones\MainBundle\Entity\User'
));
}
public function getName()
{
return 'user';
}
}
How the rendering field by field should look for that?

Basically Symfony and Form Framework creates Form object base on Type configuration classes. Form object has createView() method which is passed to view http://api.symfony.com/2.4/Symfony/Component/Form/FormView.html
In twig you can access embeded FormView object like that:
{# access embeded form #}
{{ form_row(form.user.username) }}
{{ form_row(form.user.email) }}
{{ form_row(form.user.password) }}
{# access main form field #}
{{ form_row(form.terms) }}

Related

Embedding nested form type fields in empty symfony form

I have form type for a Shop entity. This links in a 1-1 relationship to a ShopAddress entity.
When I nest the ShopAddress entity it appears blank when creating a new shop. How can I get this to render with the relevant blank fields when creating a new Shop?
// App/Froms/ShopType.php
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( "name" ) // rendering fine
->add( "shopAddress", CollectionType::class, [
"entry_type" => ShopAddressType::class, // no visible fields
] )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
// App/Froms/ShopAddressType.php
class ShopAddressType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("addressLine1", TextType::class ) // not rendering
->add("addressLine2") // not rendering
->add("postcode") // not rendering
->add("county"); // not rendering
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => ShopAddress::class,
));
}
}
Yep. Solved it. Docs had the answer You need to add it as a new FormBuilderInterface object in the add() method:
class ShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", TextType::class)
->add(
// nested form here
$builder->create(
'shopAddress',
ShopAddressType::class,
array('by_reference' => true ) // adds ORM capabilities
)
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Shop::class,
));
}
}
Adding array('by_reference' => true ) will allow you to use the full ORM (Doctrine in my case) capabilities (e.g. $shop->getAddress()->setAddressLine1('this string to add')).

Symfony 3.3 Checkbox only form not submitted

I want to have a form submitted by javascript that contains only one checkbox. But since empty checkboxes don't send their key in request, Symfony doesn't know about the form being submitted. So is there any not so hacky solution or is this kind of a "bug".
form:
class NewsletterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subscribingNewsletter', CheckboxType::class, [
'label' => 'form.label.newsletter',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
part of the controller:
$newsletterForm = $this->formFactory->create(NewsletterType::class, $userToEdit);
$newsletterForm->handleRequest($request);
if ($this->newsletterFormHandler->handle($newsletterForm)) {
$this->session->getFlashBag()->add('notice', 'flash_message.newsletter_changed');
return $response;
}
handler:
public function handle(FormInterface $form): bool
{
if (!$form->isSubmitted() || !$form->isValid()) {
return false;
}
$this->userManager->update($form->getData());
return true;
}
view:
{{ form_start(NewsletterForm) }}
{{ form_row(NewsletterForm.subscribingNewsletter, {
attr: {class: 'js-newsletter-toggle'}
}) }}
{{ form_end(NewsletterForm) }}
So I finally resolved this issue. The only way I found to get this working is to add your submit button to your code like this:
class NewsletterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subscribingNewsletter', CheckboxType::class, [
'label' => 'form.label.newsletter',
'required' => false,
])
->add('submit', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
Not in your view. I was submitting the form by javascript so the way to to this is to simulate the button click in js.

Passing data to buildForm() in Symfony 2.8, 3.0 and above

My application currently passes data to my form type using the constructor, as recommended in this answer. However the Symfony 2.8 upgrade guide advises that passing a type instance to the createForm function is deprecated:
Passing type instances to Form::add(), FormBuilder::add() and the
FormFactory::create*() methods is deprecated and will not be supported
anymore in Symfony 3.0. Pass the fully-qualified class name of the
type instead.
Before:
$form = $this->createForm(new MyType());
After:
$form = $this->createForm(MyType::class);
Seeing as I can't pass data through with the fully-qualified class name, is there an alternative?
This broke some of our forms as well. I fixed it by passing the custom data through the options resolver.
In your form type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->traitChoices = $options['trait_choices'];
$builder
...
->add('figure_type', ChoiceType::class, [
'choices' => $this->traitChoices,
])
...
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'trait_choices' => null,
]);
}
Then when you create the form in your controller, pass it in as an option instead of in the constructor:
$form = $this->createForm(ProfileEditType::class, $profile, [
'trait_choices' => $traitChoices,
]);
Here's how to pass the data to an embedded form for anyone using Symfony 3. First do exactly what #sekl outlined above and then do the following:
In your primary FormType
Pass the var to the embedded form using 'entry_options'
->add('your_embedded_field', CollectionType::class, array(
'entry_type' => YourEntityType::class,
'entry_options' => array(
'var' => $this->var
)))
In your Embedded FormType
Add the option to the optionsResolver
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Yourbundle\Entity\YourEntity',
'var' => null
));
}
Access the variable in your buildForm function. Remember to set this variable before the builder function. In my case I needed to filter options based on a specific ID.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->var = $options['var'];
$builder
->add('your_field', EntityType::class, array(
'class' => 'YourBundle:YourClass',
'query_builder' => function ($er) {
return $er->createQueryBuilder('u')
->join('u.entity', 'up')
->where('up.id = :var')
->setParameter("var", $this->var);
}))
;
}
Here can be used another approach - inject service for retrieve data.
Describe your form as service (cookbook)
Add protected field and constructor to form class
Use injected object for get any data you need
Example:
services:
app.any.manager:
class: AppBundle\Service\AnyManager
form.my.type:
class: AppBundle\Form\MyType
arguments: ["#app.any.manager"]
tags: [ name: form.type ]
<?php
namespace AppBundle\Form;
use AppBundle\Service\AnyManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyType extends AbstractType {
/**
* #var AnyManager
*/
protected $manager;
/**
* MyType constructor.
* #param AnyManager $manager
*/
public function __construct(AnyManager $manager) {
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$choices = $this->manager->getSomeData();
$builder
->add('type', ChoiceType::class, [
'choices' => $choices
])
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\MyData'
]);
}
}
In case anyone is using a 'createNamedBuilder' or 'createNamed' functions from form.factory service here's the snippet on how to set and save the data using it. You cannot use the 'data' field (leave that null) and you have to set the passed data/entities as $options value.
I also incorporated #sarahg instructions about using setAllowedTypes() and setRequired() options and it seems to work fine but you first need to define field with setDefined()
Also inside the form if you need the data to be set remember to add it to 'data' field.
In Controller I am using getBlockPrefix as getName was deprecated in 2.8/3.0
Controller:
/*
* #var $builder Symfony\Component\Form\FormBuilderInterface
*/
$formTicket = $this->get('form.factory')->createNamed($tasksPerformedForm->getBlockPrefix(), TaskAddToTicket::class, null, array('ticket'=>$ticket) );
Form:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefined('ticket');
$resolver->setRequired('ticket');
$resolver->addAllowedTypes('ticket', Ticket::class);
$resolver->setDefaults(array(
'translation_domain'=>'AcmeForm',
'validation_groups'=>array('validation_group_001'),
'tasks' => null,
'ticket' => null,
));
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->setTicket($options['ticket']);
//This is required to set data inside the form!
$options['data']['ticket']=$options['ticket'];
$builder
->add('ticket', HiddenType::class, array(
'data_class'=>'acme\TicketBundle\Entity\Ticket',
)
)
...
}

Using two entities to create a single form

I asked a similar question to this but I think it caused confusion so I decided to ask in this post with brushed up version.
What I want is to print all fields from two different entities in a single web form, BOTH TYPE. That's it.
Note: I tried using entity and collection keywords in the form type (BOTH TYPE) but twig doesn't echo anything. Keep getting; Method "brand" OR "car" for object does not exist in twig line whatever....
Relationship: 1 Brand has N Cars. one-to-many
I read the 'How to Embed a Collection of Forms', 'entity Field Type' and 'collection Field Type' but whatever I did, didn't work.
BRAND ENTITY
namespace Car\BrandBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class BrandEntity
{
protected $id;
protected $name;
protected $origin;
/**
* #ORM\OneToMany(targetEntity = "CarEntity", mappedBy = "brand")
* #var object $car
*/
protected $car;
/**
* Constructor.
*/
public function __construct()
{
$this->car = new ArrayCollection();
}
}
CAR ENTITY
namespace Car\BrandBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class CarEntity
{
protected $id;
protected $model;
protected $price;
/**
* #ORM\ManyToOne(targetEntity="BrandEntity", inversedBy="car")
* #ORM\JoinColumn(name="brand_id", referencedColumnName="id")
* #var object $brand
*/
protected $brand;
}
BRAND TYPE
namespace Car\BrandBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BrandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('name', 'text', array('label' => 'Name'))
->add('origin', 'text', array('label' => 'Origin'))
->add('button', 'submit', array('label' => 'Add'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Car\BrandBundle\Entity\BrandEntity')
);
}
public function getName()
{
return 'brand';
}
}
CAR TYPE
namespace Car\BrandBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('model', 'text', array('label' => 'Model'))
->add('price', 'text', array('label' => 'Price'))
->add('button', 'submit', array('label' => 'Add'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Car\BrandBundle\Entity\CarEntity')
);
}
public function getName()
{
return 'car';
}
}
---------------------------------------------------------------------
-------- This section is the one I'm trying to get it working ------
---------------------------------------------------------------------
BOTH TYPE
namespace Car\BrandBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Test\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BothType extends AbstractType
{
public function builder(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('brand', 'collection', array('type' => new BrandType()))
->add('car', 'collection', array('type' => new CarType()))
->add('button', 'submit', array('label' => 'Add'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Car\BrandBundle\Entity\BrandEntity',
'cascade_validation' => true
));
}
public function getName()
{
return 'both';
}
}
CONTROLLER
namespace Car\BrandBundle\Controller;
use Car\BrandBundle\Form\Type\BothType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BothController extends Controller
{
public function indexAction()
{
$form = $this->createForm(new BothType(), null,
array('action' => $this->generateUrl('bothCreate')));;
return $this->render('CarBrandBundle:Default:both.html.twig',
array('page' => 'Both', 'form' => $form->createView()));
}
}
TWIG
{% block body %}
{{ form_label(form.brand.name) }}
{{ form_widget(form.brand.name) }}
{{ form_label(form.brand.origin) }}
{{ form_widget(form.brand.origin) }}
{{ form_label(form.car.model) }}
{{ form_widget(form.car.model) }}
{{ form_label(form.car.price) }}
{{ form_widget(form.car.price) }}
{% endblock %}
Use an array to composite the two objects in your controller.
$formData = array(
'brand' = new Brand(),
'car' => new Car(),
);
$builder = $this->createFormBuilder($formData);
$builder->add('brand',new BrandFormType());
$builder->add('car', new CarFormType());
$form = $builder->getForm();
==============================================================
If you really want to make a BothType then just get rid of that collection type.
class BothType extends AbstractType
{
public function builder(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('brand', new BrandType())
->add('car', new CarType())
->add('button', 'submit', array('label' => 'Add'))
;
}
// Controller
$form = $this->createForm(new BothType(), $formData
collection is used when you have multiple instances of the same entity type.
By the way, creating classes for each composite form can quickly cause an explosion of form types. So unless you plan on reusing your BothFormType among multiple controllers then I'd suggest just building it right inside of the controller.

Symfony form collection

I'm trying to add a formType in a other form type.
CustomerType:
class CustomerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstname', 'text', array(
'required' => 'required'
))
->add('middlename')
->add('lastname')
->add('email', 'email')
->add('groups', 'entity', array(
'class' => 'MV\CMSBundle\Entity\Group',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('g')
->orderBy('g.name', 'ASC');
}
))
->add('profile', 'collection', array(
'type' => new ProfileType()
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\CMSBundle\Entity\User',
));
}
public function getName()
{
return 'customer';
}
}
ProfileType:
class ProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('isActive', 'checkbox', array(
'required' => false
))
->add('phone')
->add('address')
->add('city')
->add('zipcode')
->add('country')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MV\NameBundle\Entity\Profile',
));
}
public function getName()
{
return 'profile';
}
}
Then I get this message:
Expected argument of type "array or (\Traversable and \ArrayAccess)", "MV\NameBundle\Entity\Profile" given.
If I comment that line i get: (just to check what will goes wrong ;) )
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class MV\NameBundle\Entity\Profile. You can avoid this error by setting the "data_class" option to "MV\NameBundle\Entity\Profile" or by adding a view transformer that transforms an instance of class MV\NameBundle\Entity\Profile to scalar, array or an instance of \ArrayAccess.
What do I do wrong?
Symfony2: 2.1.8-DEV
It's not clear what your mapping is between your Customer entity and your Profile entity, but I guess there are 2 options:
Option 1: You have a OneToOne relationship (One customer can only have One profile). Therefore, you don't need the collection at all but simply need to embed a single object.
->add('profile', new ProfileType());
Option 2: You want a ManyToOne relationship (One customer can have many profiles). In that case, you need to use to embed a collection of forms. If this is what you want, rename $profile to $profiles in your Customer entity, and do the following:
//MV\CMSBundle\Entity\Customer
use Doctrine\Common\Collections\ArrayCollection;
/**
* Your mapping here.
*
* #ORM\ManyToOne(targetEntity="MV\NameBundle\Entity\Profile")
*/
protected $profiles;
public function __construct()
{
//This is why you had an error, this is missing from your entity
//An object was sent instead of a collection.
$this->profiles = new ArrayCollection();
}
Finally, update your database.

Categories