Reputation: 25
I would really appreciate if someone could help me. I spend some hours with this problem and could not find a solution.
The code works fine (see below), as long as I don't need a second AddressType Class within PersonType Class. Something like this does not work:
// PersonType:
$builder
->add('firstname', 'text')
->add('middlename', 'text')
->add('lastname', 'text')
->add('address', new AddressType())
->add('address', new AddressType());
Multiple FormTypes of the same Type will have the same HTML-IDs so Symfony does not render the second address. I have several questions on that topic:
Do I need the CollectionType even if I need only two address or is there another way (I don't need the user to be able to add dynamically another address)?
Let's assume I need the CollectionType. At the beginning a CollectionType is empty. But I need two addresses from the start. I found the following in the Symfony-Documentation to create some collection items from the start: Form Collections
// Controller:
$task = new Task();
// dummy code - this is here just so that the Task has some tags
// otherwise, this isn't an interesting example
$tag1 = new Tag();
$tag1->setName('tag1');
$task->getTags()->add($tag1);
$tag2 = new Tag();
$tag2->setName('tag2');
$task->getTags()->add($tag2);
Now, please look at my controller. I don't even create a entity. I only create the main FormType which has all other Formtypes embedded. How or where can I create the two addresses? Or even my whole Controller-Code is wrong?
Is there a solution to tell symfony that the first address is not the same as the second address? How can I set the name/id of each AddressType? (This solution does not work anymore.)
Is the form generation really ok in my controller? I don't pass a entity or an array to the form. It works, but I wonder why.... Otherwise I don't know how to pass all the entities I need to the form (contract, person, address, customer, payment, etc.).
Thanks in advance!
Controller:
class DefaultController extends Controller{
/**
* @return array
* @Route("/private")
* @Template("DmmGenericFormBundle:Default:index.html.twig")
*/
public function indexAction(Request $request)
{
// This formtype class has all other form type classes
$privateRentDepositForm = new PrivateRentDepositType();
$form = $this->createForm($privateRentDepositForm);
$form->handleRequest($request);
if($form->isValid()){
$contract = $form->get('contract')->getData();
$privateContractDetails = $form->get('privateContractDetails')->getData();
$customer = $form->get('customer')->getData();
$customerPerson = $form->get('customer')->get('person')->getData();
$privateRealEstate = $form->get('privateRealEstate')->getData();
$privateRealEstateAddress = $form->get('privateRealEstate')->get('address')->getData();
$landlord = $form->get('privateRealEstate')->get('landlord')->getData();
$landlordPerson = $form->get('privateRealEstate')->get('landlord')->get('person')->getData();
$caretakerPerson = $form->get('privateRealEstate')->get('landlord')->get('caretaker')->get('person')->getData();
$payment = $form->get('payment')->getData();
$contract->addPerson($customerPerson);
$contract->addPerson($landlordPerson);
$contract->addPerson($caretakerPerson);
$contract->setPrivateContractDetails($privateContractDetails);
$customer->setPayment($payment);
$landlord->setPrivateRealEstate($privateRealEstate);
$privateRealEstate->addCustomer($customer);
$privateRealEstate->setLandlord($landlord);
$em = $this->get('doctrine')->getManager();
$em->persist($contract);
$em->flush();
}
return array('form' => $form->createView());
}
Form Type Classes:
class PrivateRentDepositType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('contract', new ContractType())
->add('privateContractDetails', new PrivateContractDetailsType())
->add('customer', new CustomerType())
->add('privateRealEstate', new PrivateRealEstateType())
->add('payment', new PaymentType())
->add('save', 'submit');
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false
));
}
/**
* @return string
*/
public function getName()
{
return 'FormStep';
}
}
Person Type Class:
class PersonType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname', 'text')
->add('middlename', 'text')
->add('lastname', 'text')
->add('address', new AddressType());
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Person',
'config' => null,
]
);
}
/**
* @return string
*/
public function getName()
{
return 'dmm_bundle_genericformbundle_person';
}
}
Address Type Class:
class AddressType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('street', 'text')
->add('housenumber', 'text')
->add('zip', 'text')
->add('city', 'text')
->add('extra', 'text')
->add('country', 'text')
->add('postOfficeBox', 'integer')
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Address',
'config' => null,
]
);
}
/**
* @return string
*/
public function getName()
{
return 'dmm_bundle_genericformbundle_address';
}
}
Customer Type Class:
class CustomerType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('person', new PersonType())
->add('birthplace', 'text')
->add('email', 'email')
->add('phone', 'text');
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'Dmm\Bundle\GenericFormBundle\Entity\Customer',
'config' => null,
]
);
}
/**
* @return string
*/
public function getName()
{
return 'dmm_bundle_genericformbundle_customer';
}
}
DB Associations
Contract:Person = 1:n (Owning Side: Person, ArrayCollection for Person in Contract)
Person:Address = 1:n (Owning Side: Address, ArrayCollection for Address in Person)
Person:Customer = 1:1 (Owning Side: Customer, Customer IS A Person)
Edit: Code edit for Solution 2 by Cerad Controller - create all objects
$contract = new Contract();
$contractDetails = new PrivateContractDetails();
$contract->setPrivateContractDetails($contractDetails);
$customer1Person = new Person();
$customer1Address1 = new Address();
$customer1Address1->setAddressType('someAddress');
$customer1Address2 = new Address();
$customer1Address2->setAddressType('someAddress');
$customer1Person->addAddress($customer1Address1);
$customer1Person->addAddress($customer1Address2);
$contract->addPerson($customer1Person);
$customer1 = new Customer();
$customer1->setPerson($customer1Person);
$customer2Person = new Person();
$customer2Address1 = new Address();
$customer2Address1->setAddressType('someAddress');
$customer2Address2 = new Address();
$customer2Address2->setAddressType('someAddress');
$customer2Person->addAddress($customer2Address1);
$customer2Person->addAddress($customer2Address2);
$contract->addPerson($customer2Person);
$customer2 = new Customer();
$customer2->setPerson($customer2Person);
$landlordPerson = new Person();
$landlordPersonAddress = new Address();
$landlordPersonAddress->setAddressType('someAddress');
$landlordPerson->addAddress($landlordPersonAddress);
$contract->addPerson($landlordPerson);
$landlord = new Landlord();
$landlord->setPerson($landlordPerson);
$prEstate = new PrivateRealEstate();
$prEstateAddress = new Address();
$prEstateAddress->setAddressType('someAddress');
$prEstate->setAddress($prEstateAddress);
$landlord->setPrivateRealEstate($prEstate);
$prEstate->addCustomer($customer1);
$prEstate->addCustomer($customer2);
$caretakerPerson = new Person();
$caretakerAddress = new Address();
$caretakerAddress->setAddressType('someAddress');
$caretakerPerson->addAddress($caretakerAddress);
$contract->addPerson($caretakerPerson);
$caretaker = new Caretaker();
$caretaker->addLandlord($landlord);
$caretaker->setPerson($caretakerPerson);
$payment = new Payment();
$customer1->setPayment($payment);
$privateRentDepositForm = new PrivateRentDepositType();
$form = $this->createForm($privateRentDepositForm, $contract);
And I replaced
->add('address', new AddressType());
with
->add('address', 'collection', array(
'type' => new AddressType()
))
Now I get the following exception: The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class Dmm\Bundle\GenericFormBundle\Entity\Contract. You can avoid this error by setting the "data_class" option to "Dmm\Bundle\GenericFormBundle\Entity\Contract" or by adding a view transformer that transforms an instance of class Dmm\Bundle\GenericFormBundle\Entity\Contract to scalar, array or an instance of \ArrayAccess.
In ContractType the data_class is set to the right class. Only in PrivateRentDepositType the data_class is set to null. But PrivateRentDepositType is a collection of diffrent types. I tried to set the data_class to 'Dmm\Bundle\GenericFormBundle\Entity\Contract' but that leads to another excepiton: Neither the property "contract" nor one of the methods "getContract()", "contract()", "isContract()", "hasContract()", "__get()" exist and have public access in class "Dmm\Bundle\GenericFormBundle\Entity\Contract".
Upvotes: 0
Views: 701
Reputation: 48865
There are two basic approaches you can use.
Given that each person needs two and only two addresses then the quickest approach would be to do:
// Form type
$builder
->add('firstname', 'text')
->add('middlename', 'text')
->add('lastname', 'text')
->add('address1', new AddressType(),
->add('address2', new AddressType());
Add address1/address2 getter/setters to your Person entity. Internally, the Person can still store addresses in any array. But as far as the form type goes, each is individually accessible. Everything else should work.
The second approach is a bit more Symfonyish and basically involves creating and initializing a new contract and passing it to the form. That would eliminate the need to do so much processing after the form is submitted. So in your controller you would have:
$contract = new Contract();
$customerPerson = new CustomerPersion();
$contract->addPerson($customerPerson);
...
$privateContractDetails = new PrivateContractDetails()
...
$privateRentDeposit = [
'contract' => $contract,
'privateContractDetails' => new $privateContractDetails,
... rest of objects ...
];
...
$form = $this->createForm($privateRentDepositForm,$privateRentDeposit);
---
if ($form->isValid()) {
$em->persist($contract);
There of course is a lot more to creating your contract (aka aggregate root) then what is shown but you should get the idea. I would move it all into a createContract factory method somewhere. This approach allows you to add two address to your person entities which in turn means you can use the form collection stuff out of the box.
One final note: forms have changed significantly in S3.x. new FormType() no longer works. Consider updating to at least S2.8 (which has the new form stuff) otherwise you will be facing a major rewrite if you ever do move to S3+.
Upvotes: 1