Data updated in FormEvents::POST_SUBMIT not send back to controller - php

I am working with Symfony 2.7 and have created a custom FormType based on Symfonys EntityType. Within the FormEvents::POST_SUBMIT of this type I update the received data by adding a special entity. However this chance is not send back to the controller:
Action within controller:
public function customEditAction(Request $request) {
$formData = $this->getFormData();
// formData has a property myEntities which is array of MyEntity
$form = $this->createForm('my_form_type', $formData);
$form->handleRequest($request);
if ($form->isValid()) {
foreach ($formData->getMyEntities() as $myEntity)
dump($myEntity);
}
...
}
my_form_type
class MyFormType extends AbstractType {
...
public function getName() {
return 'my_form_type';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('myEntities', 'my_entities_type', array(
...
),
));
...
}
}
my_entities_type
class MyEntitiesType extends AbstractType {
...
public function getParent() {
return 'entity';
}
public function getName() {
return 'my_entities_type';
}
public function buildView(FormView $view, FormInterface $form, array $options) {
...
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($options) {
// Update data with a special entity.
$data = $event->getData();
$specialEntity = $this->getSomeSpecialEntity();
$data[] = $specialEntity;
// Dump shows, that the entiy was correctly added to $data
foreach ($data as $myEntity)
dump($myEntity);
$event->setData($data);
}
...
}
}
Expected Result:
Form is created with some set of entities within $formData->getMyEntities()
Form is presented to the user. EntityType (parent of MyEntitiesType) fetched all available Entities to build a selection. Entities within $formData->getMyEntities() are pre-selected
User makes his choice and selects a new set of Entities, e.g. Entity1 and Entity2
Form is submitted
In FormEvents::POST_SUBMIT $specialEntity is added to the data/selection
Both dumps (within POST_SUBMIT and within the controller) should show the selected entities including $specialEntity
Everything works fine beside step 6: While the dump within POST_SUBMIT shows that $specialEntity is in $data, the dump within the controller only shows the user selected entities...
Why isn't the changed data submitted back to the controller? What do I have to do, to get the updated data to the controller?
EDIT:
Adding the special entity in FormEvents::SUBMIT does not work, since this entity is not managed and will thus result in an exception within the EntityType implementation.

Related

How to get the passed options to a Symfony form

I'm trying to extend a Symfony 2.8 Form Type, following this http://symfony.com/doc/2.8/form/create_form_type_extension.html
In my ObjectType form I have something like this, where I create my form. My form includes a field of Type MyExtendedType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$passed_options = array('aKey'=>'aValue', ...);
$builder
->add('someotherfield', 'SomeOtherStandardType', ..)
->add('fieldname', 'AppBundle\Form\Type\MyExtendedType', $passed_options)
->add('someotherfield', 'SomeOtherStandardType', ..)
//...
;
}
In my MyExtendedType I have this in which I want to substitute fieldname in FormEvents::PRE_SET_DATA
public function getParent()
{
return 'Symfony\Bridge\Doctrine\Form\Type\EntityType';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder, $options) {
$form = $event->getForm()->getParent();
$modified_options = $options; //this is not correct because there are all the resolved options of the field
$modified_options['aKey'] = 'anotherValue';
$form->add($builder->getName(), 'Symfony\Bridge\Doctrine\Form\Type\EntityType', $modified_options);
});
}
The problem is that in MyExtendedType I need to access only the $passed_options. In $options I have all the resolved options that are initialized from the Form component, and doing as the previous code the field is not properly built, because of the presence of options in contrast with each other (e.g. query_builder, choices, choice_list..).
Here is the question:
how can I access just the $passed_options using the Form component? I don't want to set the properties in the ObjectType in order to have a generic approach.

The form's view data is expected to be an instance of another class error creating form instance of another entity

