Symfony Collection of forms - php

So I embed a Collection of Forms in Symfony with CollectionType, I was trying to, in the same page and in a single form, create an user and an entity called Partner with a OneToOne relation (user_id for the partner), and it's giving me this error :
Expected argument of type "?App\Entity\User", "array" given at property path "user".
Please, help me.
PartnerController.php :
#[Route('/new', name: 'app_partenaire_new', methods: ['GET', 'POST'])]
public function new(Request $request, PartenaireRepository $partenaireRepository,
UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager):
Response
{
$user = new User();
$partenaire = new Partenaire();
$form = $this->createForm(PartenaireType::class, $partenaire);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setRoles(['ROLE_PARTENAIRE']);
$user->setIsActive(true);
/* $user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
)*/;
$partenaire->setUser($user->getId());
$entityManager->persist($user);
$entityManager->persist($partenaire);
$partenaireRepository->add($partenaire, true);
return $this->redirectToRoute('app_partenaire_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('partenaire/new.html.twig', [
'partenaire' => $partenaire,
'form' => $form,
]);
}
Partner.php :
#[ORM\OneToOne(inversedBy: 'partenaire', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
PartnerType.php :
class PartenaireType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array
$options): void
{
$builder
->add('name')
->add('description');
$builder->add('user', CollectionType::class, [
'entry_type' => RegistrationFormType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]
);
}

Related

The property in class can be defined with the methods but the new value must be an array or an instance of \Traversable

i'm actually try to embed forms without CollectionType and i'm stuck with this error :
The property "registration" in class "App\\Entity\\Member" can be defined with the methods "addRegistration()", "removeRegistration()" but the new value must be an array or an instance of \\Traversable.
This error appearing on submit edit form of Member entity.
If I try edit Registration entity directly with its own (RegistrationType class) form the error does not appearing.
Every user can have multiple Registration but can only have one active Registration and can edit only active Registration. If the Member does not have an active Registration then he creates one with check active field of RegistrationType form.
class Member :
[...]
public function removeRegistration(Registration $registration): self
{
if ($this->registrations->removeElement($registration)) {
// set the owning side to null (unless already changed)
if ($registration->getMember() === $this) {
$registration->setMember(null);
}
}
return $this;
}
public function isRegistration(): ?bool
{
for ($i = 0; $i < count($this->registrations); $i++) {
if ($this->registrations[$i]->isActive()) {
return true;
}
}
return false;
}
[...]
Class MemberController :
[...]
#[Route('/{id}/edit', name: 'app_member_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, Member $member, MemberRepository $memberRepository, OperationLabelRepository $operationLabelRepository, FamilyRepository $familyRepository, OperationFamilyRepository $operationFamilyRepository, PricingCalculator $pricingCalculator, PricingRepository $pricingRepository): Response
{
$form = $this->createForm(MemberType::class, $member, [
'families' => $familyRepository->findAll()
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$memberRepository->save($member, true);
return $this->redirectToRoute('app_member_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('member/edit.html.twig', [
'member' => $member,
'form' => $form,
]);
}
[...]
Class MemberType (form)
[...]
->add('registration', RegistrationType::class);
class RegistrationType (subform)
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('active', CheckboxType::class, [
'label' => 'Inscription',
'attr' => [
'switch' => 'true'
],
'required' => false,
])
->add('properties', EntityType::class, [
'class' => RegistrationProperty::class,
'multiple' => true,
'expanded' => true,
]);
}
Thanks for your help !!
sorry if my english is bad.

Symfony 6 populate Dropdown with Dynamic entities

