I have a carousel with a collection of slides (OneToMany) and each slide contain a file that I have to validate if not exist.
When a slide has no file, the field is required but when the file exist, it's not required.
My CarouselType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('slides', CollectionType::class, [
'entry_type' => SlideType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
'row_attr' => [
'class' => 'hidden'
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Carousel::class,
]);
}
My SlideType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
])
->add('url', UrlType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
])
->add('file', FileType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Slide::class,
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if ($data->getFile() === null && $data->getFileName() === null) {
return ['upload'];
}
return ['Default'];
},
]);
}
My Carousel Entity:
/**
* #ORM\Entity(repositoryClass=CarouselRepository::class)
*/
class Carousel
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(
* targetEntity=Slide::class,
* mappedBy="carousel",
* fetch="EXTRA_LAZY",
* orphanRemoval=true,
* cascade={"persist"}
* )
* #Assert\Count(min=1, minMessage="carousel.slides.count.min")
* #Assert\Valid(groups={"upload"})
*/
private $slides;
...
...
}
My Slide Entity:
/**
* #ORM\Entity(repositoryClass=SlideRepository::class)
*/
class Slide
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $title;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $url;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $fileName;
/**
* #var UploadedFile
* #Assert\NotNull(message="slide.file.not_null", groups={"upload"})
*/
protected $file;
/**
* #ORM\ManyToOne(targetEntity=Carousel::class, inversedBy="slides")
* #ORM\JoinColumn(nullable=false)
*/
private $carousel;
...
...
}
I've added validation_groups in my SlideType but it's not working.
What's wrong with my code ?
Use PreSetData Event for adding file in the SlideType
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
$isNotRequired = ($data instanceof Slide) && $data->getUrl();
$constraints = isNotRequired ? [] : [new NotBlank()];
$event->getForm()->add('file', FileType::class, [
'row_attr' => [
'class' => 'form-group'
],
'attr' => [
'class' => 'form-control'
],
'required' => !$isNotRequired,
'constraints' => $constraints,
]);
});
You need to use "valid" to validate the subform.
https://symfony.com/doc/current/reference/constraints/Valid.html
$builder->add('slides', CollectionType::class, [
'entry_type' => SlideType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
'row_attr' => [
'class' => 'hidden'
],
'constraints' => [new Valid()],
]);
Related
After several weeks of research, I turn to you to try to understand the QueryBuilder part and implement it on my application.
Concretely, here is what I would like to do:
Depending on the agent(s) chosen by the user, display targets with the same nationality.
My agent entity and my target entity each have a nationality. They are both connected thanks to Doctrine on my Mission entity.
I think I should use NOT IN in my request but don't know how to do it. I show you what I did without results.
/**
* Récupère les nationalités de l'agent
*/
public function findNationality()
{
$this
->createQueryBuilder ('m')
->select ('*')
->join('m.agents', 'a')
->join('m.cibles', 'c')
->where('a.nationality = c.nationality')
->getQuery()
->getResult();
}
My MissionType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('description')
->add('code_name')
->add('country')
->add('speciality', EntityType::class, [
'label' => 'Choisir une spécialité: ',
'placeholder' => 'Choisir une spécialité',
'class' => Specialite::class,
'choice_label' => 'name'
])
->add('agents', EntityType::class, [
'label' => 'Choisir un ou des agent(s): ',
'class' => Agent::class,
'choice_label' => 'identification_code',
'multiple' => true,
'expanded' => true
])
->add('contacts', EntityType::class, [
'label' => 'Selectionner le(les) contact(s): ',
'class' => Contact::class,
'choice_label' => 'code_name',
'multiple' => true,
'expanded' => true
])
->add('cibles', EntityType::class, [
'label' => 'Selectionner la(les) cible(s): ',
'class' => Cible::class,
'choice_label' => 'code_name',
'multiple' => true,
'expanded' => true
])
->add('planques', EntityType::class, [
'label' => 'Selectionner la(les) planque(s): ',
'class' => Planque::class,
'choice_label' => 'code',
'multiple' => true,
'expanded' => true,
])
->add('start_date', DateType::class, [
'widget' => 'single_text'
])
->add('end_date', DateType::class, [
'widget' => 'single_text'
])
->add('status', EntityType::class, [
'label' => 'Statut de la Mission: ',
'class' => Status::class,
'choice_label' => 'name'
])
->add('type', EntityType::class, [
'label' => 'Type de Mission: ',
'class' => TypeMission::class,
'choice_label' => 'name'
]);
}
My Mission Entity
class Mission
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string", length=255)
*/
private $code_name;
/**
* #ORM\Column(type="string", length=255)
*/
private $country;
/**
* #ORM\Column(type="date")
*/
private $start_date;
/**
* #ORM\Column(type="date")
*/
private $end_date;
/**
* #ORM\OneToMany(targetEntity=Agent::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $agents;
/**
* #ORM\OneToMany(targetEntity=Cible::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $cibles;
/**
* #ORM\OneToMany(targetEntity=Contact::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $contacts;
/**
* #ORM\OneToMany(targetEntity=Planque::class, mappedBy="mission", cascade={"persist", "merge"})
*/
private $planques;
/**
* #ORM\ManyToOne(targetEntity=Specialite::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $speciality;
/**
* #ORM\ManyToOne(targetEntity=Status::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $status;
/**
* #ORM\ManyToOne(targetEntity=TypeMission::class, inversedBy="missions", cascade={"persist", "merge"})
*/
private $type;
public function __construct()
{
$this->agents = new ArrayCollection();
$this->cibles = new ArrayCollection();
$this->contacts = new ArrayCollection();
$this->planques = new ArrayCollection();
}
I would like the list of targets to update dynamically according to the choice of agents.
Any help will be welcome and I thank you in advance because I admit going around in circles and no longer know where I am.
I try to validate FormType inside CollectionType with some simple groups rules but It doesn't work, but if i try to make the same without validations groups, it's work fine.
Any idea?
This is a complete and simple exemple that reproduct the error https://github.com/ychakroun/symfony-collection-type-issue
/**
* Sticker
*
* #ORM\Table(name="sticker")
* #ORM\Entity(repositoryClass="App\Repository\StickerRepository")
*/
class Sticker
{
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="App\Entity\Link", mappedBy="sticker", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"position"="ASC"})
* #Assert\Valid()
*/
private $links;
}
/**
* Link
*
* #ORM\Table(name="link")
* #ORM\Entity(repositoryClass="App\Repository\LinkRepository")
*/
class Link
{
/**
* #var mixed
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string|null
* #Assert\NotBlank()
*
* #ORM\Column(name="title", type="string")
*/
private $title;
/**
* #var bool
*
* #ORM\Column(name="external", type="boolean")
*/
private $external;
/**
*
* #var string|null
*
* #Assert\NotBlank(groups={"isExternal"})
* #Assert\Url(groups={"isExternal"})
* #ORM\Column(name="url", type="text", nullable=true)
*/
private $url;
/**
* #var \App\Entity\PageVersion|null
*
* #Assert\NotBlank(groups={"isInternal"})
* #ORM\ManyToOne(targetEntity="App\Entity\PageVersion")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="page_version_id", referencedColumnName="id", nullable=true)
* })
*/
private $pageVersion;
/**
* #var \App\Entity\Category|null
*
* #Assert\NotBlank(groups={"isInternal"})
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="links")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=true)
* })
*/
private $category;
/**
* #var \App\Entity\Sticker|null
*
* #ORM\ManyToOne(targetEntity="App\Entity\Sticker", inversedBy="links")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="sticker_id", referencedColumnName="id", nullable=true)
* })
*/
private $sticker;
}
And this is the forms i use:
class StickerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('links', CollectionType::class, [
'entry_type' => LinkType::class,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
'by_reference' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Sticker::class,
]);
}
}
class LinkType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'label' => 'Titre du menu:',
'attr' => [
'input-group' => 'true',
],
])
->add('external', ChoiceType::class, [
'label' => false,
'expanded' => true,
'choices' => [
'Lien interne' => false,
'Lien externe' => true,
],
'choice_attr' => [
'class' => 'link-type',
],
'label_attr' => [
'class' => 'btn-group btn-group-toggle',
'data-toggle' => 'buttons',
]
])
->add('url', UrlType::class, [
'label' => 'SAISISSEZ L\'URL EXTERNE',
'attr' => ['placeholder' => 'https://'],
])
->add('pageVersion', EntityType::class, [
'required' => false,
'class' => Page::class,
'choice_label' => 'name',
])
->add('category', EntityType::class, [
'required' => false,
'class' => Category::class,
'choice_label' => 'title',
'query_builder' => function (CategoryRepository $er) {
return $er->createQueryBuilder('c')->where('c.enabled = 1');
},
])
->add('position', HiddenType::class, [
'attr' => [
'class' => 'my-position',
],
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Link::class,
'cascade_validation' => true,
'validation_groups' => function (FormInterface $form) {
/** #var Link $link */
$link = $form->getData();
$groups = ['Default'];
if ($link->getExternal()) {
$groups[] = 'isExternal';
} else {
$groups[] = 'isInternal';
}
return $groups;
},
]);
}
}
We can see that the url field is validated and it's blank
If i try to remove groups={"isExternal"} from link entity, the validation will work, like in this image:
I think you need to add the validation groups on the Sticker entity too :
/**
* Sticker
*
* #ORM\Table(name="sticker")
* #ORM\Entity(repositoryClass="App\Repository\StickerRepository")
*/
class Sticker
{
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="App\Entity\Link", mappedBy="sticker", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"position"="ASC"})
* #Assert\Valid(groups={"isInternal", "isExternal"})
*/
private $links;
}
This option is only valid on the root form and is used to specify which groups will be used by the validator.
This is the response https://github.com/symfony/symfony/issues/31441
Hello we must add an addEventListener
class StickerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('links', CollectionType::class, [
'entry_type' => LinkType::class,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
'by_reference' => false,
])
->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
;
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Sticker::class,
]);
}
public function onPreSubmit(FormEvent $event)
{
if ($event->getData()) {
$data = $event->getData();
$data['links'] = array_values($data['links']);
$event->setData($data);
}
}
}
I have a problem with one of my forms. The form has to create a new Colle entity and link some other Colle entities to it.
When I submit it, a new entity is created for each item in collesEnfants collection field. The new entity created is correctly linked to the parent and has the right 'ordre' field but it's a newly created entity and not the entity I've selected.
My form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom', TextType::class,['label' => 'Nom de la colle'])
->add('collesEnfants', CollectionType::class,
['label' => false,
'entry_type' => SousColleFormType::class,
'required' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
SousColleFormType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('matiere', EntityType::class, [
'class' => 'PACESColleBundle:Matiere',
'attr' => ['class'=> 'matiere'],
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une matière',
'mapped' => false])
->add('nom', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'group_by' => 'matiere',
'required' => true,
'placeholder' => 'choose.colle'])
->add('ordre', IntegerType::class,[
'attr'=>['class'=>'ordre'],
'required' => true,
'label' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
Controller :
$formColleMere = $this->createForm(AjoutSuperColleFormType::class, $colle);
$formColleMere->add('submit', SubmitType::class, ['label' => 'Créer']);
$formColleMere->handleRequest($request);
if ($formColleMere->isSubmitted() && $formColleMere->isValid()) {
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant);
}
if (!$colle->getCollesEnfants()->isEmpty()) {
$em->persist($colle);
$em->flush();
}
Colle entity :
class Colle
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Colle", mappedBy="colleMere", cascade={"persist"})
* #ORM\OrderBy({"ordre" = "asc"})
*/
private $collesEnfants;
/**
* #ORM\ManyToOne(targetEntity="Colle", inversedBy="collesEnfants", cascade={"persist"})
* #ORM\JoinColumn(name="colleMere_id", referencedColumnName="id")
*/
private $colleMere;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*/
protected $nom;
{........}
/**
* #ORM\ManyToOne(targetEntity="PACES\ColleBundle\Entity\Matiere", inversedBy="colles", cascade={"persist"})
* #ORM\JoinColumn(name="matiere_id", referencedColumnName="id")
* #ORM\OrderBy({"name" = "ASC"})
*/
protected $matiere;
/**
* Cet attribut sert aux 'super colles' qui sont le résultat d'une fusion de colles d'une même UE
* #var integer
* #ORM\Column(name="ordre", type="integer", nullable=true)
*/
protected $ordre;
I succeeded in doing what I want by adding 'mapped' => false to collesEnfants field.
I also changed these lines in the Controller :
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant);
}
To :
$collesEnfants = $formColleMere->get('collesEnfants')->getData();
foreach ($collesEnfants as $enfant) {
$colle->addColleEnfant($enfant['nom']);
$enfant['nom']->setOrdre($enfant['ordre']);
}
I have a problem with one of my forms. When I submit it, I have this error : Object of class ... could not be converted to string.
I already looked at some other cases like mine but I really don't know what is wrong. Method toString doesn't exist in my entity but I never needed it for all my other forms which look like this one.
My form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('nom', TextType::class,['label' => 'Nom de la colle'])
->add('collesEnfants', CollectionType::class,
['label' => false,
'entry_type' => SousColleFormType::class,
'required' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
SousColleFormType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('matiere', EntityType::class, [
'class' => 'PACESColleBundle:Matiere',
'attr' => ['class'=> 'matiere'],
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une matière',
'mapped' => false])
->add('nom', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'group_by' => 'matiere',
'required' => true,
'placeholder' => 'choose.colle'])
->add('ordre', IntegerType::class,[
'attr'=>['class'=>'ordre'],
'required' => true,
'label' => false]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'PACES\ColleBundle\Entity\Colle'
]);
}
Colle entity :
class Colle
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Colle", mappedBy="colleMere", cascade={"persist"})
* #ORM\OrderBy({"ordre" = "asc"})
*/
private $collesEnfants;
/**
* #ORM\ManyToOne(targetEntity="Colle", inversedBy="collesEnfants", cascade={"persist"})
* #ORM\JoinColumn(name="colleMere_id", referencedColumnName="id")
*/
private $colleMere;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*/
protected $nom;
{........}
/**
* #ORM\ManyToOne(targetEntity="PACES\ColleBundle\Entity\Matiere", inversedBy="colles", cascade={"persist"})
* #ORM\JoinColumn(name="matiere_id", referencedColumnName="id")
* #ORM\OrderBy({"name" = "ASC"})
*/
protected $matiere;
/**
* Cet attribut sert aux 'super colles' qui sont le résultat d'une fusion de colles d'une même UE
* #var integer
* #ORM\Column(name="ordre", type="integer", nullable=true)
*/
protected $ordre;
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