Symfony 3.4 Doctrine 1.9
I have found a lot of articles about this. But nothing could resolve my
problem.
I have two Entities User and Company which are mapped as Many2Many.
Both Entities should be filled with a form submit. Therefor I have the following FormTypes:
CustomerRegistrationType
This includes the CollectionType with entry_type of CompanyFormType, which returns the Company:class
class CustomerRegistrationType extends AbstractType
{
private $translator;
private $session;
public function __construct(TranslatorInterface $translator,Session $session)
{
$this->session = $session;
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('mandant',HiddenType::class,array('data'=>
->add('newsletter',CheckboxType::class,array('label' => false,'required' => false))
->add('company', CollectionType::class, array('entry_type' => CompanyFormType::class, 'entry_options' => array('label' => false)));
}
CompanyFormType
class CompanyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('telephone', TextType::class,array('required' => false))
->add('company_name', TextType::class,array('label' => false,'required' => false))
->add('company_supplement', TextType::class,array('label' =>
->add('send_info', CheckboxType::class,array('label' => false,'required' => false))
->add('send_invoice', CheckboxType::class,array('label' => false,'required' => false))
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Company'));
}
}
Many2Many mapping
class User extends BaseUser
{
public function __construct()
{
parent::__construct();
$this->company = new ArrayCollection();
}
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Company",inversedBy="users")
* #ORM\JoinTable(name="user_comp_comp_user",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $company;
public function getCompany()
{
return $this->company;
}
public function setCompany($company)
{
$this->company = $company;
return $this;
}
public function addCompany(Company $company)
{
if ($this->company->contains($company)) {
return;
}
$this->company[] = $company;
return $this;
}
public function removeCompany(Company $company)
{
if (!$this->company->contains($company)) {
return;
}
$this->company->removeElement($company);
return $this;
}
class Company
{
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* #var \Doctrine\Common\Collections\Collection|Company[]
* #ORM\ManyToMany(targetEntity="User",mappedBy="company")
*/
protected $users;
}
In the Controller I want to save all Data to the different Entities.
Therefor I do:
public function registerAction(Request $request)
{
$user = $this->userManager->createUser();
$form = $this->createForm(CustomerRegistrationType::class,$user);
$form->handleRequest($request);
$company = $user->getCompany();
$company->setSendInvoice(true);
$company->setSendInfo(true);
if ($form->isSubmitted()){
$user->addCompany($company);
$this->userManager->updateUser($user);
....
}
return $this->render('Registration/register.html.twig',[
'form' => $form->createView()
]);
}
I does not matter, which way I try, eachtime I get an error. In this example, the company data is empty. Other ways are in conflict with the ArrayCollection() of $this->company in User()
User {#907 ▼
#id: null
#company: ArrayCollection {#926 ▼
-elements: []
}
#groups: null
#mandant: null
#salutation: null
#first_name: null
#last_name: null
#fullname: null
#created: DateTime #1544539763 {#901 ▶}
#telephone: null
Update:
In Twig form I have no access to
{{ form_widget(form.company.company_name, {'attr': {'class': 'form-control'}})}}
Neither the property "company_name" nor one of the methods "company_name()", "getcompany_name()"/"iscompany_name()"/"hascompany_name()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".
This comes up before form submit.
I found the solution here:
https://groups.google.com/forum/#!topic/symfony2/DjwwzOfUIuQ
So I used an array based form in the controller
$form = $this->createFormBuilder()
->add('user', CustomerRegistrationType::class, array(
'data_class' => 'AppBundle\Entity\User'
))
->add('company', CompanyFormType::class, array(
'data_class' => 'AppBundle\Entity\Company'
))
->getForm();
The different Objects can catched like this:
$form->handleRequest($request);
$user = $form->get('user')->getData();
$company = $form->get('company')->getData();
and then
if ($form->isSubmitted()){
...
$user->addCompany($company);
$this->userManager->updateUser($user);
}
That's it
Related
i have 2 entities:
Domain:
class Domain
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 191, unique: true)]
#[Assert\NotBlank(message: 'domain.name.not_blank')]
private ?string $name = null;
#[ORM\ManyToMany(targetEntity: Bill::class, inversedBy: 'domains', cascade: ['persist', 'remove'])]
private Collection $bill;
public function __construct()
{
$this->bill = new ArrayCollection();
}
public function __toString()
{
return $this->name;
}
/**
* #return Collection<int, Bill>
*/
public function getBill(): Collection
{
return $this->bill;
}
public function addBill(Bill $bill): self
{
if (!$this->bill->contains($bill)) {
$this->bill->add($bill);
}
return $this;
}
public function removeBill(Bill $bill): self
{
$this->bill->removeElement($bill);
return $this;
}
}
And Bill:
class Bill
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $number = null;
#[ORM\ManyToMany(targetEntity: Domain::class, mappedBy: 'bill')]
private Collection $domains;
public function __construct()
{
$this->domains = new ArrayCollection();
}
public function __toString()
{
return $this->number;
}
public function getId(): ?int
{
return $this->id;
}
public function getNumber(): ?string
{
return $this->number;
}
public function setNumber(string $number): self
{
$this->number = $number;
return $this;
}
/**
* #return Collection<int, Domain>
*/
public function getDomains(): Collection
{
return $this->domains;
}
public function addDomain(Domain $domain): self
{
if (!$this->domains->contains($domain)) {
$this->domains->add($domain);
$domain->addBill($this);
}
return $this;
}
public function removeDomain(Domain $domain): self
{
if ($this->domains->removeElement($domain)) {
$domain->removeBill($this);
}
return $this;
}
}
BillType form:
class BillingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('number', TextType::class, [
'attr' => ['placeholder' => 'Numero fattura',]
])
->add('domains', EntityType::class, [
'class' => Domain::class,
'expanded' => true,
'multiple' => true,
'choice_label' => 'name'
])
->add('submit', SubmitType::class, [
'attr' => [
'class' => 'btn-success',
]
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Bill::class,
]);
}
}
I'm trying to set the bill for multiple domains at the same time, so my controller look like this:
public function domainBilling(Request $request, ManagerRegistry $doctrine, PaginatorInterface $paginator)
{
$filters = $this->getFilters($request)['filters'];
$form = $this->getFilters($request)['form'];
$dbquery = $doctrine
->getRepository(Domain::class)
->findAllPaginated($filters)
->getQuery();
$domains = $paginator->paginate(
$dbquery, /* query NOT result */
$request->query->getInt('page', 1), /*page number*/
Domain::perPage, /*limit per page*/
[
'defaultSortFieldName' => 'd.name',
'defaultSortDirection' => 'asc',
]
);
$billing_form = $this->createForm(BillingType::class);
$billing_form->handleRequest($request);
if ($billing_form->isSubmitted() && $billing_form->isValid()) {
$em = $doctrine->getManager();
$domains = $billing_form['domains']->getData();
$bill_number = $billing_form['number']->getData();
$bill = new Bill();
$bill->setNumber($bill_number);
$em->flush();
$this->addFlash(
'success',
'Le modifiche sono state effettuate!'
);
return $this->redirectToRoute('domain_dashboard');
}
return $this->render('Admin/domain_dashboard.html.twig', [
'billing_form' => $billing_form->createView(),
'form' => $form->createView(),
'domains' => $domains
]);
}
And it's working without calling $em->persist(); on bill or domain and without calling foreach to save every domain. If i persist one of them i find duplicate entry for bill inside db. Am i doing something wrong?
I try to insert records with relations OneToMany-ManyToOne, but I got error.
Expected argument of type "string", "App\Entity\Question" given.
I have next entities question and answer.
class Question
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $title;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="question", orphanRemoval=true)
*/
private $answers;
public function __construct()
{
$this->answers = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
/**
* #return Collection|Answer[]
*/
public function getAnswers(): Collection
{
return $this->answers;
}
public function addAnswer(Answer $answer): self
{
if (!$this->answers->contains($answer)) {
$this->answers[] = $answer;
$answer->setQuestion($this);
}
return $this;
}
public function removeAnswer(Answer $answer): self
{
if ($this->answers->contains($answer)) {
$this->answers->removeElement($answer);
if ($answer->getQuestion() === $this) {
$answer->setQuestion(null);
}
}
return $this;
}
}
Entity Answer
class Answer
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $text;
/**
* #ORM\Column(type="boolean")
*/
private $is_correct;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="answers")
* #ORM\JoinColumn(nullable=false)
*/
private $question;
public function getId(): ?int
{
return $this->id;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getIsCorrect(): ?bool
{
return $this->is_correct;
}
public function setIsCorrect(bool $is_correct): self
{
$this->is_correct = $is_correct;
return $this;
}
public function getQuestion(): ?question
{
return $this->question;
}
public function setQuestion(?Question $question): self
{
$this->question = $question;
return $this;
}
}
My form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', EntityType::class, array(
'class' => Question::class,
'choice_label' => 'title',
'label' => 'Question'
));
$builder
->add('answers', CollectionType::class, array(
'entry_type' => AnswerType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'by_reference' => false,
));
$builder
->add('create', SubmitType::class, ['label' => 'Add', 'attr' => ['class' => 'btn btn-primary']]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Question::class
]);
}
My fragment of controller
$question = new Question();
$answer = new Answer();
$question->addAnswer($answer);
$form1 = $this->createForm(QuestionAnswerType::class, $question);
$form1->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($question);
$em->flush();
}
The error pointer at next line
$form1->handleRequest($request);
I know I have problem with my controller, but I don't know how resolve it.
I don't get it how right insert records with relations OneToMany-ManyToOne. Could you help me?
I think the reason that you are seeing this error is due to the fact that in your Question class you have defined the title field as a Text type (#ORM\Column(type="text")).
However in your Form you have defined the form field title as an EntityType this is the reason I think why you are seeing this error.
You can fix this by changing the database mapping of the title field in your Question class or you could change your Form to use a TextType instead of an EntityType
Hope this helps
You have to do changes into two places.
1) First change into "Question" class
/**
* #ORM\Column(type="string")
*/
private $title;
2) Second into your form class replace "EntityType::class" with "TextType::class" and remove 'class' and 'choice_label' attribute from title
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'label' => 'Question'
));
..... your other code ...
}
In class Question __toString function is missing
public function __toString()
{
return $this->property-to-show;
}
I have a /checkout JSON API endpoint which allows an optional billingAddress parameter alongside other parameters such as email and deliveryAddress.
These addresses are stored in an Address entity related to an Order entity.
Everything works nicely if a user enters their billingAddress, but if a user removes a previously submitted billing address, I can find no way to remove the billingAddress entity. Ideally to remove the billing address I'd use the following JSON POST request.
{
"email": "nick#example.com",
"deliveryAddress": {
"line1": "1 Box Lane"
},
"billingAddress": null
}
Is this at all possible with Symfony forms?
See below for a simplified explanation of the current setup.
Entities
/**
* #ORM\Entity
*/
class Order
{
// ...
/**
* #var Address
*
* #ORM\OneToOne(targetEntity = "Address", cascade = {"persist", "remove"})
* #ORM\JoinColumn(name = "deliveryAddressId", referencedColumnName = "addressId")
*/
private $deliveryAddress;
/**
* #var Address
*
* #ORM\OneToOne(targetEntity = "Address", cascade = {"persist", "remove"}, orphanRemoval = true)
* #ORM\JoinColumn(name = "billingAddressId", referencedColumnName = "addressId", nullable = true)
*/
private $billingAddress;
public function setDeliveryAddress(Address $deliveryAddress = null)
{
$this->deliveryAddress = $deliveryAddress;
return $this;
}
public function getDeliveryAddress()
{
return $this->deliveryAddress;
}
public function setBillingAddress(Address $billingAddress = null)
{
$this->billingAddress = $billingAddress;
return $this;
}
public function getBillingAddress()
{
return $this->billingAddress;
}
// ...
}
.
/**
* #ORM\Entity
*/
class Address
{
// ...
/**
* #var string
*
* #ORM\Column(type = "string", length = 45, nullable = true)
*/
private $line1;
// ...
}
Forms
class CheckoutType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'required' => true
])
->add('billingAddress', AddressType::class, [
'required' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Order::class,
'csrf_protection' => false,
'allow_extra_fields' => true,
'cascade_validation' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
.
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('line1', TextType::class);
// ...
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Address::class,
'allow_extra_fields' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
Form events are what you need: https://symfony.com/doc/current/form/events.html
For example if you want to remove the billingAddress field after the form submission you can do that:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'required' => true
])
->add('billingAddress', AddressType::class, [
'required' => false
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if (empty($data['billingAddress'])) {
$form->remove('billingAddress');
}
});
}
Read carefully the documentation to know which event will be the best for your scenario.
Try setting the "by_reference" option for the "billingAddress" field to false in order to make sure that the setter is called.
http://symfony.com/doc/current/reference/forms/types/form.html#by-reference
Big thanks to Renan and Raphael's answers as they led me to discovering the below solution which works for both a partial PATCH and full POST request.
class CheckoutType extends AbstractType
{
/** #var bool */
private $removeBilling = false;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('deliveryAddress', AddressType::class, [
'constraints' => [new Valid]
])
->add('billingAddress', AddressType::class, [
'required' => false,
'constraints' => [new Valid]
])
->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'onPreSubmit'])
->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPreSubmit(FormEvent $event)
{
$data = $event->getData();
$this->removeBilling = array_key_exists('billingAddress', $data) && is_null($data['billingAddress']);
}
public function onPostSubmit(FormEvent $event)
{
if ($this->removeBilling) {
$event->getData()->setBillingAddress(null);
}
}
}
I writed the simpliest Money class ever:
class Money
{
private $amount;
private $currency;
// getters and setters here
public function toArray() {
return ['amount' => $this->amount, 'currency' => $this->currency];
}
}
Then I created Doctrine custom type which extend Sonata's JsonType:
class MoneyType extends JsonType
{
/**
* #param Money $value
* #param AbstractPlatform $platform
* #return mixed|null|string
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return parent::convertToDatabaseValue($value->toArray(), $platform);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return new Money(parent::convertToPHPValue($value, $platform));
}
public function getName()
{
return 'money';
}
}
Then I added it in my config.yml:
doctrine:
dbal:
types:
json: Sonata\Doctrine\Types\JsonType
money: Enl\CurrencyBundle\Doctrine\Type\MoneyType
Then I added form type for this data piece:
class MoneyType extends AbstractType
{
// Some trivial getters and setters for dependencies here
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'enl_money';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'currencies' => $this->getPossibleCurrencies(),
'data_class' => 'Enl\CurrencyBundle\Model\Money'
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('currency', 'choice', ['choices' => $options['currencies'], 'required' => true])
->add('amount', 'number', ['required' => true]);
}
public function getParent()
{
return 'form';
}
}
And then I added the field to my model:
/**
* #var Money
* #ORM\Column(type="money", nullable=true)
*/
protected $price;
And finally my sonata admin form:
$form
->with('editor.dish', ['class' => 'col-md-8'])
->add('price', 'enl_money')
// blah-blah-blah
The problem is that Sonata Admin just doesn't save the form value!
I added var_dump($object); to Admin::update() method - the new value is there in object.
After that I created the simple test:
$dish = new Dish();
$dish->setPrice(new Money(7000, "BYR"));
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$em->persist($dish);
$em->flush();
It works! What should I change in my Admin class to get this working?
I am using many to many relations in my symfony2 project, i have successfully made two entities 'Users' and Groups and i have also successfully persisted data which inserts data into users,groups and users_groups table by using
$user = new User();
$user->getGroups()->add($group);
now i want to edit the user which should also edit users_groups record..
I have searched a lot but no luck.Any help would be appreciated ...
CONTROLLER
public function editAction()
{
if($this->getRequest()->query->get("id")){
$id = $this->getRequest()->query->get("id");
$request = $this->getRequest();
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('DesignAppBundle:Users')->find($id);
$editForm = $this->createForm(new UserType());
if ($this->getRequest()->getMethod() == 'POST') {
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$postData = $request->request->get('users');
$repository = $this->getDoctrine()
->getRepository('Bundle:groups');
$group = $repository->findOneById($postData['group']);
$entity->addGroups($group );
$em->flush();
return $this->redirect($this->generateUrl('index'));
}
}
return $this->render('User.html.twig',array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
));
}
}
FORM
<?php
use and include .....
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$request = Request::createFromGlobals();
$builder->add('group', 'entity', array(
'class' => 'Bundle:Groups',
'property' => 'name',
'empty_value' => 'All',
'required' => true,
'multiple'=>true,
'data' => $request->get('group_id')
));
}
public function getDefaultOptions(array $options)
{
return array(
'validation_groups' => array('users'),
'csrf_protection' => false,
);
}
public function getName()
{
return 'users';
}
}
User
/**
* #ORM\ManyToMany(targetEntity="Groups", inversedBy="users")
* #ORM\JoinTable(name="users_groups",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
**/
protected $group;
public function __construct()
{
$this->group= new ArrayCollection();
}
public function addgroups(\Design\AppBundle\Entity\Categories $group)
{
$this->group[] = $group;
}
Group
/**
* #ORM\ManyToMany(targetEntity="Users", mappedBy="group")
*/
protected $users;
public function __construct()
{
$this->users= new ArrayCollection();
}
/**
* Add users
*
* #param Bundle\Entity\Users $users
*/
public function addUsers(Bundle\Users $users)
{
$this->users[] = $users;
}
/**
* Get users
*
* #return Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
You should not be adding the group_id, you should be adding the groups property
$builder->add('groups');
The group_id column is automatically handled by Doctrine and you should not be handling it yourself directly. Do you have a group_id property? If you do, you should remove it.
The group entity should be:
/**
* #ORM\ManyToMany(targetEntity="Users", mappedBy="group")
*/
protected $users;
public function __construct()
{
$this->users= new ArrayCollection();
}
/**
* Add users
*
* #param Bundle\Entity\User $user
*/
public function addUsers(Bundle\User $users)
{
$this->users[] = $users;
}
/**
* Get users
*
* #return Doctrine\Common\Collections\Collection
*/
public function getUsers()
{
return $this->users;
}
EDIT
This is how your controller should be:
public function editAction($request)
{
$id = $this->getRequest()->query->get("id");
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('DesignAppBundle:Users')->find($id);
$editForm = $this->createForm(new UserType(),$entity);
if ($this->getRequest()->getMethod() == 'POST') {
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$em->persits($entity);
$em->flush();
return $this->redirect($this->generateUrl('index'));
}
}
return $this->render('User.html.twig',array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
));
}
and this is how your UserType should be:
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('group', 'entity', array(
'class' => 'Bundle:Groups',
'property' => 'name',
'empty_value' => 'All',
'required' => true,
'multiple'=>true,
));
}
public function getDefaultOptions(array $options)
{
return array(
'validation_groups' => array('users'),
'csrf_protection' => false,
);
}
public function getName()
{
return 'users';
}
}