Symfony 6 populate Dropdown with Dynamic entities - php

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

Related

Symfony form: The selected choice is invalid

My symfony app have this entity:
<?php
namespace App\Entity;
class Lead
{
private ?string $zipCode;
private ?string $city;
public function getZipCode(): ?string
{
return $this->zipCode;
}
public function setZipCode(string $zipCode): self
{
$this->zipCode = $zipCode;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(string $city): self
{
$this->city = $city;
return $this;
}
}
And the Form LeadType is :
$builder->add('zipCode', TextType::class, [
'attr' => [
'placeholder' => 'Ex : 44000',
'onchange' => 'cityChoices(this.value)',
]
])
->add('city', ChoiceType::class, [
'choices' => [],
'attr' => ['class' => 'form-control'],
'choice_attr' => function () {return ['style' => 'color: #010101;'];},
]);
When the user enters a zip code, the javascript function cityChoices() add the select options using an external api like:
My problem is in the controller that the form is invalid. Because the user has selected a city that was not offered in the choices of LeadType ('choices' => []).
Here is the error:
0 => Symfony\Component\Form\FormError {#3703 ▼
#messageTemplate: "The selected choice is invalid."
#messageParameters: array:1 [▼
"{{ value }}" => "Bar-le-Duc"
]
How to make the choice valid?
Completly #craight
I add to the form :
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent$event){
$form = $event->getForm();
$city = $event->getData()['city'];
if($city){
$form->add('city', ChoiceType::class, ['choices' => [$city => $city]]);
}
})
When pre submit, I update choices and I put only the user select 'choices' => ['Bar-le-Duc' => 'Bar-le-Duc'],
The form becomes valid in the controller

Symfony Collection of forms

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,
]
);
}

Symfony 6 - Error while inserting a new Entry with a ManyToMany Relationship

