Symfony - ManyToMany - replaces instead of adding - symfony

I apologize in advance if my question seems silly to you, I'm a beginner, I've searched but I can't find the answers.
We are in a factory. In this factory, each worker can have several posts, and each posts can contain several workers. So we are in a ManyToMany relationship. The problem is that when I add a worker to a post, he doesn't add to the worker already present in this post, he replaces him! As if a post could only contain one worker.
Can someone tell me what I'm doing wrong or send me precisely the documentation related to this type of problem?
Thanks.
Here is the related code.
(Poste = Post, Operateur = Worker)
in the Post Entity :
/**
* #ORM\ManyToMany(targetEntity=Operateur::class, inversedBy="postes")
* #ORM\JoinTable(name="poste_operateur")
*/
private $operateurs;
/**
* #return Collection|Operateur[]
*/
public function getOperateurs(): Collection
{
return $this->operateurs;
}
public function addOperateur(Operateur $operateur): self
{
if (!$this->operateurs->contains($operateur)) {
$this->operateurs[] = $operateur;
$operateur->addPoste($this);
}
return $this;
}
public function removeOperateur(Operateur $operateur): self
{
$this->operateurs->removeElement($operateur);
$operateur->removePoste($this);
return $this;
}
In the Operateur (worker) entity :
/**
* #ORM\ManyToMany(targetEntity=Poste::class, mappedBy="operateurs")
*/
private $postes;
/**
* #return Collection|Poste[]
*/
public function getPostes(): Collection
{
return $this->postes;
}
public function addPoste(Poste $poste): self
{
if (!$this->postes->contains($poste)) {
$this->postes[] = $poste;
$poste->addOperateur($this);
}
return $this;
}
public function removePoste(Poste $poste): self
{
if ($this->postes->removeElement($poste)) {
$poste->removeOperateur($this);
}
return $this;
}
In the PosteController, method to add an operateur to a post :
/**
* #Route("/{id}/new", name="poste_ope", methods={"GET", "POST"})
*/
public function addOpe(Request $request, Poste $poste): Response
{
$form = $this->createForm(PosteType2::class, $poste);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
$this->addFlash(
'success',
"L'opérateur a bien été ajouté au poste {$poste->getNom()} !"
);
return $this->redirectToRoute('operateur_index');
}
return $this->render('poste/addope.html.twig', [
'poste' => $poste,
'form' => $form->createView(),
]);
}
The form in PostType2 :
class PosteType2 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('operateurs', EntityType::class, [
'class' => Operateur::class,
'label' => 'ajouter un opérateur à ce poste',
'choice_label' => 'nom',
'multiple' => true,
'expanded' => true,
])
->add('save', SubmitType::class, [
'label' => 'Enregistrer',
'attr' => [
'class' => 'btn btn-primary'
]
]);
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Poste::class,
]);
}
}

The problem was in the PosteController, here is the correction :
add an addPost
Here is the documentation who helped me : https://symfony.com/doc/current/doctrine/associations.html#saving-related-entities

Related

Symfony 5 - DataTransformer with Form Validation Constraint

