Saving / embedding associated form types - symfony

I have 2 Entities User and Member
The User has all of the normal stuff associated (username, password, email)
The Member has some other fields, including, First Name, Last Name, Age etc
I'm trying to create a new Member and to display a form with both the User fields and the Member fields.
I currently have the following:
User.php
$protected $username;
$protected $email;
/**
* #ORM\OneToMany(targetEntity="BM\UserBundle\Entity\Member", mappedBy="user")
*/
protected $member;
Member.php
/**
* #ORM\ManyToOne(targetEntity="BM\UserBundle\Entity\User", inversedBy="member")
*/
protected $user;
I have 2 form types as well:
MemberType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', new UserType())
->add('firstname')
->add('lastname')
->add('age')
;
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'BM\UserBundle\Entity\User'
);
}
UserType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('email')
->add('password')
;
}
public function getName()
{
return 'user';
}
When I refresh the page where the form is rendered, I get the following error:
Catchable Fatal Error: Argument 1 passed to BM\UserBundle\Entity\Member::setUser() must be an instance of BM\UserBundle\Entity\User, array given, called in /var/www/proj/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 345 and defined in /var/www/proj/src/BM/UserBundle/Entity/Member.php line 259
Line 259 is:
public function setUser(\BM\UserBundle\Entity\User $user = null)
Am I approaching this the right way?
EDIT:
MemberController.php
$member = new Member();
$form = $this->createForm(new MemberType(), $member);
if($request->isMethod('post')) {
$form->submit($request);
if ($form->isValid()) {
$em->persist($member);
$em->flush();
$request->getSession()->getFlashBag()->add('success', 'Member has been saved');
} else {
$request->getSession()->getFlashBag()->add('error', 'Could not save the member');
}
}

Try in this way
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', new UserType())
->add('firstname')
->add('lastname')
->add('age')
;
}
public function setDefaultOptions(array $options)
{
return array(
'data_class' => 'BM\UserBundle\Entity\Member'
);
}
# UserType.php #
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('email')
->add('password')
;
}
public function setDefaultOptions(array $options)
{
return array(
'data_class' => 'BM\UserBundle\Entity\User'
);
}
Take a look the documentation: http://symfony.com/doc/current/cookbook/form/form_collections.html

Related

Symfony 3 : Sending the $options argument values from formType to underFormType

I've created a tag system in a bundle using a formType (TagsType) which I include in my main formType (See below).
I'd like to know how I can send the $options argument values from MyFormType to TagsType.
//...
use EC\TagBundle\Form\Type\TagsType;
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('tags', TagsType::class)
//...
;
}
}
My TagsType
//...
use Symfony\Component\Form\Extension\Core\Type\TextType;
class TagsType extends AbstractType
{
/**
* #var ObjectManager
*/
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addModelTransformer( new CollectionToArrayTransformer(), true )
->addModelTransformer( new TagsTransformer($this->manager), true )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('attr', [
'class' => 'tag-input',
]);
$resolver->setDefault('required', false);
}
public function getParent()
{
// Il retourne un TextType par défaut.
return TextType::class;
}
}
I found. Just do this :
->add('tags', TagsType::class, ['empty_data' => $options])
TagsType.php
$options = $options['empty_data'];

Symfony Form - Allow removal of nested form associated entity

I have a /checkout JSON API endpoint which allows an optional billingAddress parameter alongside other parameters such as email and deliveryAddress.
These addresses are stored in an Address entity related to an Order entity.
Everything works nicely if a user enters their billingAddress, but if a user removes a previously submitted billing address, I can find no way to remove the billingAddress entity. Ideally to remove the billing address I'd use the following JSON POST request.
{
"email": "nick#example.com",
"deliveryAddress": {
"line1": "1 Box Lane"
},
"billingAddress": null
}
Is this at all possible with Symfony forms?
See below for a simplified explanation of the current setup.
Entities
/**
* #ORM\Entity
*/
class Order
{
// ...
/**
* #var Address
*
* #ORM\OneToOne(targetEntity = "Address", cascade = {"persist", "remove"})
* #ORM\JoinColumn(name = "deliveryAddressId", referencedColumnName = "addressId")
*/
private $deliveryAddress;
/**
* #var Address
*
* #ORM\OneToOne(targetEntity = "Address", cascade = {"persist", "remove"}, orphanRemoval = true)
* #ORM\JoinColumn(name = "billingAddressId", referencedColumnName = "addressId", nullable = true)
*/
private $billingAddress;
public function setDeliveryAddress(Address $deliveryAddress = null)
{
$this->deliveryAddress = $deliveryAddress;
return $this;
}
public function getDeliveryAddress()
{
return $this->deliveryAddress;
}
public function setBillingAddress(Address $billingAddress = null)
{
$this->billingAddress = $billingAddress;
return $this;
}
public function getBillingAddress()
{
return $this->billingAddress;
}
// ...
}
.
/**
* #ORM\Entity
*/
class Address
{
// ...
/**
* #var string
*
* #ORM\Column(type = "string", length = 45, nullable = true)
*/
private $line1;
// ...
}
Forms
class CheckoutType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'required' => true
])
->add('billingAddress', AddressType::class, [
'required' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Order::class,
'csrf_protection' => false,
'allow_extra_fields' => true,
'cascade_validation' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
.
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('line1', TextType::class);
// ...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Address::class,
'allow_extra_fields' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
Form events are what you need: https://symfony.com/doc/current/form/events.html
For example if you want to remove the billingAddress field after the form submission you can do that:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'required' => true
])
->add('billingAddress', AddressType::class, [
'required' => false
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if (empty($data['billingAddress'])) {
$form->remove('billingAddress');
}
});
}
Read carefully the documentation to know which event will be the best for your scenario.
Try setting the "by_reference" option for the "billingAddress" field to false in order to make sure that the setter is called.
http://symfony.com/doc/current/reference/forms/types/form.html#by-reference
Big thanks to Renan and Raphael's answers as they led me to discovering the below solution which works for both a partial PATCH and full POST request.
class CheckoutType extends AbstractType
{
/** #var bool */
private $removeBilling = false;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'constraints' => [new Valid]
])
->add('billingAddress', AddressType::class, [
'required' => false,
'constraints' => [new Valid]
])
->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'onPreSubmit'])
->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPreSubmit(FormEvent $event)
{
$data = $event->getData();
$this->removeBilling = array_key_exists('billingAddress', $data) && is_null($data['billingAddress']);
}
public function onPostSubmit(FormEvent $event)
{
if ($this->removeBilling) {
$event->getData()->setBillingAddress(null);
}
}
}