I have a CreditCard entity, on form screen there is a dropdown with user's Bank entities and I want to fill BankAccount dropdown according to selected bank dynamically, I have tried couple solutions from SO and added an event listener to my form but when I change the Bank dropdown I get "Integrity constraint violation: kart_adi (credit card name field on entity) cannot be null. I am using Symfony 6 with php 8.1.2
KrediKarti.php:
class KrediKarti
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToOne(targetEntity: Banka::class)]
#[ORM\JoinColumn(nullable: false)]
private $banka;
#[ORM\ManyToOne(targetEntity: BankaHesap::class, inversedBy: 'krediKartlari')]
#[ORM\JoinColumn(nullable: false)]
private $hesap;
#[ORM\Column(type: 'string', length: 255)]
private $kart_adi;
#[ORM\Column(type: 'string', length: 255)]
private $kart_no;
#[ORM\Column(type: 'integer')]
private $hesap_kesim_gunu;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $aciklama;
#[ORM\Column(type: 'float')]
private $borc;
#[ORM\Column(type: 'float')]
private $alacak;
#[ORM\Column(type: 'datetime_immutable')]
private $created_at;
public function getId(): ?int
{
return $this->id;
}
public function getBanka(): ?Banka
{
return $this->banka;
}
public function setBanka(?Banka $banka): self
{
$this->banka = $banka;
return $this;
}
public function getHesap(): ?BankaHesap
{
return $this->hesap;
}
public function setHesap(?BankaHesap $hesap): self
{
$this->hesap = $hesap;
return $this;
}
public function getKartAdi(): ?string
{
return $this->kart_adi;
}
public function setKartAdi(string $kart_adi): self
{
$this->kart_adi = $kart_adi;
return $this;
}
public function getKartNo(): ?string
{
return $this->kart_no;
}
public function setKartNo(string $kart_no): self
{
$this->kart_no = $kart_no;
return $this;
}
public function getHesapKesimGunu(): ?int
{
return $this->hesap_kesim_gunu;
}
public function setHesapKesimGunu(int $hesap_kesim_gunu): self
{
$this->hesap_kesim_gunu = $hesap_kesim_gunu;
return $this;
}
public function getAciklama(): ?string
{
return $this->aciklama;
}
public function setAciklama(?string $aciklama): self
{
$this->aciklama = $aciklama;
return $this;
}
public function getBorc(): ?float
{
return $this->borc;
}
public function setBorc(float $borc): self
{
$this->borc = $borc;
return $this;
}
public function getAlacak(): ?float
{
return $this->alacak;
}
public function setAlacak(float $alacak): self
{
$this->alacak = $alacak;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): self
{
$this->created_at = $created_at;
return $this;
}
}
KrediKartiType.php:
$builder
->add('kart_adi',TextType::class,['label' => 'Kart Adı','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Kart Adı']])
->add('kart_no',TextType::class,['label' => 'Kart No','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Kart No']])
->add('hesap_kesim_gunu', NumberType::class, [
'label' => 'Hesap Kesim Tarihi', 'attr' =>['class' => 'form-control', 'placeholder' => 'Hesap Kesim Günü']
])
->add('aciklama',TextType::class,['label' => 'Açıklama','required' => false, 'attr' =>['class' => 'form-control', 'placeholder' => 'Açıklama']])
->add('borc', NumberType::class,
[
'label' => 'Borç', 'attr' =>['class' => 'form-control', 'placeholder' => "Borç",'value' => '0.00','style' => 'text-align: right']
])
->add('alacak', NumberType::class,
[
'label' => 'Alacak', 'attr' =>['class' => 'form-control', 'placeholder' => "Alacak",'value' => '0.00','style' => 'text-align: right']
])
->add('banka', EntityType::class, array(
'class' => Banka::class,
'choice_label'=> 'adi',
'placeholder' => 'Lütfen bankanızı seçiniz',
'attr' =>['class' => 'form-control']
))
;
$formModifier = function (FormInterface $form, Banka $banka = null) {
$banka_hesap = null === $banka ? [] : $banka->getBankaHesaplari();
$form->add('hesap', EntityType::class, [
'class' => BankaHesap::class,
'placeholder' => '',
'choices' => $banka_hesap,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getBanka());
}
);
$builder->get('banka')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$banka = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $banka);
}
);
new.html.twig:
<script>
var $banka = $('#kredi_karti_banka');
var $token = $('#kredi_karti__token');
$banka.change(function() {
var $form = $(this).closest('form');
var data = {};
data[$banka.attr('name')] = $banka.val();
data[$token.attr('name')] = $token.val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
complete: function(html) {
// Replace current position field ...
$('#kredi_karti_hesap').replaceWith(
// ... with the returned one from the AJAX response.
$(html.responseText).find('#meetup_position')
);
// Position field now displays the appropriate positions.
}
});
});
</script>
I simply want to fetch bank accounts based on the selected bank on bank change.
You could add options to the form when you create it. So you can create an endpoint where you can create the form with the extra options which could be the bank id and then render only the select element you wish, in your case the bank_accounts select. You can call this endpoint on change event via JS to the banks select. Also you need to modify the KrediKartiType so that the bank_accounts select should take the values from a query builder. For Example:
class BankAccountsController extends AbstractController
{
#[Route('/bank-accounts-select', name: 'bank_accounts_select', methods: ['GET'])]
public function getBankAccountsSelect(Request $request): Response
{
$form = $this->createForm(
KrediKartiType::class,
null,
['bankId' => $request->query->get('bankId')]
);
return $this->render('_bank-accounts-select.html.twig', [
'form' => $form->createView(),
]);
}
}
The form type for the bank_accounts key can look like this:
->add('bank_accounts', EntityType::class, [
'class' => BankaHesap::class,
'query_builder' => function (BankaHesapRepository $bankaHesapRepository) use ($options) {
return $bankaHesapRepository->bankAccountsByBankId((int) $options['bankId']);
},
'label_attr' => ['class' => 'mb-0 d-block'],
])
The Repository method to get the bank accounts from bankId:
public function bankAccountsByBankId(?int $bankId): QueryBuilder
{
return $this->createQueryBuilder('bh')
->join('bh.bank', 'b')
->where('b.id = :bankId')
->setParameter('bankId', $bankId)
->orderBy('bh.name', 'ASC') // this is optional :)
;
}
You would also need the twig file which renders the select element _bank-accounts-select.html.twig:
{{ form_row(form.bank_accounts, {
attr: {
'class': 'mb-3 form-control'
}
}) }}
At last the Js part, where you need to call the endpoint bank-accounts-select, where you replace the select with the one you get from the endpoint:
$bankSelect.on('change', (event) => {
const bankId = event.target.value;
$.ajax({
url: BASE_URL+'/bank-accounts-select',
data: {
bankId: bankId,
},
success: function (html) {
const $bankAccountsSelect = REGION_SELECT_FROM_CLASS_NAME //replace this with your jQuery/Javascript selector;
$bankAccountsSelect.html($(html).find("option"));
$bankAccountsSelect.val("").trigger("change");
},
});
});
This should do it, And I suggest you use English language on your code ;)

