FormType - ManyToMany on himself - symfony

I try to do a Parents, Children relation on an Entity, but I've a problem when I submit the form. (It's a ManyToMany on himself)
To do it, I've a ManyToMany relation on my entity like it:
class Strain
{
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Strain", inversedBy="children")
*/
private $parents;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Strain", mappedBy="parents")
*/
private $children;
public function __construct()
{
$this->parents = new ArrayCollection();
$this->children = new ArrayCollection();
}
public function addParent(Strain $strain)
{
$this->parents->add($strain);
}
public function removeParent(Strain $strain)
{
$this->parents->removeElement($strain);
}
public function getParents()
{
return $this->parents;
}
public function getChildren()
{
return $this->children;
}
I think it's okay, I've the foreign keys, an intermediate table strain_strain, with 2 columns: strain_source and strain_target.
My FormTypes:
class StrainType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('parents', CollectionType::class, array(
'entry_type' => StrainParentType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
))
;
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Strain'
));
}
And the second:
class StrainParentType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('gmoStrain', EntityType::class, array(
'class' => 'AppBundle\Entity\Strain',
'choice_label' => 'systematicName',
'placeholder' => '-- select a parent --',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('strain')
->orderBy('strain.systematicName', 'ASC');
}
))
;
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Strain'
));
}
When I send the form, symfony return this error:
Neither the property "strain" nor one of the methods "getStrain()", "strain()", "isStrain()", "hasStrain()", "__get()" exist and have public access in class "AppBundle\Entity\Strain".
If someone have an idea :/ Because I don't know how to do it.
EDIT:
The problem is in the FormType, because I need a Collection of EntityType, I've do 2 FormType, but I can do it in on FormType and use entry_options to define config of EntityType, like this:
class StrainType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('parents', CollectionType::class, array(
'entry_type' => EntityType::class,
'entry_options' => array(
'class' => 'AppBundle\Entity\Strain',
'choice_label' => 'systematicName',
'placeholder' => '-- select a parent --',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('strain')
->orderBy('strain.systematicName', 'ASC');
}
),
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Strain'
));
}

Related

Symfony : embeded subform and createdAt / updatedAt : validation conflict

I have an entity form that embed a collectionType and subforms.
In order to make the required option work, I had to enable Auto_mapping
class ClassePriceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('priceform', PriceformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'mainprice_block'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ClassePrice::class,
'attr' => array(
'class' => 'fullwidth'
)
]);
}
}
class PriceformType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pricecover', ChoiceType::class, [
'label' => 'Price type',
'placeholder' => 'Select a price option',
'choices' => [ 'Global' => '1' ,'Step' => '2'],
'required' => true
])
->add('rangesubform', RangesubformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'range_block'
])
->add('pricesubform', PricesubformType::class, [
'data_class' => ClassePrice::class,
'block_prefix' => 'price_block'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'inherit_data' => true
]);
}
}
class RangesubformType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('rangetype', EntityType::class, [
'class' => Ptur::class,
'label' => 'Step type',
'choice_translation_domain'=> true,
'required' => true
])
->add('rangeformat', EntityType::class, [
'class' => Format::class,
'label' => 'Format',
'required' => true
])
->add('rangemin', IntegerType::class, [
'label' => 'Range min',
'required' => true
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'inherit_data' => true,
'attr' => array(
'class' => 'form-horizontal'
)
]);
}
}
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
auto_mapping:
App\Entity\: []
enabled:
true
In my entites I also use the createdAt / updatedAt auto generation made by
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Gedmo\Mapping\Annotation as Gedmo;
The problem is that with auto_mapping activation, I got a validator error when creating a new entity input : createdAt and updatedAt can not be null...
Any idea how to solve that please?
Make sure your object is valid, inside the class:
public function __construct()
{
$this->createdAt = new DateTime();
$this->updatedAt = new DateTime();
}
Set the time like this in your entity:
/**
* #var datetime $created_at
*
* #Orm\Column(type="datetime")
*/
protected $created_at;
/**
* #var datetime $updated_at
*
* #Orm\Column(type="datetime", nullable = true)
*/
protected $updated_at;
/**
* #orm\PrePersist
*/
public function onPrePersist()
{
$this->created_at = new \DateTime("now");
}
/**
* #Orm\PreUpdate
*/
public function onPreUpdate()
{
$this->updated_at = new \DateTime("now");
}

Form error mapping on collection