I build a FormType in Symfony5 and make use of DataTransformer on 2 fields :
compagnie_princ
compagnie_sec.
DataTransformer basically takes an object ID and renders It to a label.
DataTransformer works fine, when Form is initially rendered on browser, see below capture:
Problem is after validation callbacks are executed, If an error occured, It fails to transform my Id back to a text value.
Code samples (most important parts) :
AccordCommercialFormType.php
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('compagnie_princ', TextType::class,
[
'label' => 'forms.parameter.accord.compagnie_princ',
]
)
->add('compagnie_sec', TextType::class,
[
'label' => 'forms.parameter.accord.compagnie_sec',
]
);
/** ... **/
$builder>addEventListener(
FormEvents::PRE_SET_DATA,
[$this, 'onPreSetData']
)
->addEventListener(
FormEvents::PRE_SUBMIT,
[$this, 'onPreSubmit']
);
$builder->get('compagnie_princ')->addModelTransformer($this->transformer);
$builder->get('compagnie_sec')->addModelTransformer($this->transformer);
}
Events are captured on preSubmit to fetch ID, because fields 'compagnie_princ' and 'compagnie_sec' are autocompleted with AJAX, populating hidden inputs. My guess is something is going wrong on that part.
public function onPreSetData(FormEvent $event): void
{
$form = $event->getForm();
$form->add('compagnie_princ_id', HiddenType::class,
['mapped' => false,
'attr' => ['class' => 'hidden-field'],
'data' => $event->getData()->getCompagniePrinc() ? $event->getData()->getCompagniePrinc()->getId() : null
]
);
$form->add('compagnie_sec_id', HiddenType::class,
['mapped' => false,
'attr' => ['class' => 'hidden-field'],
'data' => $event->getData()->getCompagnieSec() ? $event->getData()->getCompagnieSec()->getId() : null,
]
);
}
public function onPreSubmit(FormEvent $event): void
{
$data = $event->getData();
$data['compagnie_princ'] = (int)$data['compagnie_princ_id'];
$data['compagnie_sec'] = (int)$data['compagnie_sec_id'];
$event->setData($data);
}
CompagnieToIdTransformer.php
class CompagnieToIdTransformer implements DataTransformerInterface
{
public function __construct(private EntityManagerInterface $em){
}
public function transform($compagnie)
{
if (null === $compagnie) {
return '';
}
return $compagnie->getCodeIata();
}
public function reverseTransform($compagnieId):?Compagnie
{
if (!$compagnieId) {
return null;
}
$compagnie = $this->em
->getRepository(Compagnie::class)
->find($compagnieId)
;
if (null === $compagnie) {
throw new TransformationFailedException(sprintf(
'A company with number "%s" does not exist!',
$compagnieId
));
}
return $compagnie;
}
}

Dependants fields inside collectionType