Symfony 5 - Notice: Array to string conversion in user create form because of roles [duplicate]

This question already has answers here:
Symfony form - Choicetype - Error "Array to string covnersion"
(3 answers)
Closed 1 year ago.
I'm having trouble solving this problem, when i get in the page for creating a user in my Symfony web app, the user controller complains about the createForm() function and the error only says "Notice: Array to string conversion".
The form is the default created with the crud, actually everything is almost default so I can´t think on a single reason this thing doesn´t work as intended.
I've tried to modify the form to use a multiple selection but that didn´t work either.
And I tried to modify the data type in the entity but that just didn´t even worked because it throwed an error in the file immediately.
Here is the UserType.php (form generator)
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('roles', ChoiceType::class, [
'label' => 'Tipo de usuario',
'attr' => [
'multiple' => true,
'class'=>'form-control myselect'
],
'choices' => [
'Admin' => 'ROLE_ADMIN',
'Usuario' => 'ROLE_USER'
],
])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
The user controller in the breackpoint where Symfony shows the error:
/**
* #Route("/new", name="user_new", methods={"GET","POST"})
*/
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user); // <-- This is the highlighted part.
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('user_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('user/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
And the user entity in the fragments where i declare the role variable and the getter and setter:
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
$roles is an array, you can see it in your configuration :
/**
* #ORM\Column(type="json")
*/
private $roles = [];
Even if we have type="json", it will be used as an array but in your database it will be a regular json.
ChoiceType here is a string so you can't use it in $roles.
You can add a data transformer (see similar response) to convert the value when displaying it in your select or when using it with your entity.
The simple way to do it is using a CallBackTransformer directly in your form type :
use Symfony\Component\Form\CallbackTransformer;
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
fn ($rolesAsArray) => count($rolesAsArray) ? $rolesAsArray[0]: null,
fn ($rolesAsString) => [$rolesAsString]
));
Or if your are not using PHP 7.4+
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesAsArray) {
return count($rolesAsArray) ? $rolesAsArray[0]: null;
},
function ($rolesAsString) {
return [$rolesAsString];
}
));

Symfony 4. Why does submitted Form just partially populate the Model?