I have a form:
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('autor', AutorType::class)
->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
'error_bubbling' => false,
))
->add('submit', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Article::class,
));
}
}
This embeds to a custom form. my Article entity looks like this:
class Article
{
/**
* #Assert\Type(type="AppBundle\Model\Autor")
* #Assert\Valid()
*/
private $autor;
/**
*
* #Assert\All({
* #Assert\Type(type="AppBundle\Model\Category")
* })
* #Assert\Valid()
*/
private $categories;
}
My problem is a category field error (category name not blank for example); the error is never mapped to the field itself.
With 'error_bubbling' => true,the error is map with ArticleType form.
With 'error_bubbling' => false, the error is map to the collection
CollectionType but never to the CategoryType form or name filed of CategoryType.
I am on Symfony 3.3 and can not use cascade_validation, I use #Assert\Valid() but it don't seem to work as I expected.
Where did I do wrong?
Thanks for your help.
Try using Valid() as form constraint instead of class one. (Be sure to remove class Valid constraint)
Just encountered same thing and after like 20 combinations it was the solution for me. I'm on symfony 3.2 though.
use Symfony\Component\Validator\Constraints\Valid;
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('autor', AutorType::class)
->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
'error_bubbling' => false,
'constraints' => [
new Valid(),
],
))
->add('submit', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Article::class,
));
}
}
Try setting the error_bubbling option to the collection entries. Not the collection itself. Because the error does not happen to the collection but to the category item in the collection.
$builder
->add('autor', AutorType::class)
->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
'entry_options' => [
'error_bubbling' => false,
],
))
->add('submit', SubmitType::class)
;
Or set it as default in your CategoryType:
// AppBundle\Form\Type\CategoryType.php
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Category::class,
'error_bubbling' => false,
// maybe other options
]);
}

Symfony Form How to do it cleaner

I'm trying to create new order in eshop, now I have it like this, but is it possible to somehow do this data inserting cleaner or automatic?
$order = new Orders();
$customers = new Customers();
foreach ($form['cart']->getData() as $product) {
if ($product['product']->getQuantity() >= $product['quantity']) {
$cart = new Cart();
$cart->setUserId($sessionID);
$cart->setProductId($product['product']);
$cart->setQuantity($product['quantity']);
$cart->setPrice($product['product']->getPrice());
$cart->setBoughtPrice($product['product']->getBoughtPrice());
$em->persist($cart);
$em->flush();
} else {
$this->addFlash('error', 'Není skladem tolik kusu!');
return $this->redirectToRoute('web_admin_cash_register');
}
}
$customers->setBillingFullName($form['customer']['billingFullName']->getData());
$customers->setBillingAddress($form['customer']['billingAddress']->getData());
$customers->setBillingCity($form['customer']['billingCity']->getData());
$customers->setBillingCountry($form['customer']['billingCountry']->getData());
$customers->setBillingPhoneNumber($form['customer']['billingPhoneNumber']->getData());
$customers->setBillingPostalCode($form['customer']['billingPostalCode']->getData());
$customers->setIco($form['customer']['ico']->getData());
$customers->setDic($form['customer']['dic']->getData());
$customers->setEmail($form['customer']['email']->getData());
$em->persist($order);
$em->flush();
Orders Entity:
/**
* #ORM\OneToMany(targetEntity="OrderItems", mappedBy="order", cascade={"persist", "remove"})
*/
private $cart;
OrderItems Entity:
/**
* #ORM\ManyToOne(targetEntity="Orders", inversedBy="cart")
* #ORM\JoinColumn(name="order_id", referencedColumnName="id")
*/
private $order;
NewOrderType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('cart', CollectionType::class, [
'entry_type' => CartType::class,
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'attr' => array(
'class' => 'collection',
),
])
->add('customer', BillingInfoType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
));
}
CartType(OrderItems):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', EntityType::class, [
'class' => 'Web\ShopBundle\Entity\Product',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('p')
->where('p.enabled = true')
->orWhere('p.quantity >= 1');
},
'constraints' => [
new NotBlank(['message' => 'Povinné pole'])
],
'choice_label' => 'productCode'
])
->add('quantity', IntegerType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[]
);
}
Thank you

Get parent object within child formType event