What I Have ...
Got an OperationForm containing a collection of PaymentEmbeddedForm.
What I intend to do
I want to add some dependant fields in my entries inside the collection of PaymentEmbeddedForm.
The wall i'm hitting.
If some fields are dynamically added inside one of my PaymentEmbeddedForm then when the OperationForm (the root form) is submited, it doesn't recognize the new data, only the field from which the other dependants fields are bound.
Example
OperationFrom
field1
field2
field3 : (Collection of PaymentEmbeddedForm)
Payment1 (PaymentEmbeddedForm)
Payment1.field1
Payment1.field2
Payment1.field3 (dynamically added)
Payment2 (PaymentEmbeddedForm)
Payment2.field1
Payment2.field2
In this case, the data of Payment1.field3 will not be bound at my form ....
What I've tried
Events manipulation inside PaymentEmbeddedForm
If I create an independant PaymentEmbeddedForm, there is no problème, the dependants fields are well bound at my form.
Events manipulation inside OperationForm
When I debug the data of the form with dump($data), I can't see the data of the dependants fields. They are always null at first submition. But once the form has been submitted at least once, the following submition is OK, the data is well bound.
My code
//****************************************
//PaymentEmbeddedForm.php
//****************************************
class PaymentEmbeddedForm extends AbstractType
{
/** #var AccountRepository $accountRepo*/
private $accountRepo;
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var Organization $org */
$org = $options['organization'];
$builder
->add('method', EntityType::class, [
'placeholder' => '-- Choississez un moyen de paiement --',
'class' => PaymentMethod::class,
'choice_label' => 'displayName',
'choice_value' => 'name'
])
;
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($org) {
return $this->onPreSetData($event, $org);
})
->get('method')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($org) {
return $this->methodOnPostSubmit($event, $org);
})
;
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => Payment::class,
'organization' => false
]);
$resolver->setAllowedTypes('organization', 'App\Entity\Core\Organization');
}
public function onPreSetData(FormEvent $event, Organization $org)
{
/** #var Payment $payment */
$payment = $event->getData();
if (!$payment)
return;
$this->setupSpecificMethodFields($event->getForm(), $payment->getMethod(), $org);
return;
}
public function methodOnPostSubmit(FormEvent $event, ?Organization $org)
{
$form = $event->getForm();
$this->setupSpecificMethodFields(
$form->getParent(),
$form->getData(),
$org
);
}
private function setupSpecificMethodFields(FormInterface $form, ?PaymentMethod $method, ?Organization $org)
{
// $form
// ->remove('account')
// ->remove('amount')
// ->remove('iban')
// ->remove('number')
// ->remove('paidAt')
// ->remove('isPaid')
// ;
if (null === $method) {
return;
}
$this->setupAmountField($form);
$this->setupPaidAtField($form);
$this->setupIsPaidField($form);
if (in_array($method->getName(), ['cash', 'credit_card', 'check', 'transfer', 'direct_debit', 'direct_debit_in_installments', 'interbank_payment_order']))
$this->setupAccountField($form, $method, $org);
if (in_array($method->getName(), ['check', 'voucher']))
$this->setupNumberField($form);
if (in_array($method->getName(), ['direct_debit', 'direct_debit_in_installments']))
$this->setupIbanField($form);
}
private function setupPaidAtField(FormInterface $form): void
{
$form->add('paidAt', DateType::class);
return;
}
private function setupIsPaidField(FormInterface $form): void
{
$form->add('isPaid');
return;
}
private function setupAccountField(FormInterface $form, PaymentMethod $method, Organization $org): void
{
$accountChoices = new ArrayCollection();
switch($method->getName()) {
case 'cash':
$accountChoices = $org->getBankAccounts();
break;
case 'credit_card':
case 'check':
case 'transfer':
case 'direct_debit':
case 'direct_debit_in_installments':
case 'interbank_payment_order':
$accountChoices = $org->getBankAccounts();
break;
}
$form->add('account', AccountType::class, [
'choices' => $accountChoices
]);
}
private function setupAmountField(FormInterface $form): void
{
$form->add('amount', MoneyType::class);
return;
}
private function setupIbanField(FormInterface $form): void
{
$form->add('iban');
}
private function setupNumberField(FormInterface $form): void
{
$form->add('number');
}
}
//****************************************
// OperationFormType.php
//****************************************
class OperationFormType extends AbstractType
{
/** #var AccountRepository $accountRepo*/
private $accountRepo;
public function __construct(EntityManagerInterface $em)
{
$this->accountRepo = $em->getRepository(Account::class);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('amount', MoneyType::class)
->add('description', TextareaType::class)
->add('account', AccountType::class, [
'choices' => $this->accountRepo->getChildren($this->accountRepo->findOneBy([
'code' => $options['type'] == 'income' ? 7 : 6,
'chart' => $options['organization']->getChart()->getId()
]), false, null, 'ASC', false)
])
->add('operateAt', DateType::class)
->add('payments', CollectionType::class, [
'entry_options' => [
'organization' => $options['organization']
],
'entry_type' => PaymentEmbeddedForm::class,
'allow_delete' => true,
'allow_add' => true,
'prototype' => true
])
->add('reset', ResetType::class, [
'label' => 'Réinitialiser'
])
->add('submit', SubmitType::class, [
'label' => 'Enregistrer'
])
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'data_class' => OperationFormModel::class,
'organization' => false,
'type' => null
]);
$resolver
->setAllowedTypes('organization', 'App\Entity\Core\Organization')
->setAllowedTypes('type', ['string', 'null'])
->setAllowedValues('type', ['expense', 'income', null])
;
}
public function onPreSetData(FormEvent $event)
{
/** #var OperationFormModel $data */
$data = $event->getData();
if ($data->getPayments()->isEmpty()) {
$newPayment = new Payment();
$newPayment->setPaidAt(new DateTime());
$data->addPayment($newPayment);
}
}
}
Thanks for you're help.
Bdisklaz

