Dependants fields inside collectionType - symfony

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

Related

Symfony 5 - DataTransformer with Form Validation Constraint

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

Processing self-referencing, complex data structure with Symfony Forms

How to process following data structure through Symfony Forms?
An entity which holds collection of an entity which has a relation to itself:
Order
/** #var OrderProduct */
$orderProducts
public function getQuoteProducts()
{
return $this->quoteProducts;
}
public function addOrderProduct(OrderProduct $orderProduct)
{
if (!$this->orderProducts->contains($orderProduct)) {
$this->orderProducts[] = $orderProduct;
$orderProduct->setOrder($this);
}
return $this;
}
public function removeOrderProduct(OrderProduct $orderProduct)
{
if ($this->orderProducts->contains($orderProduct)) {
$this->orderProducts->removeElement($orderProduct);
}
return $this;
}
OrderProduct
/** #var Order */
$order
/** #var OrderProduct */
$relatedOrderProducts
public function addRelatedOrderProduct(OrderProduct $orderProduct)
{
if (!$this->relatedOrderProducts->contains($orderProduct)) {
$this->relatedOrderProducts[] = $orderProduct;
$orderProduct->setMainOrderProduct($this);
}
return $this;
}
public function removeRelatedOrderProduct(OrderProduct $orderProduct)
{
if ($this->relatedOrderProducts->contains($orderProduct)) {
$this->relatedOrderProducts->removeElement($orderProduct);
$orderProduct->setMainOrderProduct(null);
}
return $this;
}
The request:
'order' => [
'some_order_property' => 'value',
// ...
'orderProducts' => [
'some_order_product_property' => 'value',
// ...
'relatedOrderProducts' => [
[
'some_order_product_property' => 'value',
// ...
],
[
'some_order_product_property' => 'value',
// ...
],
]
]
]
Forms:
OrderType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('orderProducts', OrderProductCollectionType::class)
->add(...);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => Order::class]);
}
OrderProductCollectionType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'type' => OrderProductType::NAME,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
]);
}
OrderProductType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('relatedOrderProducts', RelatedOrderProductCollectionType::class)
->add(...);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => OrderProduct::class]);
}
RelatedOrderProductCollectionType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'type' => RelatedOrderProductType::NAME,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
]);
}
RelatedOrderProductType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(...);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => OrderProduct::class]);
}
This setup works, only it doesn't relate OrderProducts from RelatedOrderProductType form to the Order (so they have order_id set to null).
When this relation is forced (eg. with a FormEvent listener), then all the relations are getting deleted on subsequent form submission. That's most likely due to orphanRemoval=true set on the entity.
Probably some details are missing from my example, but me general question is: can this data structure be processed with Symfony Forms? If so, then how?

$event->getData() returns null for an embedded form