Symfony2 - Prevent a form input from displaying

I created two entities User (that contains email, password, username) and Profile(avatar...)
in the UserType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('password', 'password')
->add('email', 'email')
;
}
and in the ProfileType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('avatar')
->add('city')
->add('country', 'country')
->add('firstName')
->add('lastName')
->add('address')
->add('mobile')
->add('phone')
->add('user', new UserType($this->get('security.context')->getToken()->getUser()))
;
}
Is there is a way to not display the password in the profile when the user want to edit his/her infos ?
PS: Is it a great way to separate the User entity from the Profile ?
You can achieve this via FormEvents. Something like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->user;
$builder
->add('username')
->add('password', 'password')
->add('email', 'email');
$builder->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent, $event) use ($user){
$data = $event->getData(); // NULL or an instance of User object
if ( $data && $data->getId() == $user->getId()){
$event->getForm()->remove('password');
}
});
}
If you want to read more about dynamic forms, you could kind a lot of information in official docs.

Form submission problems

I want to create new user and give him a role from combo box but it doesn't work here is what have I done so far:
ROLE FORM
class RoleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('role','entity',array(
'class' => 'vendor\Bundle\Entity\Role',
'property'=>'role'
))
;
}
USER FORM
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('password')
->add('email')
->add('firstname')
->add('lastname')
->add('role',new RoleType())
;
}
But I can't save my user to database I get this error
An exception occurred while executing 'UPDATE Roles SET role = ? WHERE role_id = ?' with params [{}, 1]:
CONTROLLER
private function createEditForm(User $entity)
{
$form = $this->createForm(new UserType(), $entity, array(
'action' => $this->generateUrl('user_update', array('id' => $entity->getId())),
'method' => 'PUT',
));
$form->add('submit', 'submit', array('label' => 'Update'));
return $form;
}
/**
* Edits an existing User entity.
*
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('myBundle:User')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$deleteForm = $this->createDeleteForm($id);
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('user_edit', array('id' => $id)));
}
return $this->render('myBundle:User:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
Why don't you just define the role field inside the UserType builder?
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('password')
->add('email')
->add('firstname')
->add('lastname')
->add('role','entity',array(
'class' => 'vendor\Bundle\Entity\Role',
'property'=>'role'
))
;
}

how to set field value for collection type in symfony2

//--form timesheettype---it is not entity class
class TimeSheetType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options) {
$builder->
add('dailyTimeSheet', 'collection', array('type' => new DailyType(), 'allow_add' => true, 'allow_delete' => true, 'prototype' => true,))
->add('comment','textarea');
}
public function getName() {
return 'TimeSheetDaily';
}
}
//--- DailyType -- there is entity for this type
class DailyType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('project','entity',array('class'=> 'Parabola\EntityBundle\Entity\Project','property'=>'name'))
->add('projectTask', 'entity', array('class'=> 'Parabola\EntityBundle\Entity\ProjectTask','property'=>'name'))
->add('hours', 'text');
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Parabola\EntityBundle\Entity\TimeSheetDaily');
}
public function getName() {
return 'DailySheet';
}
//-- controller--
$repository = $this->getDoctrine()
->getRepository('ParabolaEntityBundle:TimeSheetDaily')->findAll();
$form = $this->createForm(new \Parabola\TimeSheetBundle\Form\TimeSheetType(),$repository);
I have entity class TimeSheetDaily. While building form of TimeSheetType, I am passing array of TimeSheetDaily class object to form type. and that TimeSheetType has collection of DailyType. It is not Setting value to the collection field which is nothing but a TimeSheetDaily entity.
Have you defined __constructor of TimeSheetDaily class?
Do you have something like this?
public function __construct(){
//....
$this->dailyTimeSheet = new ArrayCollection();
//....
};
It's important to correctly initialize this collection in order for Symfony to be able to insert data into it....

Resources