EntityType - Can not use query_builder

I have a problem with my FormType in Symfony. I have a field that allows me to check one or more checkboxs whose values represent objects from another entity. (In this case, types of holidays).
Except that I do not want to display them all, so I use a query_builder as such:
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
But it raised this error :
The name "Heures supp" contains illegal characters. Names should start
with a letter, digit or underscore and only contain letters, digits,
numbers, underscores ("_"), hyphens ("-") and colons (":").
However, if I remove the query_builder, I have all my TypeConge ( the "Heures supp" aswell).
GestionSoldes.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class GestionSoldes
{
/**
* Types de congés
*
* #var Collection|TypeConge[]
*/
private $typesConges;
/**
* All types
*
* #var boolean
*/
private $allTypes;
public function __construct()
{
$this->typesConges = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection|TypeConge[]
*/
public function getTypesConges(): Collection
{
return $this->typesConges;
}
public function addTypesConge(TypeConge $typesConge): self
{
if (!$this->typesConges->contains($typesConge)) {
$this->typesConges[] = $typesConge;
}
return $this;
}
public function removeTypesConge(TypeConge $typesConge): self
{
if ($this->typesConges->contains($typesConge)) {
$this->typesConges->removeElement($typesConge);
}
return $this;
}
public function getAllTypes(): ?bool
{
return $this->allTypes;
}
public function setAllTypes(bool $allTypes): self
{
$this->allTypes = $allTypes;
return $this;
}
}
Form:
<?php
namespace App\Form;
use App\Entity\TypeConge;
use App\Entity\GestionSoldes;
use App\Repository\TypeCongeRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class GestionSoldesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('typesConges', EntityType::class, [
'class' => TypeConge::class,
'choice_label' => 'nom',
'expanded' => true,
'multiple' => true,
'query_builder' => function (TypeCongeRepository $repoTypes) {
return $repoTypes->getTypesNotNull();
}
])
->add('allTypes', CheckboxType::class, [
'required' => false,
'label' => 'Tous les types de congés',
'label_attr' => [
'class' => 'custom-control-label',
'for' => 'allTypes'
],
'attr' => [
'class' => 'custom-control-input',
'id' => 'allTypes'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => GestionSoldes::class,
]);
}
}
My repo function:
/**
* Retourne les types de congés qui ont un solde initial différent de null ( pour form EntityType )
*
* #return void
*/
public function getTypesNotNull()
{
return $this->createQueryBuilder('t')
->where('t.soldeInitial is not null')
->orderBy('t.nom', 'ASC');
}
As it shows no errors when you remove te query-builer, The problem must come from the query-builder.
As the only moment you use the property nom in the query-builder is in
->orderBy('t.nom', 'ASC');
The problem is here.
Make sure that your ORM (doctrine or else) is ok with string containing spaces.
to verify my point try to remove the orderBy from your query

How to set a value for entity field in a form? Symfony2