I'm using symfony 2.3. I have a form "AddressType" with an event listener that works as expected when instantiated alone. But when I embeded AddressType into BusinessType, the address form is not displaying. I don't understand what is happening exacltly and how can I fix it.
BusinessType
class BusinessType extends AbstractType {
private $em;
public function __construct($em) {
$this->em = $em;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
// cela suppose que le gestionnaire d'entité a été passé en option
$entityManager = $options['em'];
$transformer = new EmailToUserTransformer($entityManager);
$ActivityTransformer = new ActivityToStringTransformer($entityManager);
$builder
->add('name')
->add('description')
->add('file')
->add('file2')
->add('email')
->add('fb')
->add('twitter')
->add('googlePlus')
->add('linkedin')
->add('tel')
->add('mobile')
->add('fax')
->add('web')
->add('address', new AddressType($this->em))
->add(
$builder->create('user', 'text')
->addModelTransformer($transformer)
)
->add('activity', null, array(
'empty_value' => 'Sélectionner une activité',
))
->add(
$builder->create('secondary', 'entity', array(
'empty_value' => 'Sélectionner une activité secondaire',
'class' => 'BiginfoAdminBundle:Activity',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('a');
},
))
->addModelTransformer($ActivityTransformer)
)
->add('enabled')
->add('medias', 'collection', array(
'type' => new $options['media_form'],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false))
->add('opens', 'collection', array(
'type' => new $options['open_form'],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Biginfo\AdminBundle\Entity\Business',
));
$resolver->setDefaults(array(
'data_class' => 'Biginfo\AdminBundle\Entity\Business',
'open_form' => 'Biginfo\AdminBundle\Form\OpenType',
'media_form' => 'Biginfo\AdminBundle\Form\MediaType',
'cascade_validation' => true
));
$resolver->setRequired(array(
'em',
));
$resolver->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
}
/**
* #return string
*/
public function getName() {
return 'biginfo_adminbundle_business';
}
}
AddressType
class AddressType extends AbstractType {
private $em;
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$propertyPathToPostalCode = 'postalCode';
$builder
->add('street', 'text', array(
'label' => 'Adresse'
))
;
$builder
->addEventSubscriber(new AddGouvernauratFieldSubscriber($propertyPathToPostalCode, $this->em))
->addEventSubscriber(new AddDelegationFieldSubscriber($propertyPathToPostalCode, $this->em))
->addEventSubscriber(new AddSectorFieldSubscriber($propertyPathToPostalCode, $this->em))
->addEventSubscriber(new AddPostalCodeFieldSubscriber($propertyPathToPostalCode, $this->em))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Biginfo\AdminBundle\Entity\Address',
));
}
public function getName() {
return 'address';
}
}
AddDelegationFieldSubscriber
class AddDelegationFieldSubscriber implements EventSubscriberInterface {
private $propertyPathToCity;
private $em;
public function __construct($propertyPathToCity, $em) {
$this->propertyPathToCity = $propertyPathToCity;
$this->em = $em;
}
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addDelegationForm($form, $gouvernaurat_id, $delegation = null) {
$formOptions = array(
'class' => 'BiginfoAdminBundle:Delegation',
'empty_value' => 'Sélectionner une délégation',
'label' => 'Délégation',
'mapped' => false,
'attr' => array(
'class' => 'delegation_selector',
),
'query_builder' => function (EntityRepository $repository) use ($gouvernaurat_id) {
$qb = $repository->createQueryBuilder('delegation')
->innerJoin('delegation.gouvernaurat', 'gouvernaurat')
->where('gouvernaurat.id = :gouvernaurat')
->setParameter('gouvernaurat', $gouvernaurat_id)
;
return $qb;
}
);
if ($delegation) {
$formOptions['data'] = $delegation;
}
$form->add('delegation', 'entity', $formOptions);
}
public function preSetData(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
/**
* Le composant PropertyAccess fournit des fonctions pour lire
* et écrire depuis/dans un objet ou un tableau en une simple chaîne de caractères.
*/
$accessor = PropertyAccess::getPropertyAccessor();
$sector1 = $accessor->getValue($data, 'sector');
$sector = $this->em->getRepository('BiginfoAdminBundle:Sector')
->findOneBy(array('name' => $sector1));
$delegation = ($sector) ? $sector->getDelegation() : null;
$gouvernaurat_id = ($delegation) ? $delegation->getGouvernaurat()->getId() : null;
$this->addDelegationForm($form, $gouvernaurat_id, $delegation);
}
public function preSubmit(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
$gouvernaurat_id = array_key_exists('gouvernaurat', $data) ? $data['gouvernaurat'] : null;
$this->addDelegationForm($form, $gouvernaurat_id);
}
}
I had the same problem but referring to this issue #5694 you have to set 'prototype' => false in parent form.

how to dynamically set cascade validation of form from controller