A bit in the panic - I am generating Symfony form for a complex search, i.e. mapped data to the entity will be used just for a search query building.
I create simple form, model, some extended types from ChoiceType for prepopulation choices by some logic. The form is submitted with GET method.
In the model you find maker and model fields for example. The latter populated on the frontend with AJAX, after maker has been selected. When I do submit the form, and maker and model have non-default value, the handleRequest only populates the maker property of the Model, but the model is left empty. Also the checkboxes are correctly populated if checked. All in all, $form->getData() returns just Maker and checkboxes, other fields are null. $request->query has all parameters.
The data mappers are senseless here. And also there is nothing to transform in the data, the Model is mostly from scalar values. The request contains everything, but it is not handled correctly. I tried to implement ChoiceLoaderInterface, but that doesn't work for me, because during loading choices I have to have access to the options of the form, which I don't (I used this article https://speakerdeck.com/heahdude/symfony-forms-use-cases-and-optimization).
I am using Symfony 4.2.4; PHP 7.2.
Controller's method
/**
* #Route("/search/car", name="car_search", methods={"GET"})
* #param Request $request
*/
public function carSearchAction(Request $request)
{
$carModel = new CarSimpleSearchModel();
$form = $this->createForm(CarSimpleSearchType::class, $carModel);
$form->handleRequest($request);
$form->getData();
.....
}
CarSimpleSearchModel
class CarSimpleSearchModel
{
public $maker;
public $model;
public $priceFrom;
public $priceTo;
public $yearFrom;
public $yearTo;
public $isCompanyOwner;
public $isPrivateOwners;
public $isRoublePrice;
}
CarSimpleSearchType the form
class CarSimpleSearchType extends AbstractType
{
protected $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('vehicle_type', HiddenType::class, [
'data' => VehicleTypeType::CAR,
'mapped' => false,
])
->add('maker', CarMakerSelectType::class)
->add('model', CarModelsSelectType::class)
->add(
'priceFrom',
VehiclePriceRangeType::class,
[
'vehicle_type' => VehicleTypeType::CAR,
]
)
->add(
'priceTo',
VehiclePriceRangeType::class,
[
'vehicle_type' => VehicleTypeType::CAR,
]
)
->add(
'yearFrom',
VehicleYearRangeType::class,
[
'vehicle_type' => VehicleTypeType::CAR,
]
)
->add(
'yearTo',
VehicleYearRangeType::class,
[
'vehicle_type' => VehicleTypeType::CAR,
]
)
->add('isCompanyOwner', CheckboxType::class)
->add('isPrivateOwners', CheckboxType::class)
->add('isRoublePrice', CheckboxType::class)
->add('submit', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => CarSimpleSearchModel::class,
'compound' => true,
'method' => 'GET',
'required' => false,
'action' => $this->urlGenerator->generate('car_search'),
]
);
}
public function getBlockPrefix()
{
return 'car_search_form';
}
}
CarMakerSelectType field
class CarMakerSelectType extends AbstractType
{
/**
* #var VehicleExtractorService
*/
private $extractor;
/**
* VehicleMakerSelectType constructor.
*
* #param VehicleExtractorService $extractor
*/
public function __construct(VehicleExtractorService $extractor)
{
$this->extractor = $extractor;
}
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'placeholder' => null,
'vehicle_type' => null,
'choices' => $this->getVariants(),
]
);
}
private function getVariants()
{
$makers = $this->extractor->getMakersByVehicleType(VehicleTypeType::CAR);
$choices = [];
foreach ($makers as $maker) {
$choices[$maker['name']] = $maker['id'];
}
return $choices;
}
}
CarModelSelectType field
class CarModelsSelectType extends AbstractType
{
private $extractor;
public function __construct(VehicleExtractorService $extractor)
{
$this->extractor = $extractor;
}
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'disabled' => true,
]
);
}
}
VehiclePriceRangeType field
class VehiclePriceRangeType extends AbstractType
{
private $extractor;
public function __construct(VehicleExtractorService $extractor)
{
$this->extractor = $extractor;
}
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'vehicle_type' => null,
]
);
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
foreach ($this->getRange($options['vehicle_type']) as $value) {
$view->vars['choices'][] = new ChoiceView($value, $value, $value);
}
}
private function getRange(int $vehicleType)
{
return PriceRangeGenerator::generate($this->extractor->getMaxVehiclePrice($vehicleType));
}
}
VehicleYearRangeType field
class VehicleYearRangeType extends AbstractType
{
private $extractor;
public function __construct(VehicleExtractorService $extractorService)
{
$this->extractor = $extractorService;
}
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'vehicle_type' => null,
]
);
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
foreach ($this->getRange($options['vehicle_type']) as $value) {
$view->vars['choices'][] = new ChoiceView($value, $value, $value);
}
}
protected function getRange(int $vehicleType): array
{
$yearRange = RangeGenerator::generate(
$this->extractor->getMinYear($vehicleType),
$this->extractor->getMaxYear($vehicleType),
1,
true,
true
);
return $yearRange;
}
}
So, I can use the raw data from the Request and manually validate-populate the model and send to further processing, but I guess that's not the Right Way, and I want to populated the form by the framework. How can I ?..
In my case, I had a dependent EntityType populated by ajax that is initially disabled. Since choices where null, it was returning an InvalidValueException on submission. What I had to do is create an EventListener and add the valid choices for the current 'main' field. This is basically it, more or less adapted to your case.
Original form:
// Setup Fields
$builder
->add('maker', CarMakerSelectType::class)
->add('model', CarModelsSelectType::class, [
'choices' => [],
// I was setting the disabled on a Event::PRE_SET_DATA if previous field was null
// since I could be loading values from the database but I guess you can do it here
'attr' => ['disabled' => 'disabled'],
]
);
$builder->addEventSubscriber(new ModelListener($this->extractor));
Event Subscriber that adds back valid choices:
class ModelListener implements EventSubscriberInterface
{
public function __construct(VehicleExtractorService $extractor)
{
$this->extractor = $extractor;
}
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SUBMIT => 'onPreSubmitData',
];
}
public function onPreSubmitData(FormEvent $event)
{
// At this point you get only the scalar values, Model hasn't been transformed yet
$data = $event->getData();
$form = $event->getForm();
$maker_id = $data['maker'];
$model= $form->get('model');
$options = $model->getConfig()->getOptions();
if (!empty($maker_id)) {
unset($options['attr']['disabled']);
$options['choices'] = $this->extractor->getModelsFor($maker_id);
$form->remove('model');
$form->add('model', CarModelsSelectType::class, $options );
}
}
}
}

