First of all, thanks for all of you who take a look of this problem.
I got a FormType like userFormType.
class UserFormType extends AbstractType{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('address','collection', ....)
->add('group','entity',....)
->add('companies','collection',....);
...
}
}
So you see i got two collection in the user form. I create the form and i set the companies. When i wanna to modify ONLY the information of companies and the address, but not in touch with group. So i have to render a user form, but not some company forms or address forms. so i write a controller like this:
$user= $this->get('security.context')->getToken()->getUser();
$form =$this->createForm(new UserForm(),$user);
$request = $this->get('request');
if ('POST' == $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
....
}
}
Of course, i do not wanna to modify the group so in the twig template, i dot not render the group. the form is rendered correctly, but every time i try to submit it, it tell me:
Argument 1 passed to ... User->setGroup() must be an instance of Group ... null given
So I'm asking, what should i do?
The error specifically is because your method definition in User is probably:
public function setGroup(Group $group);
but in order to set it null it would need to be:
public function setGroup(Group $group = null);
That will fix the error but it still might not be what you want functionality wise. My question is, why have the group field on the form if you are not using it? You may need another form type or pass an option to the form to not include the group field during edits.
Related
For my Poll Application i created a FormType called CampaignType which holds a CollectionType named blocks which in turn holds a CollectionType named lines, which holds a CollectionType named fields, which holds a CollectionType named pollResults.
In my next code example you can see my code that renders the View to fill a campaign(poll).
public function fillAction(Request $request, $id)
{
$campaign = $this->getDoctrine()->getRepository(Campaign::class)->find($id);
$entityManager = $this->getDoctrine()->getManager();
foreach ($campaign->getBlocks() AS $block){
foreach ($block->getLines() AS $line){
foreach ($line->getFields() AS $field){
$pollResult = new PollResult();
$pollResult->setCampaign($campaign);
$pollResult->setField($field);
$pollResult->setUser($this->getUser());
$entityManager->persist($pollResult);
$field->getPollResults()->add($pollResult);
}
}
}
$form = $this->createForm(CampaignType::class, $campaign);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
var_dump("true");
//$entityManager->persist($campaign);
$entityManager->flush();
return $this->redirectToRoute("grappt_poll_campaignShow", ['id' => $id]);
}
return $this->render('GrapptPollBundle:Campaigns:fill.html.twig', [
'campaign' => $campaign,
'form' => $form->createView()
]);
}
The only thing that must be persisted in the database are the PollResults.
Every PollResult has an entry for the campaign_id and the field_id it belongs to, the user_id who filled out the campaign and the value the user chose (and of course its own id, which gets generated automatically).
My Problem is that i don't know how to do that.
Where do i have to call $entityManager->persist($pollResult);.
Right now i put it directly under the initialization-stuff.
Do i have to put it into the if($form->isSubmitted() && $form->isValid())-query and loop through every pollResult?
Do i have to call $entityManager->persist($campaign); although nothing changes there?
Furthermore i wonder if i have to add something for the value-entry of each PollResult?
Thanks in advance for every answer
lxg
What will $form->isValid() return ?
It will depend on the validation constraints of you master form. If your validation constraints are in the annotations of your entity, in your master entity you should have the #Assert\Valid() annotation which will be sure that the nested form is valid :
class Campaign
{
/**
* #ORM\OneToMany(…)
* #Assert\Valid() // <- this line here
*/
private $blocks;
...
If you prefer to put your validation constraints in your CampaignType, you can put it in the options :
public function buildForm (FormBuilderInterface $builder, array $options)
{
$builder
->add('blocks', CollectionType::class,[
'entry_type' => BlockType::class,
'constraints' => array(new Valid()) // <- this line here
...
So, where should you put the persist()?
The best is to have Symfony's form validation (->isValid()) before any persistance, for security and data sanity (don't persist before ensuring csrf protection for instance). If you may add a lot of data (like persisting thousands of entities after one form submission), you can look into Doctrine's batch processing and bulk inserts : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/batch-processing.html
Should you also persist the Campaign object ?
It depends on the cascade persistence rules you have in your entity.
You can find all the rules to fine-tune the cascade here : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/working-with-associations.html#transitive-persistence-cascade-operations
I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.
When form is submitted, object's set method (League#setInformation) is called with corresponding data. All is working correctly. (See code below as an example)
I need to pass additional parameters to setInformation, namely current user id which is stored in session data.
That trick would help keeping session and model separate. Maybe useful in different situations too.
Do you know a way to deal with it?
class LeagueFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name');
$builder->add('information', 'collection', [
'type' => new LeagueInformationFormType(),
]);
}
public function setDefaultOptions(\Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver) {
$resolver->setDefaults([
'data_class' => 'xxx\Models\League',
]);
}
public function getName() {
return 'league';
}
}
class League {
public function getInformation() {
//...
}
public function setInformation($data) {
...
}
}
What I would do is declare form as a service, and inject the data from session. If you can, try to refactor your setInformation() function to two functions for example, so you dont have to provide all information through that one. However I think form events will help you set everything as you like.
If you are using Doctrine2 and the League class is actually a Doctrine2 Entity, I would recommend using a Doctrine2 subscriber/listener.
You can configure the subscriber/listener to do something either just before sending the data to the databse (onFlush), just after telling doctrine about a new entity (persist) or just before updating an existing record (update), whichever is the most appropiate in your case.
Inject the SecurityContext (#security.context in your DIC) into the subscriber/listener to pull out the current user information. (Make sure you check there is a user, because the subscriber wil also be run when nobody is logged in and a League object is saved)
The main advantage of this is that is does not pollute your form or controller. And if for some reason you create a League entity some other way the current user wil also be set.
Some docs:
http://doctrine-orm.readthedocs.org/en/latest/reference/events.html
It's a different story if you are not using Doctrine2 though.
I am a noobie in Symfony2. The handleRequest() function does not work for "GET" method whereas same code works fine for "POST".
public function addAction(Request $request){
$std = new Student();
$form = $this->createForm(new StudentForm, $std,
array( 'method'=>'GET'));
$form->handleRequest($request);
if($form->isSubmitted()){
$std= $form->getData();
$em= $this->getDoctrine()->getManager();
$em->persist($std);
$em->flush();
return $this->render('target.twig');
}
return $this->render('target twig',
array('newStdForm'=> $form->createView(),));
}
The above code is not working but if I change 'method':'GET' to 'method':'POST', then it works fine.
Specify the form's method in the StudentForm class's buildForm method. Therefore, handleRequest will be able to grab the GET parameters.
class StudentForm
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->setMethod('GET');
}
}
I think it is because in POST requests, parameters are passed in the body of the HTTP request. And that handleRequest looks for those values inside the body of the request. But in a GET request, parameters are passed in the url directly. So I think that is why the handling doesn't work.
Usually we use GET to fetch a page or url and a POST to send info to server.
Are you sure your twig template is correct?
I faced this issue today.
Pierre Roland's answer is partially correct for the current version.
I checked the default "HttpFoundationRequestHandler" which is called in "handleRequest".
An explicit GET form will be considered "submitted" if:
the form has no name (if you use a form class for example).
the request query contains a parameter with the form's name.
I have a case where we create registration for sports events.
The registration contains some fields specific to each sport. Some of which will be named similarly although they will be different for each sport. Example: "favorite position on the field":
For Basketball it would be a choice field between:
Point guard
Shooting guard
etc...
For baseball, it would be the same choice field but with some different choices available:
Pitcher
Infield
Outfield
...
When first creating the form (for display), the sport is passed as part of the data in the registration:
$registration = new Registration;
$registration->setEvent($event);
and $event->getSport(); would return the sport for that event.
So far so good, and adding a listener to the generation of my form, I can set only the fields specific to that sport:
public static function getSubscribedEvents()
{
return [FormEvents::POST_SET_DATA => 'preSetData'];
}
/**
* #param event DataEvent
*/
public function preSetData(DataEvent $event)
{
$form = $event->getForm();
if (null === $event->getData()) {
return;
}
// (The get event here means the real life sports gathering)
$sport = $event->getData()->getEvent()->getSport();
/**
* Then I customize the fields depending on the current sport
*/
}
The problem comes when the user submits this form back. In this case, $event->getData()->getEvent() is null.
The "event" (real life one) is a document_id field in the registration form (using MongoDB here).
If I listen to the ::BIND event instead of ::PRE_SET_DATA, then I can access everything, but it's too late to customize the form as it is already bound. ::PRE_BIND does the same as ::PRE_SET_DATA.
How can I correctly retrieve my Event and Sport Documents here in order to customize my form and validate it appropriately?
Why would you need an event to do such task? You can define the fields in the buildForm() action of the form class. To access the event object simply use $options['data']->getEvent()
So ... Finally found how to do this properly.It requires subscribing to two different events.
First time the form is built, some data is passed to it, therefore, the PRE_SET_DATA event contains that data and everything works fine as explained in the question.
On the moment the form is submitted, it is first created with NO data, therefore the data accessed in PRE_SET_DATA will be null. In this case we skip over the form customization:
public function preSetData(DataEvent $event)
{
$myEvent = $event->getData()->getEvent();
if (null === $myEvent) {
return;
}
$this->customizeForm();
}
This ensures that we don't run into issues when submitting the form and no data is passed, however getData() will return an empty object and not NULL.
Now, when the form is submitted, we will bind it to the data received. That's when we want to interfere. So we'll also subscribe to the PRE_BIND event:
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_BIND => 'preBind',
FormEvents::PRE_SET_DATA => 'preSetData',
];
}
In pre-bind, the data we receive is only an array of values and not an object graph.
But if we injected the object manager in our listener, then we can find our objects and work with them:
public function preBind(DataEvent $event)
{
$data = $event->getData();
$id = $data['event'];
$myEvent = $this->om
->getRepository('Acme\DemoBundle\Document\Event')
->find(new \MongoId($id));
if($myEvent === null){
$msg = 'The event %s could not be found';
throw new \Exception(sprintf($msg, $id));
}
$this->customizeForm();
}