My form looks like this :
class CpanelRetailerForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('email', 'email', array(
'attr' => array(
'class' => 'text-input',
'size' => '50'
),
'required' => false
))
->add('addUser', 'checkbox', array(
'label' => 'Add User account',
'required' => false,
'mapped' => false
))
->add('user',new CpanelUserForm());
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Acme\TestBundle\Entity\Retailer',
//'cascade_validation' => true
));
}
public function getName() {
return 'retailer';
}
}
I want to dynamically set this line from controller depending on whether addUser field is checked or unchecked.
cascade_validation' => true
Here is my controller code:
$form = $this->createForm(new CpanelRetailerForm(), new Retailer());
$form->
if ($this->getRequest()->isMethod('POST')) {
$form->bind($this->getRequest());
if ($form->get('addUser')->getData()) {
// then set the cascade_validation to true here
}
}
How can I do this inside controller?
My attempt :
added this line in my form class:
$builder->addEventListener(
FormEvents::POST_SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$addUser = $form->get('addUser')->getData();
$validation = false;
if ($addUser) {
$validation = true;
}
$resolver = new OptionsResolver();
$resolver->setDefaults(array(
'cascade_validation' => $validation
));
$this->setDefaultOptions($resolver);
}
);
This didnot work for me. Although I receive data in $addUser, cascade_validation is not added
How can I do this inside controller?
You can´t! Thats the simple answer. Lets take a look at following simple form class:
class TestType extends AbstractType {
/**
* #var boolean
*/
private $myOption;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$this->myOption = false;
$builder
->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) {
dump('formEvents::PRE_SET_DATA');
})
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
dump('FormEvents::POST_SET_DATA');
})
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
dump('FormEvents::PRE_SUBMIT');
})
->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) {
dump('FormEvents::SUBMIT');
})
->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
dump('formEvents::POST_SUBMIT');
})
->add('name', TextType::class)
->add('send', SubmitType::class);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setRequired(array(
'my_option'
));
$resolver->setDefaults(array(
'my_option' => $this->setMyOption()
));
}
/**
* #return bool
*/
public function setMyOption() {
dump($this->myOption);
return $this->myOption;
}
}
Lets take in how you render and handle a form inside a Controller:
public function formAction(Request $request) {
$form = $this->createForm(TestType::class);
dump('calledCreateForm');
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
dump('finished');
dump($form->getData());
die();
}
return $this->render('#TestPra/Test/test_form.html.twig', array(
'form' => $form->createView()
));
}
After submitting the form you get the following output order:
$this->setMyOption() > null
FormEvents::PRE_SET_DATA
FormEvents::POST_SET_DATA
calledCreateForm
FormEvents::PRE_SUBMIT
FormEvents::SUBMIT
FormEvents::POST_SUBMIT
finished
The first thing allways gets called is configureOptions and because you don´t have any data of the filled form before calling handleRequest there is no way to change the options of the allready created form without manipulating Symfonys form component.

Symfony2/ Use custom repository method in a formType