I am currently working on a symfony 6 project where I came across the following problem:
I have to entites "Article" and "ColorPalette". The relationship between the both of these is a ManyToMany Relationship.
Now I have a form for creating new Articles where I have to define between 1 and n colors for the article. The select field uses select2 which works perfectly fine for all relationships beside the many to many one. So when I want to submit the form I get the following error:
Entity of type "Doctrine\Common\Collections\ArrayCollection" passed to
the choice field must be managed. Maybe you forget to persist it in
the entity manager?
I was able to find out that this has something to do with the field for the colorPalette which is a ManyToMany Relation but I could not figure out how to solve the issue.
My Current Code:
Article Entity Code:
/**
* #ORM\Entity(repositoryClass=ArticleRepository::class)
*/
class Article
{
...other properties
/**
* #ORM\ManyToMany(targetEntity=ColorPalette::class, inversedBy="articles")
*/
private $colorPalette;
...other functions
/**
* #return Collection|ColorPalette[]
*/
public function getColorPalette(): Collection
{
return $this->colorPalette;
}
public function addColorPalette(ColorPalette $colorPalette): self
{
if (!$this->colorPalette->contains($colorPalette)) {
$this->colorPalette[] = $colorPalette;
}
return $this;
}
public function removeColorPalette(ColorPalette $colorPalette): self
{
$this->colorPalette->removeElement($colorPalette);
return $this;
}
}
ArticleType Code:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title',null, array(
'label' => "title"
))
->add('titleEnglish',null, array(
'label' => "english title"
))
->add('description',null, array(
'label' => "description"
))
->add('stock',null, array(
'label' => "stock"
))
->add('status',ChoiceType::class, [
'label' => "status",
'choices' => [
'Gesperrt' => 'Gesperrt',
'Verfuegbar' => 'Verfuegbar',
'Auslaufartikel' => 'Auslaufartikel',
'Vergriffen' => 'Vergriffen'
],
'attr' => [
'class' => 'dropdown'
]
])
->add('colorPalette', EntityType::class, [
'class' => ColorPalette::class,
'multiple' => true,
'choice_label' => 'title',
'choices' => [],
'attr' => [
'class' => 'dropdown'
],
'label' => 'colors',
])
;
// Add EventSubscribers for specific fields (here: colorPalette)
$builder->addEventSubscriber(new EntityFieldListener($this->colorPaletteRepository,$this->colorPaletteClass,"colorPalette","colorPalette","id","title","colors"));
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Article::class,
]);
}
EntityFieldListener Code:
class EntityFieldListener implements EventSubscriberInterface
{
public function __construct($repository="",$class="",$fieldName="",$table="",$identifier="",$choiceLabel="",$label="") {
$this->repository = $repository;
$this->class = $class;
$this->fieldName = $fieldName;
$this->table = $table;
$this->identifier = $identifier;
$this->choiceLabel = $choiceLabel;
$this->label = $label;
}
public static function getSubscribedEvents(): array {
return [
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit',
];
}
//This Event is used to prepopulate the previous select options for all Select Fields in the corresponding form
public function onPreSetData(FormEvent $event, $test): void
{
// Get the parent form
$form = $event->getForm();
// Get the data for the choice field
$data = $event->getData();
if($data->getId() != null) {
// Get the Id of the currently selected Base Article for the query builder
$functionName = 'get'.ucfirst($this->fieldName);
$selected = $data->$functionName()->getId();
$form->add($this->fieldName, EntityType::class, array(
'class' => $this->class,
'choice_label' => $this->choiceLabel,
'label' => $this->label,
'attr' => [
'class' => 'dropdown'
],
'query_builder' => function () use ($selected){
return $this->repository->createQueryBuilder($this->table)
->where($this->table.'.'.$this->identifier.' = :'.$this->identifier)
->setParameter($this->identifier, $selected);
},
));
}
}
public function onPreSubmit(FormEvent $event) {
// Get the parent form
$form = $event->getForm();
// Get the data for the choice field
$data = $event->getData();
if(isset($data[$this->fieldName]) and $data[$this->fieldName]!=null){
$selected = $data[$this->fieldName];
$form->add($this->fieldName, EntityType::class, array(
'class' => $this->class,
'choice_label' => $this->choiceLabel,
'label' => $this->label,
'attr' => [
'class' => 'dropdown'
],
'query_builder' => function () use ($selected){
//If a parameter is an array then this should be used:
// if(is_array($selected)) {
// $query = $this->repository->createQueryBuilder($this->table);
// $searchQuery="";
// for($i = 0; $i < count($selected); $i++) {
// if($i < count($selected)-1) {
// $searchQuery .= $this->table.'.'.$this->identifier.' = '.$selected[$i].' OR ';
// }
// else {
// $searchQuery .= $this->table.'.'.$this->identifier.' = '.$selected[$i];
// }
// }
// $query->andWhere($searchQuery);
// return $query;
// }
return $this->repository->createQueryBuilder($this->table)
// ->where($this->table.'.id = :id')
->where($this->table.'.'.$this->identifier.' = :'.$this->identifier)
->setParameter($this->identifier, $selected);
},
));
}
}
}
Controller Code (Only the route for inserting new entries):
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager): Response
{
$article = new Article();
$form = $this->createForm(ArticleType::class, $article);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//insert the new article
$this->entityManager->persist($article);
$this->entityManager->flush();
return $this->redirectToRoute($this->routeName.'_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm($this->routeName.'/new.html.twig', [
$this->routeName => $article,
'form' => $form,
]);
}
JS-Code:
//Imports
import {createSelectPicker} from './components/Select2/SelectPicker';
//Document Ready Function
$(function() {
document.getElementById("page-content").style.visibility = "visible";
createSelectPicker({id:'#article_colorPalette',minimumInputLength:1,multiple:true,selectField:'colorPalette',url:url_from_twig_ajax});
} );
Do you have an Idea how to solve this?
I would appreciate any help. :)

Symfony 5.2 : Problems with Argument passed in Dynamically Modifification Form Using Form Events