setting 'by reference' to false, on symfony form throws 'Could not determine access type'

I'm trying to create a form for the creation of a product in sylius. I want to add a collection of "PackItem".
However,only the last item is added and when I add "by_reference" => false I've got this issue
Could not determine access type for property "products".
This is my code
#ProductTypeExtension.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var PackItem $packItem */
$packItem = new PackItem();
$packItem->setParent($builder->getData());
$builder
->add('products', CollectionType::class, [
'entry_type' => PackItemType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => [
'data' => $packItem
],
'by_reference' => false,
]);
}
/**
* {#inheritdoc}
*/
public function getExtendedType()
{
return ProductType::class;
}
PackItemType.php
#PackItemType.php
final class PackItemType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('child', 'entity', [
'label' => 'winzana.ui.token',
'class' => Product::class,
'query_builder' => function(EntityRepository $er) {
$qr = $er->createQueryBuilder('t')
->leftJoin('t.products', 'p')
->having('COUNT(p.parent) = 0')
->groupBy('t.id')
->orderBy('t.code', 'ASC')
;
return $qr;
}
])
->add('quantity', IntegerType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PackItem::class
]);
}
Product :
class Product extends BaseProduct
{
/**
* #ORM\OneToMany(targetEntity="XXXX\PackBundle\Entity\PackItem", mappedBy="parent", cascade={"persist"})
* #var ArrayCollection|PackItem $products
*/
private $products;
Thank you for your time
You can try to initialise your products in the __construct() method of your class product
public function __construct()
{
$this->products= new ArrayCollection();
}
if it does not correct the problem, then check if you set correctly the getProducts(), setProducts() and addProduct().
You can check this page for information,
http://symfony.com/doc/current/best_practices/business-logic.html#doctrine-mapping-information
regards.
The problem was solved by this change
/**
* #param ArrayCollection|PackItem[] $products
*/
public function setProducts($products)
{
$this->products = $products;
}
I don't use the setter so I didn't made it however by_references needs it.
Now I've got an other problem, only the last Item is saved.

Categories