I am new with Symfony2, hopefully I am clear enough.
I have a repository:
class districtRepository extends EntityRepository
{
public function findAllFromCity($idCity)
{
return $this->createQueryBuilder('d')
->where('d.city = :city')
->setParameter('city', $idCity)
->orderBy('d.name', 'ASC');
->getQuery()
->getResult();
}
}
And a form type
class searchPropertyType extends AbstractType
{
public function getDefaultOptions(array $options)
{
// return array('validation_constraint' => $collectionConstraint
return array ('required'=>false, 'csrf_protection' => true);
}
public function buildForm(FormBuilder $builder, array $options)
{
$em = $this->getDoctrine()->getEntityManager();
$builder
->add('keywords')
->add('disctrict')
->add('price_min')
->add('price_max')
->add('type')
->add('date_from' , 'date', array('widget' => 'single_text'))
->add('date_to' , 'date', array('widget' => 'single_text'))
;
}
public function getName()
{
return 'searchProperty';
}
}
How do I simply use findAllFromCity() to get a list of option in ->add('disctrict') ??
I know the Query Builder solution, but it makes me repeating my code.
I've read about the service container solution. Is is applicabe in my case? Can you show me how or put me on good tracks??
Indeed, only way to access your 'findAllFromCity' method in the 'searchPropertyType' class is to inject the Doctrine registry.
In your form type class:
use Symfony\Bridge\Doctrine\RegistryInterface;
class searchPropertyType extends AbstractType
{
private $doctrine;
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
In your services.xml file:
<service id="my_project.form.type.search_property" class="{mynamespace}\searchPropertyType" public="false">
<tag name="form.type" />
<argument type="service" id="doctrine" />
</service>
In order to use this this method, you'll have to use the 'choice' type with 'choices' or 'choice_list' option.
In your form type class:
public function buildForm(FormBuilder $builder, array $options)
{
$builder
// ...
->add('disctrict', 'choice', $this->getDistrictChoices($options['city_id']))
// ...
;
}
private function getDistrictChoices()
{
$choices = array();
$districts = $this->doctrine->getRepository('MyBundle:District')->findAllFromCity($cityId);
foreach ($dictricts as $dictrict) {
$choices[$district->getId()] = $district->getName();
}
return $choices;
}
Of course, this is an example and it needs to be adapted.
And remember: class name fisrt letters are always in upper case.
Thanks to Simon, I have updated as follow and it is now working!
searchController:
public function basicsearchAction(Request $request)
{
$form = $this->createForm(new searchPropertyType($this->getDoctrine()));
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
}
searchPropertyType
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('keywords')
->add('district', 'choice', array('choices'=>$this->getDistrictChoices(), 'multiple'=>true) )
->add('price_min')
->add('price_max')
->add('type', 'entity', array('class'=>'FlatShanghaidefaultBundle:propertytype',
'property'=>'name',
'multiple'=>true
))
->add('date_from' , 'date', array('widget' => 'single_text'))
->add('date_to' , 'date', array('widget' => 'single_text'))
;
}
private function getDistrictChoices()
{
$choices = array();
$districts = $this->doctrine->getRepository('FlatShanghaidefaultBundle:district')->findByDefaultCity();
foreach ($districts as $district) {
$choices[$district->getId()] = $district->getName();
}
return $choices;
}
service.yml
services:
search_property:
class: FlatShanghai\propertyBundle\Form\searchPropertyType
arguments: [doctrine]
I was also concerned about repeated code. Not only for my query builder, but also didn't want to write a custom choice mapping such as "getDistrictChoices". I chose to abstract my query inside my EntityRepository to make it available in my form type as well.
Here's what I did.
My form:
class UserEditType extends AbstractType
{
/**
* #var Session $session
*/
private $session;
public function __construct(Session $session){
$this->session = $session;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('firstName')
->add('middleName')
->add('lastName')
->add('username')
->add('userStatus', EntityType::class, [
'class' => UserStatus::class,
'property' => 'status',
'query_builder' => function(UserStatusRepository $er) {
return $er->createQueryBuilder('s')
->orderBy('s.status');
},
])
->add('agency', EntityType::class, [
'class' => WorkflowGroup::class,
'property' => 'groupName',
'query_builder' => function(WorkflowGroupRepository $er) {
return $er->createSelectAllQB($this->session->get(AbstractController::SESSION_KEY_AGENCY_ID));
},
])
->add('phoneNumber')
->add('email', EmailType::class)
->add('activationDate', DateType::class, [
'widget' => 'single_text',
'format' => 'M/d/yyyy',
'attr' => [
'data-provide' => 'datepicker',
'data-date-format' => 'mm/dd/yyyy',
'data-date-show-on-focus' => 'false',
'data-date-auto-format' => 'true'
]
])
->add('expirationDate', DateTimeType::class, [ 'widget' => 'single_text', 'format' => 'yyyy-MM-dd HH:mm' ]);
;
}
...
}
My Repository:
class WorkflowGroupRepository extends EntityRepository
{
public function createSelectAllQB($agencyId)
{
return $this->createQueryBuilder('w')
->addOrderBy('w.groupName', 'ASC')
->andWhere('w.agencyId = :agencyId')
->setParameter('agencyId', $agencyId);
}
public function selectAll($agencyId)
{
return $this->createSelectAllQB($agencyId)->getQuery()->execute();
}
}

Resources