I try to apply documentation https://symfony.com/doc/current/form/dynamic_form_modification.html but i have some troubles when I receive the result of the first select categoryLevel1Id to collect options in the second select categoryLevel2Id.
I'm new with Doctrine, and I think something escapes me.
With the following code, I have an exception:
Expected argument of type "int", "object" given at property path
"categoryLevel1Id"
$builder->add('categoryLevel1Id', EntityType::class, [
'class' => CategoryLevel1::class,
'label' => 'contrib.create.category_level1',
'placeholder' => 'contrib.create.category_level1_placeholder',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->andWhere("c.langId = ?1")
->andWhere("c.validated = ?2")
->setParameter(1, $this->langService->getLangIdByLang(locale_get_default()))
->setParameter(2, 1);
},
'choice_label' => 'title',
'choice_attr' => function($choice, $key, $value) {
return ['class' => 'text-dark'];
},
]);
With the following code, I have this exception:
Argument 2 passed to
App\Form\Front\Situ\CreateSituFormType::App\Form\Front\Situ{closure}()
must be an instance of App\Entity\CategoryLevel1 or null, int given,
called in ..\src\Form\Front\Situ\CreateSituFormType.php on line 147
$categories = $this->categoryLevel1Repository->findLocaleCategories();
$categoriesOptions = [];
foreach ($categories as $categorie) {
$categoriesOptions[] = [
$categorie['title'] => $categorie['id'],
];
}
$builder->add('categoryLevel1Id', ChoiceType::class, [
'choices' => call_user_func_array('array_merge', $categoriesOptions),
'label' => 'contrib.create.category_level1',
'label_attr' => ['class' => ''],
'placeholder' => 'contrib.create.category_level1_placeholder',
'choice_attr' => function($choice, $key, $value) {
return ['class' => 'text-dark'];
},
]);
I tried to call a CategoryLevel1Type::class instead but it didn't work when code came to $formModifier = function (FormInterface $form.. of course or I did'nt know adapt code!
This is my buildForm() with any __construct before:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'label' => 'contrib.create.title',
'attr' => [
'class' => 'mb-md-4',
'placeholder' => 'contrib.create.title_placeholder'
],
])
->add('description', TextareaType::class, [
'label' => 'contrib.create.description',
'attr' => [
'rows' => '5',
'placeholder' => 'contrib.create.description_placeholder',
],
])
;
// Check locale categories level 1
$categories = $this->categoryLevel1Repository->findLocaleCategories();
// If no category, create it and its subcategory
if (empty($categories)) {
$builder
->add('categoryLevel1Id', CreateCategoryLevel1Type::class, [
'label' => 'category.create.category_level1',
'label_attr' => ['class' => 'pt-0'],
])
->add('categoryLevel2Id', CreateCategoryLevel2Type::class, [
'label' => 'category.create.category_level2',
'label_attr' => ['class' => 'pt-0'],
])
;
} else {
$builder->add('categoryLevel1Id', EntityType::class, [
'class' => CategoryLevel1::class,
'label' => 'contrib.create.category_level1',
'placeholder' => 'contrib.create.category_level1_placeholder',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->andWhere("c.langId = ?1")
->andWhere("c.validated = ?2")
->setParameter(1, $this->langService->getLangIdByLang(locale_get_default()))
->setParameter(2, 1);
},
'choice_label' => 'title',
'choice_attr' => function($choice, $key, $value) {
return ['class' => 'text-dark'];
},
]);
$formModifier = function (FormInterface $form, CategoryLevel1 $categoryLevel1 = null) {
$categoriesLevel2 = null === $categoryLevel1 ? [] :
$categoryLevel1->getGategoriesLevel2();
$form->add('categoryLevel2Id', EntityType::class, [
'class' => 'App\Entity\CategoryLevel2',
'label' => 'contrib.create.category_level2',
'placeholder' => 'contrib.create.category_level2_placeholder',
'choices' => $categoriesLevel2,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCategoryLevel1Id());
}
);
$builder->get('categoryLevel1Id')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$categoryLevel1 = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $categoryLevel1); // (Error code 2 here)
}
);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Situ::class,
'translation_domain' => 'user_messages',
]);
}
Then Entities with Relations:
CategoryLevel1:
.../...
/*
* #ORM\OneToMany(targetEntity="App\Entity\CategoryLevel2", cascade={"persist", "remove"}, mappedBy="categoryLevel1Id")
*/
protected $categoriesLevel2;
/*
* #ORM\OneToMany(targetEntity=Situ::class, cascade={"persist", "remove"}, mappedBy="categoryLevel1")
*/
protected $situs;
public function __construct()
{
$this->categoriesLevel2 = new ArrayCollection();
$this->situs = new ArrayCollection();
}
.../...
public function getGategoriesLevel2()
{
return $this->categoriesLevel2;
}
public function addCategoryLevel2(CategoryLevel2 $categoryLevel2)
{
$this->categoriesLevel2->add($categoryLevel2);
$categoryLevel2->setCategoryLevel1($this);
}
public function getSitus()
{
return $this->situs;
}
public function addSitu(Situ $situ)
{
$this->situs->add($situ);
$situ->setCategoryLevel1($this);
}
CategoryLevel2:
.../...
/**
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="App\Entity\CategoryLevel1", inversedBy="categoriesLevel2")
*/
private $categoryLevel1Id;
/**
* #ORM\Column(type="boolean")
*/
private $validated;
/**
* #ORM\OneToMany(targetEntity=Situ::class, cascade={"persist", "remove"}, mappedBy="categoryLevel2")
*/
protected $situs;
public function __construct()
{
$this->situs = new ArrayCollection();
}
.../...
public function getSitus()
{
return $this->situs;
}
public function addSitu(Situ $situ)
{
$this->situs->add($situ);
$situ->setCategoyLevel2($this);
}
.../...
Situ :
.../...
/**
* #ORM\Column(type="integer")
*/
private $categoryLevel1Id;
/**
* #ORM\Column(type="integer")
*/
private $categoryLevel2Id;
/**
* #ORM\ManyToOne(targetEntity=CategoryLevel1::class, inversedBy="situs")
*/
protected $categoryLevel1;
/**
* #ORM\ManyToOne(targetEntity=CategoryLevel2::class, inversedBy="situs")
*/
protected $categoryLevel2;
.../...
Into SituController, nothing but :
public function createSitu(Request $request, User $user): Response
{
$situ = new Situ();
$form = $this->createForm(CreateSituFormType::class, $situ);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// TODO
}
return $this->render('front/situ/new.html.twig', [
'situ' => $situ,
'form' => $form->createView(),
]);
}
You probably got confused between these 2 entities (CategoryLevel2 and Situ):
In CategoryLevel2 it's a App\Entity\CategoryLevel1 type
while in Situ it's an integer type
You can't use EntityType::class in $builder->add('categoryLevel1Id', EntityType::class, [ because in your CreateSituFormType the field categoryLevel1Id is an integer and not an entity.
In your Situ entity you might change:
/**
* #ORM\Column(type="integer")
*/
private $categoryLevel1Id;
into:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\CategoryLevel1")
*/
private $categoryLevel1Id;
to solve the problem.