I have 3 tables realated, inProducto, inProveedor and InProveedorProducto, here my relashionship:
inProveedorProducto:
class InProveedorProducto
{
/**
* #ORM\ManyToOne(targetEntity="InProducto", inversedBy="InProveedorProducto")
* #ORM\JoinColumn(name="id_producto", referencedColumnName="id_producto")
*/
protected $producto;
/**
* #ORM\ManyToOne(targetEntity="InProveedor", inversedBy="InProveedorProducto")
* #ORM\JoinColumn(name="id_proveedor", referencedColumnName="id")
*/
protected $proveedor;
InProveedor:
class InProveedor
{
/**
* #ORM\OneToMany(targetEntity="InProveedorProducto", mappedBy="InProveedor", cascade={"persist"})
*/
protected $proveedorProducto;
public function __construct()
{
$this->proveedorProducto = new ArrayCollection();
}
And InProducto:
class InProducto
{
/**
* #ORM\OneToMany(targetEntity="InProveedorProducto", mappedBy="InProducto", cascade={"persist"})
*/
protected $producto;
public function __construct()
{
$this->producto = new ArrayCollection();
}
My problem is that I have to open a new Form for inProveedorProducto, but idProveedor field should be automatic from prior selection of user.
My controller:
public function newAction()
{
$entity = new InProveedorProducto();
$form = $this->createCreateForm($entity);
$form->get('idProveedor')->setData(1);
return $this->render('NivalInventarioBundle:InProveedorProducto:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
When I open de form, the first field which is idProveedor appears filled out with the number 1.
THen we select the idProducto and put the price, but when try to create:
Error:
An exception occurred while executing 'INSERT INTO in_proveedor_producto (id_proveedor, id_producto, precio_compra) VALUES (?, ?, ?)' with params [null, 21, 1]:
It seems like idProveedor is comming NULL.
My inProveedorProducto type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('idProveedor')
->add('producto', 'entity', array(
'class' => 'NivalInventarioBundle:InProducto',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.nombre', 'ASC');
},
'choice_label' => 'nombre',
'by_reference' => true,
'expanded' => false,
'placeholder' => 'Seleccione una opción',
'mapped' => true,
'multiple' => false
))
->add('precioCompra')
;
Please help me.
Regards,
Operate with a model, not with a form data.
public function newAction()
{
$entity = new InProveedorProducto();
$entity->setProveedor($this->get('doctrine')->getRepository('NivalInventarioBundle:InProveedor')->find(1));
$form = $this->createCreateForm($entity);
return $this->render('NivalInventarioBundle:InProveedorProducto:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
And then just
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('proveedor')
->add('producto')
->add('precioCompra')
;

Symfony2 Dynamic Form Modification not saving generated data

I'm going crazy because if I choose a client from an entity field, it correctly populate the second entity field called proposals. Then I choose the proposal dynamically generated, but when I save the form it saves the form correctly but without filling the proposal field. I followed the Symfony Tutorial about the Dynamic Forms which can be found here
This is my FormType code:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('client', 'entity', array(
'class' => 'AppBundle\Entity\Client',
'property' => 'name',
'label' => 'Client:',
'empty_value' => '',
'required' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.name', 'ASC');
},
));
$formModifier = function (FormInterface $form, Client $client = null) {
$proposals = null === $client ? array() : $this->em->getRepository('AppBundle:Proposals')->findBy(
array('client'=>$client->getId()),
array('id' => 'DESC'));
$form->add('proposal', 'entity', array(
'class' => 'AppBundle\Entity\Proposal',
'choice_label' => 'subject',
'placeholder' => '',
'choices' => $proposals,
'label' => 'Proposal',
'required' => false
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$client = null;
$data = $event->getData();
if(!empty($data)) {
$client = $data->getClient();
}
$formModifier($event->getForm(), $client );
}
);
$builder->get('client')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$client = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $client);
}
);
This is the Prenotazione Entity, the one who belong the form.
class Prenotazione {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="prenotazioni")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
/**
* #ORM\OneToOne(targetEntity="Proposal", inversedBy="prenotazione")
* #ORM\JoinColumn(name="proposal_id", referencedColumnName="id")
*/
private $proposal;
public function getId() {
return $this->id;
}
public function setProposal(\AppBundle\Entity\Proposal $proposal = null)
{
$this->proposal = $proposal;
return $this;
}
public function getProposal() {
return $this->proposal;
}
public function setClient(\AppBundle\Entity\Client $client = null)
{
$this->client = $client;
return $this;
}
public function getClient()
{
return $this->client;
}
}
Where am I wrong ?
Are you sure your proposals query is correct?
$proposals = null === $client ? array() : $this->em->getRepository('AppBundle:Proposals')->findBy(
array('client'=>$client->getId()),
array('id' => 'DESC'));
Shouldn't this be either array('client_id' => $client->getId()), or array('client' => $client),?
Try checking the actual content of $proposals by adding a dump($proposals) just below and looking up the result in the symfony profiler.

Resources