Symfony : embeded subform and createdAt / updatedAt : validation conflict - symfony

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");
}

Related

Expected argument of type "string", "null" given at property path

Actually when I try to edit the form by sending empty fields, the above error comes on ,
My UserType class looks like:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', null, [
'label' => 'Prénom'
])
->add('lastName', null, [
'label' => 'Nom'
])
->add('email', EmailType::class, [
'label' => 'Adresse e-mail'
])
->add('password', PasswordType::class, [
'label' => 'Mot de passe'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
This problem can be resolved by adding 'empty_data' param in the builder add function:
So the new UserType classe becomes:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', null, [
'label' => 'Prénom',
**'empty_data' => ''**
])
->add('lastName', null, [
'label' => 'Nom',
**'empty_data' => ''**
])
->add('email', EmailType::class, [
'label' => 'Adresse e-mail',
**'empty_data' => ''**
])
->add('password', PasswordType::class, [
'label' => 'Mot de passe',
**'empty_data' => ''**
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
Instead of #Mustapha GHLISSI answer, you can set your field to accept null values:
<?php
class User
{
public ?string $firstName;
// your other fields
}
The public ?string $firstName will accept string and null values for the field firstName.
Before PHP 8.0 its maybe:
public function setFirstName(string $firstName = null): self
{
$this->firstName = $firstName;
return $this;
}

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

FormType - ManyToMany on himself

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'
));
}

How to create a form to edit user roles with FriendsOfSymfony UserBundle

I'm trying to create a controller where I can edit the roles of a user (just that, nothing else) and I'm king of stuck.
I've created a form type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'roles', 'choice', [
'choices' => ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_CUSTOMER'],
'expanded' => true,
'multiple' => true,
]
)
->add('send', 'submit');
}
First, what would be the best way to retrieve the roles? Is there any way to associate a label to them?
In the controller I have this:
/**
* User role edition
*
* #Route(
* path="/edit-roles",
* name = "backoffice_user_edit_roles",
* requirements = {
* "id_user" = "\d*",
* },
* methods = {"GET"}
* )
*
* #Security("has_role('ROLE_ADMIN')")
*
* #Template
*/
public function editRolesAction($id_user)
{
$user = $this->user_repository->findOneById($id_user);
$form = $this->form_factory->create('dirital_user_roles_form_type', $user);
return [
'form' => $form->createView(),
'user' => $user
];
}
Problems that I have:
The form doesn't get populate with the current user roles, how should I do that?
When receiving the form, how can I update the user?
Thanks a lot
Actually it was easier than I thought – this is the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'roles', 'choice', [
'choices' => ['ROLE_ADMIN' => 'ROLE_ADMIN', 'ROLE_USER' => 'ROLE_USER', 'ROLE_CUSTOMER' => 'ROLE_CUSTOMER'],
'expanded' => true,
'multiple' => true,
]
)
->add('save', 'submit', ['label' => 'ui.button.save']);
}
And the controller:
public function editRolesAction(Request $request, $id_user)
{
$user = $this->user_repository->findOneById($id_user);
$form = $this->form_factory->create('dirital_user_roles_form_type', $user);
$form->handleRequest($request);
if($form->isValid())
{
$this->addFlash('success', 'section.backoffice.users.edit_roles.confirmation');
$this->em->persist($user);
$this->em->flush();
$this->redirectToRoute('backoffice_user_edit_roles', ['id_user' => $user->getId()]);
}
return [
'form' => $form->createView(),
'user' => $user
];
}
The only part that remains to do is grabbing the form choices from the config instead of hardcoding them.
To rehuse easily in other controllers or actions, I like more this approach:
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\CallbackTransformer;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add(
'roles', 'choice', [
'choices' => ['ROLE_ADMIN' => 'ROLE_ADMIN', 'ROLE_USER' => 'ROLE_USER', 'ROLE_CUSTOMER' => 'ROLE_CUSTOMER'],
'expanded' => true,
'multiple' => true,
]
)
->add('save', 'submit', ['label' => 'ui.button.save']);
;
// Data transformer
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
without touching the controller.
I added the whole file to see the clases you need to import

Resources