When I send my form I get :
An exception occurred while executing 'INSERT INTO Post (content, date_creation, date_modif, user_id, topic_id) VALUES (?, ?, ?, ?, ?)' with params ["<p>AA\/BB\/CC<\/p>", "2015-09-01 17:11:28", "2015-09-01 17:11:28", null, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
Normal, cause I have a relationship mapping between several entities (Topic.php, Post.php and User.php)
PostType.php:
<?php
namespace BISSAP\ForumBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PostType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content', 'ckeditor', array(
'label' => 'Votre message',
'config_name' => 'my_custom_config',
'config' => array('language' => 'fr')))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BISSAP\ForumBundle\Entity\Post'
));
}
/**
* #return string
*/
public function getName()
{
return 'bissap_forumbundle_post';
}
}
TopicType.php:
<?php
namespace BISSAP\ForumBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TopicType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', array(
'label' => 'Titre'))
->add('posts','collection', array( 'type' => new PostType(), 'label' => false, 'allow_add' => true,))
->add('save', 'submit', array(
'attr' => array(
'class' => 'btn right-flt')))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BISSAP\ForumBundle\Entity\Topic'
));
}
/**
* #return string
*/
public function getName()
{
return 'bissap_forumbundle_topic';
}
}
Part of my Controller that use form:
public function categoryAction(Request $request)
{
//Arguments recovery from url
$URL = $request->getPathInfo();
$element_URL = explode("/", $URL);
$catId = $element_URL[3];
(isset ($element_URL[4]))?$pageId = $element_URL[4]:$pageId = '1';
$em = $this->getDoctrine()->getManager();
//Pagination
$findListTopics = $em->getRepository('BISSAPForumBundle:Topic')->findByForum($catId);
$listTopics = $this->get('knp_paginator')
->paginate($findListTopics,$pageId/*page number*/,8/*limite per page*/)
//Form
$topic = new \BISSAP\ForumBundle\Entity\Topic();
$form = $this->createForm(new TopicType(), $topic);
$user = $this->getUser();
$forum = $em->getRepository('BISSAPForumBundle:Forum')->find(1);
print_r($form->getData());
if ($form->handleRequest($request)->isValid()) {
$topic->setUser($user);
$topic->setForum($forum);
$topic->setViewCount(23);
$topic->setContent("content a sup");
$topic->setReplyCount(123);
$topic->setLastPost(25);
$topic->setSlug('slug_sluggg');
$topic->setGenre('genre');
$em->persist($topic);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Sujet bien enregistrée.');
return $this->redirect($this->generateUrl('bissap_forum_topic', array('topic_id' => $topic->getId())));
//return $this->render('BISSAPForumBundle:F:topic-forum.html.twig', array('topicId' => $topic->getId(), 'user' => $user, 'topic' => $topic, 'firstDisplay' => true));
}
return $this->render('BISSAPForumBundle:F:category-forum.html.twig', array('listTopics' => $listTopics, 'catId' => $catId, 'form' => $form->createView(), 'user' => $user));
}
So, i would like to get to $post object to access the setTopic() and setUser() just before flush()...I thought to use $form->getData(), but I don't know how use it to get secondary object ($post).
When i submit the form two object are persisted, Topic and Post, sorry but i don't see the problem with the topic title..?!
Related
My application show this error
Type error: Too few arguments to function AppBundle\Form\ActualiteType::__construct(), 0 passed in /Applications/MAMP/htdocs/SyndicNous/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php on line 90 and exactly 2 expected
My formType
class ActualiteType extends AbstractType
{
/**
* #var bool $admin
*/
private $admin;
/**
* #var User $user
*/
private $user;
/**
* ActualiteType constructor.
* #param bool|false $admin
*/
public function __construct($admin = false, $user)
{
$this->admin = $admin;
$this->user = $user;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$categories = array(
'Travaux' => 'Travaux',
'Voisinage' => 'Voisinage',
);
$builder
->add('title')
->add('category')
->add('content')
->add('datePublish')
->add('category', ChoiceType::class, array(
'choices' => $categories
)
);
if ($this->user->getResidence() != null) {
$builder->add('residence', EntityType::class, array(
'class' => 'AppBundle:Residence',
'choices' => $this->user->getResidence(),
));
} else {
$builder->add('residence', 'entity', array(
'class' => 'AppBundle:Residence',
'choice_label' => 'name'
));
};
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Actualite'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_actualite';
}
}
Do you have any idea where the problem would come from? Thank you
I do not understand what you're trying to do. You do not need to use the constructor to pass parameters to your formType. There is the second parameter of the buildForm method for this ($options).
In your controller, create your form like this :
$form = $this->createForm(ActualiteType::class, $actualite, [
'admin' => $admin,
'user' => $user
]);
And modify your formType like that :
class ActualiteType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$admin = $options['admin']; // Not used ?
$user = $options['user'];
$categories = array(
'Travaux' => 'Travaux',
'Voisinage' => 'Voisinage',
);
$builder->add('title')
->add('category')
->add('content')
->add('datePublish')
->add('category', ChoiceType::class, array(
'choices' => $categories
)
);
if ($user->getResidence() != null) {
$builder->add('residence', EntityType::class, array(
'class' => 'AppBundle:Residence',
'choices' => $user->getResidence(),
));
} else {
$builder->add('residence', 'entity', array(
'class' => 'AppBundle:Residence',
'choice_label' => 'name'
));
};
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Actualite',
'admin' => null, // Default
'user' => null // Default
));
}
}
Do not forget to put the default values of your options in configureOptions method.
My form data returned after the flow has finished is a bit odd. Two of the entity properties (that were populated using an embedded form) are in the array format keyed by the field names and not a string.
Engine and model arrays of the values lol.
Anything I have done to cause this?
Here is my setup.
The flow
<?php
namespace AppBundle\Form;
use AppBundle\Form\Type\CreateVehicleFormType;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
/**
* Class CreateVehicleFlow
* #package AppBundle\Form
*/
class CreateVehicleFlow extends FormFlow
{
protected $allowRedirectAfterSubmit = true;
/**
* #return array
*/
protected function loadStepsConfig()
{
return [
// Step 1.
[
'label' => 'Wheels',
'form_type' => CreateVehicleFormType::class,
],
// Step 2.
[
'label' => 'Engine',
'form_type' => CreateVehicleFormType::class,
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine();
},
],
// Step 3.
[
'label' => 'Model',
'form_type' => CreateVehicleFormType::class
],
// Step 4.
[
'label' => 'Confirmation',
],
];
}
}
Parent create vehicle form
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Class CreateVehicleFormType
* #package AppBundle\Form\Type
*/
class CreateVehicleFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
switch ($options['flow_step']) {
case 1:
$validValues = [2, 4];
$builder->add(
'numberOfWheels',
ChoiceType::class, [
'choices' => array_combine($validValues, $validValues),
'attr' => [
'class' => 'form-control input-lg',
'required' => 'required'
],
]);
break;
case 2:
$builder->add('engine', VehicleEngineFormType::class, []);
break;
case 3:
$builder->add('model', VehicleModelFormType::class, [
'numberOfWheels' => $options['data']->getNumberOfWheels()
]);
break;
}
}
/**
* #return string
*/
public function getBlockPrefix()
{
return 'createVehicle';
}
/**
* #return string
*/
public function getName()
{
return 'create_vehicle';
}
}
Child vehicle engine form
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Class CreateVehicleFormType
* #package AppBundle\Form\Type
*/
class VehicleEngineFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('engine', ChoiceType::class, [
'label' => false,
'choices' => [
'Diesel' => 'Diesel',
'Petrol' => 'Petrol',
'Electric' => 'Electric',
'Hybrid' => 'Hybrid'
],
'attr' => [
'class' => 'form-control input-lg',
'required' => 'required'
],
]
);
}
/**
* #return string
*/
public function getName()
{
return 'vehicle_engine';
}
}
Child vehicle model form
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class CreateVehicleFormType
* #package AppBundle\Form\Type
*/
class VehicleModelFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [];
if ($options['numberOfWheels'] === 2) {
$choices['BMW R nineT'] = 'BMW R nineT';
$choices['Ducati Monster 1200S'] = 'Ducati Monster 1200S';
} else {
$choices['Ferrari LaFerrari'] = 'Ferrari LaFerrari';
$choices['Aston Martin One-77'] = 'Aston Martin One-77';
}
$builder->add('model', ChoiceType::class, [
'label' => false,
'choices' => $choices,
'attr' => [
'class' => 'form-control input-lg',
'required' => 'required'
],
]
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'numberOfWheels' => 2,
]);
}
/**
* #return string
*/
public function getName()
{
return 'vehicle_model';
}
}
And finally my controller including my hack to get them back as string properties
/**
* One form type for the entire flow.
*
* #param Request $request
* #return Response
*
* #Route("/create.vehicle", name="create.vehicle")
*/
public function CreateVehicleAction(Request $request)
{
$formData = new Vehicle();
$flow = $this->get('app.form.flow.create_vehicle'); // must match the flow's service id
$flow->setGenericFormOptions([
'action' => $this->generateUrl('create.vehicle')
]);
$flow->bind($formData);
// form of the current step
$form = $submittedForm = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
// flow finished
// Bit hacky but not sure hy they are arrays set to the property.
if (is_array($formData->getEngine())) {
$formData->setEngine($formData->getEngine()['engine']);
}
if (is_array($formData->getModel())) {
$formData->setModel($formData->getModel()['model']);
}
$flow->reset();
$this->flashMessage(
'success',
[
'title' => 'Vehicle Created',
'message' => sprintf(
'You have created a %s %d wheeled vehicle.',
$formData->getModel(),
$formData->getNumberOfWheels()
)
]
);
return $this->redirect(
$this->getPresencePath('sign_in.page')
);
}
}
if ($flow->redirectAfterSubmit($submittedForm)) {
$params = $this
->get('craue_formflow_util')
->addRouteParameters(
array_merge(
$request->query->all(),
$request->attributes->get('_route_params')
),
$flow
);
return $this->redirect(
$this->getPresencePath('sign_in.page', $params)
);
}
return $this->render('AppBundle:multi-step-form:create-vehicle.html.twig', array(
'form' => $form->createView(),
'flow' => $flow
));
}
Can anyone spot the issue?
I have three entity for a project : Book, Author and Theme. For information, Author and Theme are linked with Book in Many to One/One to Many. A book has one or more author and one or more themes.
And I have generated crud about theses for basics functions (add, edit and delete) and default views/form.
But I need to do some functions search, three exactly:
Search Book with part of the Title
Search Book with one Author
Search Book with one or more Theme.
I coded the three functions and working good but I have a problem about the fields used in search forms.
The first one is correct, it show a input field where we put the title and the book list as result is correct but the two others …
Because of the relationship the field are drop list where the user choose a item and search it, the result is correct but I wish for the second to have an input field for search the author name and the last to display checkbox so I’ve made research but I can’t understand why in my forms type, I can’t change the kind of field I want.
Here my two Forms Type :
Search with one Author : I just want to display an input field and not a list but get error each time
<?php namespace Projet\BibliothequeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BookByAuthorType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('authors')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Projet\BibliothequeBundle\Entity\Book'
));
}
/**
* #return string
*/
public function getName()
{
return 'authors';
}
}
Search Book with one or more Theme : I tried to display chechbox by link the 'themes' array to result of checkbox but don't work too.
<?php namespace Projet\BibliothequeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class BookByThemeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// $themes = array();
// $themes=$builder->add('themes');
$builder
->add('themes', 'choice', [
'choices' => 'themes',
'multiple' => true,
'expanded' => true
])
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Projet\BibliothequeBundle\Entity\Book'
));
}
/**
* #return string
*/
public function getName()
{
return 'themes';
}
}
UPDATE : My BookController functions maybe it's the origin of my problems.
// AUTHOR SEARCH FUNCTIONS
public function SearchByAuthorAction() {
$entity = new Book();
$form = $this->CreateSearchAuthorForm($entity);
return $this->render('ProjetBibliothequeBundle:Book:searchbyauthor.html.twig', array(
'form' => $form->createView(),
));
}
private function CreateSearchAuthorForm(Book $entity) {
$form = $this->createForm(new BookByAuthorType(), $entity, array(
'action' => $this->generateUrl('book_submit_search_by_author'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Search'));
return $form;
}
public function SubmitSearchByAuthorAction(Request $request) {
$entity = new Book();
$form = $this->CreateSearchAuthorForm($entity);
$form->handleRequest($request);
$author = $form->get('authors')->getData();
$repository = $this->getDoctrine()
->getEntityManager()
->getRepository('ProjetBibliothequeBundle:Book');
$Booklist = $repository->findByAuthor($author);
return $this->render('ProjetBibliothequeBundle:Book:resultbyauthor.html.twig',
array('Booklist' => $Booklist));
}
//THEME SEARCH FUNCTIONS
public function SearchByThemeAction() {
$entity = new Book();
$form = $this->CreateSearchThemeForm($entity);
return $this->render('ProjetBibliothequeBundle:Book:searchbytheme.html.twig', array(
'form' => $form->createView(),
));
}
private function CreateSearchThemeForm(Livre $entity) {
$entities = $this->getDoctrine()->getManager()->getRepository('ProjetBibliothequeBundle:Book')->findAll();
$form = $this->createForm(new BookByThemeType(), $entity, array(
'action' => $this->generateUrl('book_submit_search_by_theme'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Search'));
return $form;
}
public function SubmitSearchByThemeAction(Request $request) {
$entity = new Book();
$form = $this->CreateSearchThemeForm($entity);
$form->handleRequest($request);
$theme = $form->get('themes')->getData();
$repository = $this->getDoctrine()
->getEntityManager()
->getRepository('ProjetBibliothequeBundle:Book');
$Booklist = $repository->findByTheme($theme);
return $this->render('ProjetBibliothequeBundle:Book:resultbytheme.html.twig',
array('Booklist' => $Booklist));
}
There is no need to set data_class for GET operations. Just create a basic form, may be like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('themes', 'choice', [
'choices' => 'themes',
'multiple' => true,
'expanded' => true
])
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(['method' => 'GET', 'csrf_protection' => false]);
}
in your action catch values from URL, pass them to findBy method or manually build you query:
public function someAction(Request $request)
{
...
$form->createForm(new FORM_TYPE, null);
....
$filters = $request->query->get($form->getName());
$result = $entityManager->getRepository(REPOSITORY)->findBy($filters, $orderBy, $limit, $offset);
...
}
I have a symfony form which I use either for creating new entities of for parametring existing ones. Parametring works fine.
If creating new entities, I use a presetdata event listener to not render the fields so only the prototype is rendered.
In that case addind a new row works fine only if no entities exist in my database. if there is already one row, it throws the
This form should not contain extra fields
error. I have set true to allow_add and allow_delete. Any Idea about what the issue is?
My controller:
/**
* #Route("/create", name="worker_create")
* #Method({"POST","GET"})
* #Template(":FoodAnalytics/Worker:create_worker.html.twig")
* #param Request $request
* #return array|\Symfony\Component\HttpFoundation\RedirectResponse
*/
public function createWorkerAction(Request $request)
{
$formManager = $this->get('form_manager');
$createWorkerForm = $formManager->createForm(new UserUserWorkersType(false, '_create'), $this->getUser(), 'POST' , 'worker_create',null,'Enregistrer');
if ($request->getMethod() == 'POST')
{
if ($formManager->handleRequestAndFlush($createWorkerForm, $this->getUser()))
{
$formManager->addSuccess('Les associés ont été rajoutés.');
return $this->redirect($this->generateUrl('my_teams'));
}
return $this->render(
':FoodAnalytics/Team:my_teams.html.twig',
array(
'create_worker_form' => $createWorkerForm->createView(),
'tab' => 'create_worker'
));
}
return array(
'create_worker_form' => $createWorkerForm->createView()
);
}
My first form:
<?php
//src/AppBundle/Form/User/UserUserWorkersType.php
namespace AppBundle\Form\User\UserCollections;
use AppBundle\Form\FoodAnalytics\WorkerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserUserWorkersType extends AbstractType
{
private $displayExisting;
private $suffix;
public function __construct($displayExisting = true, $suffix)
{
$this->displayExisting = $displayExisting;
$this->suffix = $suffix;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('workers', 'collection', array(
'type' => new WorkerType($this->displayExisting),
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
'label' => 'Associés',
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'AppBundle\Entity\User\User',
)
);
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_user_user_workers'.$this->suffix;
}
}
My collection form:
<?php
namespace AppBundle\Form\FoodAnalytics;
use AppBundle\Entity\FoodAnalytics\Worker;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class WorkerType extends AbstractType
{
private $displayExisting;
public function __construct($displayExisting = true)
{
$this->displayExisting = $displayExisting;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event)
{
/** #var $worker Worker */
$worker = $event->getData();
$form = $event->getForm();
if (!$worker || ($worker && $this->displayExisting))
{
$form
->add('title','choice',array(
'choices' => ['M.' => 'M.', 'Mme' => 'Mme'],
'label' => 'Titre',
))
->add('firstName','text',array(
'label' => 'Prénom',
))
->add('lastName','text',array(
'label' => 'Nom',
))
->add('position','text',array(
'label' => 'Poste occupé',
))
->add('totalCost','number',array(
'label' => 'Coût total mensuel (€)',
'required' => false,
'attr' => array(
'data-toggle' => "tooltip",
'data-placement' => "top",
'title' => "Indiquez ici le coût total que représente le salarié par mois (salaires brut, primes, taxes, coûts cachés, etc). Cette donnée est totalement confidentielle.",
)))
->add('weeklyHours','number',array(
'label' => "Durée de travail par semaine (h)",
'required' => false,
'attr' => array(
'data-toggle' => "tooltip",
'data-placement' => "top",
'title' => "Indiquez ici la durée de travail hebdomadaire de votre associé. Cette donnée est totalement confidentielle.",
)));
}
});
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\FoodAnalytics\Worker'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_foodanalytics_worker';
}
}
I have a form "AddressType" with an event listener that works as expected when instantiated alone. Here is the code.
namespace Nc\ClientsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Nc\ClientsBundle\Form\EventListener\AddCityFieldSubscriber;
class AddressType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('street')
->add('number', 'text')
->add('complement')
->add('district')
->add('state', 'entity', array(
'class' => 'ClientsBundle:State',
'property' => 'name',
));
$subscriber = new AddCityFieldSubscriber($builder->getFormFactory());
$builder->addEventSubscriber($subscriber);
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Nc\ClientsBundle\Entity\Address',
);
}
public function getName()
{
return 'addresstype';
}
}
<?php
namespace Nc\ClientsBundle\Form\EventListener;
use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
use Doctrine\ORM\EntityRepository;
class AddCityFieldSubscriber implements EventSubscriberInterface
{
private $factory;
public function __construct(FormFactoryInterface $factory)
{
$this->factory = $factory;
}
public static function getSubscribedEvents()
{
// Tells the dispatcher that we want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
// During form creation setData() is called with null as an argument
// by the FormBuilder constructor. We're only concerned with when
// setData is called with an actual Entity object in it (whether new,
// or fetched with Doctrine). This if statement let's us skip right
// over the null condition.
if (null === $data) {
return;
}
if (!$data->getCity()) {
$form->add($this->factory->createNamed('city_selector', 'city', null, array(
'choices' => array('' => '-- Selecione um estado --'),
'required' => true,
'expanded' => false,
'multiple' => false,
)));
} else {
$city = $data->getCity();
$form->add($this->factory->createNamed('city_selector', 'city', null, array(
'choices' => array($city->getId() => $city->getName()),
'required' => true,
'expanded' => false,
'multiple' => false,
)));
}
}
}
<?php
namespace Nc\ClientsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Nc\ClientsBundle\Form\DataTransformer\CityToIdTransformer;
class CitySelectorType extends AbstractType
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilder $builder, array $options)
{
$transformer = new CityToIdTransformer($this->om);
$builder->prependNormTransformer($transformer);
}
public function getDefaultOptions(array $options)
{
return array(
'invalid_message' => 'Selecione um estado e uma cidade.',
);
}
public function getParent(array $options)
{
return 'choice';
}
public function getName()
{
return 'city_selector';
}
}
<?php
namespace Nc\ClientsBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Nc\ClientsBundle\Entity\City;
class CityToIdTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (city) to a integer (id).
*
* #param City|null $city
* #return string
*/
public function transform($city)
{
if ($city === null) {
return '';
}
return $city->getId();
}
/**
* Transforms a string (id) to an object (city).
*
* #param string $id
* #return City|null
* #throws TransformationFailedException if object (city) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$city = $this->om
->getRepository('ClientsBundle:City')
->findOneBy(array('id' => $id))
;
if (null === $city) {
throw new TransformationFailedException(sprintf(
'An city with id "%s" does not exist!',
$id
));
}
return $city;
}
}
What happens is that when I try to embed this form into the "ClientType" form, the "city" field is not rendered.
namespace Nc\ClientsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ClientType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
$builder->add('type', 'choice', array(
'choices' => array('1' => 'Comum', '2' => 'Parceiro'),
'expanded' => true,
'multiple' => false,
'required' => true,
));
$builder->add('contactInfo', new ContactInfoType(), array('label' => ' '));
$builder->add('address', new AddressType(), array('label' => ' '));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Nc\ClientsBundle\Entity\Client',
);
}
public function getName()
{
return 'clienttype';
}
}
When you try to embed city field and entity does not have city value filled, method $form->getData() returns null, so city field won't render at this case, because "if" statement always returns false (preSetData function in the subscriber class)
First of all we gotta check if city is filled, if so add corresponding form field, otherwise add blank field (or whatever).
Here are some code snippets:
Add city field if its value is filled
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (!($data instanceof Address) || !$data->getCity()) {
return;
}
$city = $data->getCity();
$form->add($this->factory->createNamed('city_selector', 'city', null, array(
'choices' => array($city->getId() => $city->getName()),
'required' => true,
'expanded' => false,
'multiple' => false,
)));
}
Add blank city field if its value is not filled (postSetData triggers just after from data is set)
public function postSetData(DataEvent $event)
{
$form = $event->getForm();
if (!$form->has('city_selector')) {
$form->add($this->factory->createNamed('city_selector', 'city', null, array(
'choices' => array('' => '-- Selecione um estado --'),
'required' => true,
'expanded' => false,
'multiple' => false,
)));
}
}
We also have to register postSetData event
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::POST_SET_DATA => 'postSetData'
);
}