Symfony2 Form Collection allow_add and allow_delete null error (Silex)

I've run into a problem when following the Symfony cookbook for form collections with add/remove. See: http://symfony.com/doc/current/cookbook/form/form_collections.html
Now, for some reason, if I dynamically add a form row but don't fill in any of its fields, I get the following error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Project::addTask() must be an instance of Task, null given in D:\web_workspace\wedding\src\testapp.php line 82
I would like people to be able to have blank rows in the form which will just get ignored. For example, if you click "Add Task" a few times, but don't fill in the last row, the form should still be submitted, and the last row should be ignored.
I've created a very simple Silex demo that fits in just a couple of files. I'll highlight it here, but the full example is here, and can be run by just adding Silex via composer: https://gist.github.com/mattsnowboard/7065865
I have the following Models (just Project which has a description and a collection of Tasks)
class Task
{
protected $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}
class Project
{
protected $description;
protected $tasks;
public function __construct()
{
$this->tasks = array();
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
public function getTasks()
{
return $this->tasks;
}
public function addTask(Task $task)
{
if (!is_null($task) && !in_array($task, $this->tasks)) {
$this->tasks[] = $task;
}
return $this;
}
public function removeTask(Task $task)
{
if (!is_null($task)) {
$this->tasks = array_diff($this->tasks, array($task));
}
return $this;
}
}
My forms are as follows
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', array(
'required' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Task',
));
}
public function getName()
{
return 'task';
}
}
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'required' => false
))
->add('submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Project',
));
}
public function getName()
{
return 'project';
}
}
The controller adds some test data to the form and just prints the data when it is submitted:
$app->match('/', function (Request $request) use ($app) {
$project = new Project();
// dummy code
$task1 = new Task();
$task1->setName('A Task');
$project->addTask($task1);
// end dummy code
$form = $app['form.factory']->create(new ProjectType(), $project);
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
$debug = print_r($data, true);
echo $debug;
}
return $app['twig']->render('test.html.twig', array(
'form' => $form->createView(),
));
})
->method('GET|POST');
The view is mostly this, and the javascript sample from the cookbook
{{ form_start(form) }}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tasks" data-prototype="{{ form_widget(form.tasks.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for task in form.tasks %}
<li>{{ form_row(task.name) }}</li>
{% endfor %}
</ul>
{{ form_widget(form.submit) }}
{{ form_end(form) }}
Am I just missing something? Is this a bug? I feel like this example which is very close to the cookbook should work pretty easily...
Updated: Removing "allow_deleted" doesn't actually fix this.
Sorry, I figured it out, the collection type shouldn't have 'required' => false
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'required' => false
))
Should be
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true
))

Categories