I've created two form types as below
EmailOptInType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
$_1 = $event->getData();
$_2 = $event->getForm()->getData();
$_3 = $event->getForm()->getParent()->getData();
$form = $event->getForm();
});
}
/**
* #return string
*/
public function getParent()
{
return 'checkbox';
}
And SubscribeType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'text', array(
'label' => 'Sign up to news',
'required' => true,
'attr' => array(
'placeholder' => 'Enter your email to subscribe',
),
))
->add('email_opt_in', 'newsletter_opt_in', array(
'data' => true
));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults(array(
'data_class' => 'Bundle\Entity\Customer',
));
}
As you can see the subscribeType form includes the opt in form. Is it possible to fetch the parent forms data within the event listener of the child form EmailOptInType?
In the snippet above $_1 returns a boolean value of true which represents the checked checkbox, $_2 returns the same and $_3 returns an empty customer object.
Is it possible to get the customer object that has just been created / submitted?
You can pass the customer object an a form option when building the form (eg. in the controller):
$customer = $myRepository->find($customerID);
$form = $this->createForm(SubscribeType::class, $customer, ['customer' => $customer]);
The new SubscribeType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', 'text', array(
'label' => 'Sign up to news',
'required' => true,
'attr' => array(
'placeholder' => 'Enter your email to subscribe',
),
'customer' => $options['customer']
))
->add('email_opt_in', 'newsletter_opt_in', array(
'data' => true
));
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults(array(
'data_class' => 'Bundle\Entity\Customer',
'customer' => null
));
}
And the new EmailOptInType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$customer = $options['customer'];
$builder
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use ($customer) {
//do thing with $customer
$_1 = $event->getData();
$_2 = $event->getForm()->getData();
$_3 = $event->getForm()->getParent()->getData();
$form = $event->getForm();
});
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults(array(
//[...]
'customer' => null
));
}

how to dynamically set cascade validation of form from controller

My form looks like this :
class CpanelRetailerForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('email', 'email', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('addUser', 'checkbox', array(
'label' => 'Add User account',
'required' => false,
'mapped' => false
))
->add('user',new CpanelUserForm());
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Acme\TestBundle\Entity\Retailer',
//'cascade_validation' => true
));
}
public function getName() {
return 'retailer';
}
}
I want to dynamically set this line from controller depending on whether addUser field is checked or unchecked.
cascade_validation' => true
Here is my controller code:
$form = $this->createForm(new CpanelRetailerForm(), new Retailer());
$form->
if ($this->getRequest()->isMethod('POST')) {
$form->bind($this->getRequest());
if ($form->get('addUser')->getData()) {
// then set the cascade_validation to true here
}
}
How can I do this inside controller?
My attempt :
added this line in my form class:
$builder->addEventListener(
FormEvents::POST_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$addUser = $form->get('addUser')->getData();
$validation = false;
if ($addUser) {
$validation = true;
}
$resolver = new OptionsResolver();
$resolver->setDefaults(array(
'cascade_validation' => $validation
));
$this->setDefaultOptions($resolver);
}
);
This didnot work for me. Although I receive data in $addUser, cascade_validation is not added
How can I do this inside controller?
You can´t! Thats the simple answer. Lets take a look at following simple form class:
class TestType extends AbstractType {
/**
* #var boolean
*/
private $myOption;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->myOption = false;
$builder
->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) {
dump('formEvents::PRE_SET_DATA');
})
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
dump('FormEvents::POST_SET_DATA');
})
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
dump('FormEvents::PRE_SUBMIT');
})
->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) {
dump('FormEvents::SUBMIT');
})
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
dump('formEvents::POST_SUBMIT');
})
->add('name', TextType::class)
->add('send', SubmitType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired(array(
'my_option'
));
$resolver->setDefaults(array(
'my_option' => $this->setMyOption()
));
}
/**
* #return bool
*/
public function setMyOption() {
dump($this->myOption);
return $this->myOption;
}
}
Lets take in how you render and handle a form inside a Controller:
public function formAction(Request $request) {
$form = $this->createForm(TestType::class);
dump('calledCreateForm');
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
dump('finished');
dump($form->getData());
die();
}
return $this->render('#TestPra/Test/test_form.html.twig', array(
'form' => $form->createView()
));
}
After submitting the form you get the following output order:
$this->setMyOption() > null
FormEvents::PRE_SET_DATA
FormEvents::POST_SET_DATA
calledCreateForm
FormEvents::PRE_SUBMIT
FormEvents::SUBMIT
FormEvents::POST_SUBMIT
finished
The first thing allways gets called is configureOptions and because you don´t have any data of the filled form before calling handleRequest there is no way to change the options of the allready created form without manipulating Symfonys form component.

Resources