I get the following error when trying to create a form from another entity to pass through to my view.
I have two entities in this context CourseGuide and CourseGuideRow and I would like to pass through a form view of CourseGuideRowType to my view - how can I do this?
The form's view data is expected to be an instance of class
CRMPicco\CourseBundle\Entity\CourseGuide, but is an instance of class
CRMPicco\CourseBundle\Entity\CourseGuideRow. You can avoid this error
by setting the "data_class" option to null or by adding a view
transformer that transforms an instance of class
CRMPicco\CourseBundle\Entity\CourseGuideRow to an instance of
CRMPicco\CourseBundle\Entity\CourseGuide.
This is my controller:
// CourseGuideController.php
public function viewAction(Request $request)
{
if (!$courseId = $request->get('id')) {
throw new NotFoundHttpException('No Course ID provided in ' . __METHOD__);
}
$resource = $this->get('crmpicco.repository.course_guide_row')->createNew();
$form = $this->getForm($resource);
// ...
}
My Symfony FormBuilder class:
// CourseGuideRowType.php
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
class CourseGuideRowType extends AbstractResourceType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('channel', 'crmpicco_channel_choice', array('data_class' => null))
->add('name', 'text')
->add('courses', 'text')
;
}
/**
* #return string name
*/
public function getName()
{
return 'crmpicco_course_guide_row';
}
}
I have tried the data_class => null suggestion mentioned elsewhere, but this has no effect.
If I pass through the data_class like this:
$form = $this->getForm($resource, array('data_class' => 'CRMPicco\CourseBundle\Entity\CourseGuideRow'));
I then get this:
Neither the property "translations" nor one of the methods
"getTranslations()", "translations()", "isTranslations()",
"hasTranslations()", "__get()" exist and have public access in class
"CRMPicco\CourseBundle\Entity\CourseGuideRow".
Why is this? There are translations attached to the CourseGuide entity but not the CourseGuideRow.
try to add this function in your FormType:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'YourBundle\Entity\YourEntity',
));
}
And don't forget the specific use:
use Symfony\Component\OptionsResolver\OptionsResolver;
EDIT
In native Symfony (with the Form component):
public function showAction()
{
/.../
$entity = new YourEntity();
$form = $this->createForm('name_of_your_form_type', $entity);
# And the response:
return $this->render('your_template.html.twig', ['form' => $form->createView()]);
}

Symfony 2.7 form builder with new field from another entity?

So I start off with my database layout, I have a User table which has an accounts_id link to another table Accounts.
Inside Accounts I have companyname. Now I have built a AbstractType for my user sign up. I want this form to include a field for companyname. But what I have tried is not working, so where is what I have done so far,
use XBundle\Form\AccountType;
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('firstname', 'text',['label'=>'Firstname'])
->add('surname', 'text',['label'=>'Surname'])
//->add('companyname','text', ['data_class' => 'XBundle\Entity\Accounts'])
->add('companyname', new AccountType()) <- current attempt
->add('email', 'email',['label'=>'Email'])
->add('password', 'password',['label'=>'Password']);
//->add('confirm', 'password', ['mapped' => false,'label'=>'Re-type password'])
//->add('homepage', 'text',['label'=>'Homepage'])
//->add('save', 'submit', ['label'=>'Register']);
}
public function getName() {
return 'registration';
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => 'XBundle\Entity\Users',
'cascade_validation' => true,
]);
}
}
And for my AccountType I have the following,
class AccountType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('companyname','text', ['label'=>'Company']);
}
public function getName() {
return 'account';
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => 'XBundle\Entity\Accounts',
]);
}
}
When I build a createForm & createView for my twig (which renders without any problems without the companyname / AccountType). I get the following error,
Neither the property "companyname"
nor one of the methods "getCompanyname()",
"companyname()", "isCompanyname()", "hasCompanyname()",
"__get()" exist and have public access in class "XBundle\Entity\Users"
Now I know that companyname is in my Accounts Entity. I have also done a test to make sure my AccountType works, by building a new form with that type, which renders the Company Name field without any problems.
But I am not sure what I am doing wrong, or even if this is the right way about going about adding another entities field on my user sign up form, please help :)
The error you get is expected.
AccountType is a form to edit Account entity details. By adding companyname to the builder you explicily say that setters and getters exists for that property. An this part is correct.
UserType is a form to edit User entity details, NOT Account details. If you add an AccountType property named companyname,the form component expect you to provide these methods:
public function getCompanyname() {/*...*/}
public function setCompanyname(Account $account) {/*...*/}
Which is not what you are expecting, and exactly what the error is saying.
You need to provide accessor for the Account to your user entity, and then define how the form should handle it.
Option 1: you want to select an existing account
Add this to the UserType builder:
public function buildForm(FormBuilderInterface $builder, array $options) {
// options are guessed anyway, you choose if set it explicitly or not
$builder->add('account'/*, 'entity', array(
'class' => 'XBundle:Account'
)*/);
}
And the following to your User entity:
class User {
private $account;
public function getAccount() {
return $this->account;
}
public function setAccount(Account $account /* = null, if optional */) {
$this->account = $account;
}
}
Option 2: you want one-to-one association and an editable account
Add this to the UserType builder:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('account', new AccountType());
}
And the following to your User entity:
class User {
private $account;
public function __construct() {
$this->account = new Account();
}
public function getAccount() {
return $this->account;
}
}

Symfony2 Custom Field Type from Populate with values

This is a follow on from the earlier question Symfony2 Custom Form Type or Extension
I am trying to attach a custom field type for Product on an Order. The name field will contain the product name and the id field the product id.
I am using FormEvents::PRE_SET_DATA to try and populate the data but it throws an error, getData() returns Form\Type\ProductAutoCompleteType.
How do I correct the code?
OrderType has the following:
$builder->add('product', new Type\ProductAutoCompleteType(), array(
'data_class' => 'Acme\TestBundle\Entity\Product'
));
ProductAutoCompleteType:
class ProductAutoCompleteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name');
$builder
->add('id');
/* Turns out this is not needed any more
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$product = $form->getData();
$form
->add('name', 'text', array('mapped' => false, 'data' => $product->getName()));
}
);
*//
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
}
public function getParent()
{
return 'form';
}
public function getName()
{
return 'productAutoComplete';
}
}
Updated
Error: FatalErrorException: Error: Call to a member function getProduct() on a non-object in /var/www/symblog/src/Acme/TestBundle/Form/Type/ProductAutoCompleteType.php line 26
Controller
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('AcmeTestBundle:Order')->find(6);
$form = $this->createForm(new OrderType(), $order);
Updated 2
After changing the field [product][id] and submitting the form I now get the following error, I assume this is because the field name is [product][id] and not [product]?
Neither the property "id" nor one of the methods "setId()", "set()" or "__call()" exist and have public access in class "Proxies__CG\Acme\TestBundle\Entity\Product".
Updated 3
I have it submitting data to the controller now, in my controller on submit I have had to add the following, it looks messy to do validation and attach the product this way, is this the right way?
$data = $request->request->get($form->getName());
if ($data['product']['id']) {
$product = $em->getRepository('AcmeTestBundle:Product')->find($data['product']['id']);
if ($product) {
if ($product->getShop()->getId() != $order->getShop()->getId()) {
$form->get('product')->get('name')->addError(new FormError('Invalid shop product'));
}
$form->getData()->setProduct($product);
} else {
$form->get('product')->get('name')->addError(new FormError('A product must be selected'));
}
}
Putting all your logic into the controller is not a good practice. Your forms should handle as much of this as possible to ensure reusability of these forms.
If the Order that you query does not have a related product in it, then it's normal that $form->getData() returns null.
Debugging tip:
Output the value of $form->getData() to inspect the Product object.
Output the value of $form->getParent()->getData() to inspect the Order object from the parent form.
Validation tip:
In case a product in this form can be null, make sure that you only set the name field's data if the product is not null.
Query tip:
Since the findBy method on the repository will not automatically include the related product, I recommend using a QueryBuilder and make a join. Example in the controller:
$order = $em->getRepository('AcmeTestBundle:Order')
->createQueryBuilder('order')
->select('order, product')
->innerJoin('order.product', 'product')
->where('order.id = :order_id')
->setParameter('order_id', $order_id)
->getQuery()
->getOneOrNullResult();
Of course, having so much code in your controller is not great either, so once you get your stuff working, create a custom repository with a custom method and move this code there.
In your entity:
/**
* #Entity(repositoryClass="Acme\TestBundle\Repository\OrderRepository")
*/
class Order {
In your repository
class OrderRepository {
public function findByIdWithProduct() {
// $order = $this->createQueryBuilder...
return $order;
}
}

How to map two symfony form field to one model method?

Hi stackexchange users,
I have a data (model) class which has two methods which look like this:
class ContactDetails {
public function setWebsite($address, $type) {
//do something...
}
public function getWebsite($type) {
//do something...
}
}
Now I want to create a form where the user can input a website address and choose a type (e.g. "private" or "business") for the address.
To make this possible I have created a custom form type like this
class ContactDetailsType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('type', 'text') //better: choice, but for the sake of demo...
->add('website', 'text')
;
}
public function getName() {
return 'ContactDetailsType';
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver
->setDefaults(array(
'data_class' => 'ContactDetails',
));
}
}
The controller then looks like this:
public function indexAction(Request $request) {
//generate completely new cost unit
$costunit = new ContactDetails();
//generate form
$form = $this->createForm(new ContactDetailsType(), $costunit);
$form->add('save', 'submit');
$form->handleRequest($request);
if ($form->isValid()) {
//yay!
}
}
This obviously doesn't work, as the form component doesn't know how to map these two fields from the type to the data model class.
Question: What is the best practise to map the data of two fields of a form to one method call in a data model class and vice-versa?
On your place a i would make both fields virtual in form and then use event listener to set data in entity.
Info about form events

Categories