I'm using Symfony 2.7.x
My goals :
1/ A form where the user chooses the figures
2/ Form submitting
3/ Compute something with the help of the submitted data
4/ Display the value
Then I want to create a form, an entity but I don't need any database.
<?php
namespace RD\FicheBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use RD\FicheBundle\Entity\Donnees;
use RD\FicheBundle\Form\DonneesType;
class ThController extends Controller
{
// ... code
public function calculsAction(Request $request)
{
$donnees = new Donnees();
$form = $this->get('form.factory')->create(new DonneesType(), $donnees);
$form->handleRequest($request);
if ($form->isValid())
{
// What should I use right here to get my data from the form?
// ???????????????????????
// My calculation
$monney= $log_num*$surf_num*$ann_num*$chauff_num*$del_num;
$CO2 = 200*$log_num*$surf_num*$ann_num*$chauff_num*$del_num;
// Getting back the data to the template to display them
return $this->render('RDFicheBundle:Th:calculs.html.twig', array(
'form' => $form->createView(),
'monney' => $monney,
'CO2' => $CO2
));
}
// ... code
}
}
?>
How to get the data coming from the form without doing anything with a database? Should I use an entity?
Form arent't expecting any storage/database layer if you dont't tell them to. (like with field types: entity, and options on other ones.)
In your case:
Getting the data from submitted (or unsubmitted) form is simple as
$form->getData()
If you do this after your $form->handleRequest($request); line, you will get submitted data.
You can also check if you form was submitted with $form->isSubmitted(), which return an boolean true/false.
If you want your form to populate the submitted data to associated entity (object), you just need set data_class in your form type like this
/**
* #inheritdoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Acme\AppBundle\Entity\Example'
]);
}
data_class option can be set also for separate, fields(types) of your form, if you got some more complex scenario.
These docs should help you here:
http://symfony.com/doc/current/book/forms.html#creating-form-classes
Also here you can see that forms are NOT persisted to db automaticly:
http://symfony.com/doc/current/book/forms.html#forms-and-doctrine
Related
I am building a form using Easy Admin's FormBuilder. My goal is to have an AssociationField which represents a OneToMany relationship, for example, to assign multiple products to a shop. Additionally, I only want some filtered products to be listed, so I overrode the createEditFormBuilder method in the CrudController, I used this question as reference, and this is the code for the overridden function :
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$filteredProducts = $context->getEntity()->getInstance()->getFilteredProducts();
$formBuilder->add('products', EntityType::class, ['class' => 'App\Entity\Product', 'choices' => $filteredProducts, 'multiple' => true]);
return $formBuilder;
}
I expected an Association field as the ones configured in the configureFields() function, however, the displayed field doesn't allow text search or autocomplete features, plus has incorrect height.
Expected:
Actual:
I tried to change the second argument in the $formBuilder->Add() function, but all specific EasyAdmin types threw errors.
UPDATE: I also tried using EasyAdmin's CrudFormType instead of EntityType, which doesn't support the 'choice' parameter. Still, the result was the same.
There is setQueryBuilder on the field, you can use it for filtering entities like this
<?php
// ...
public function configureFields(string $pageName): iterable
{
// ...
yield new AssociationField::new('products')->setQueryBuilder(function($queryBuilder) {
$queryBuilder
->andWhere('entity.id IN (1,2,3)')
;
})
;
// ...
}
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
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 using Symfony 2.1.3-DEV and trying to accomplish transforming entity to string (ID of some kind) and then back from string to entity when form is submitted. The issue is the same if I'm using the transformer given in the cookbook:
http://symfony.com/doc/master/cookbook/form/data_transformers.html
Controller code:
$task = $entityManager->find('AcmeTaskBundle:Task', $id);
$form = $this->createForm(new TaskType(), $task); // so $task->issue is Issue object
I get this error:
The form's view data is expected to be an instance of class
Acme\TaskBundle\Entity\Issue, but is a(n) string. You can avoid this
error by setting the "data_class" option to null or by adding a view
transformer that transforms a(n) string to an instance of
Acme\TaskBundle\Entity\Issue.
The thing is, that I already have a transformer, which transforms TO string.
From the Form.php:
if (null !== $dataClass && !$viewData instanceof $dataClass) {
throw new FormException(
//...
);
}
Why $viewData is checked to be instance of data_class parameter (or the guessed type of given object)? Isn't view data supposed to be string/array etc.? Am I missing something?
After some digging step-by-step I found the problem that I was facing.
View data indeed must be the instance of class specified by data_class parameter. If you are using transformer Object -> string, you must set the data_class parameter to null.
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
By default, data_class is result of get_class of specified initial data. If you pass object to controller's createForm or some corresponding form-creator function, and no default value for data_class exists, it will be set to class of given object.
Still, the example given in the docs works fine - if form is inner (inside another form), data_class will not be set so it will be null.
As it's very rare to make form only from one field (text field in my transformer case), usually this form with transformer will be inside some other form, so it'll work fine.
I had the same problem because I accidentally typed in my controller:
$em->getRepository('AcmeWhateverBundle:Something')->findBy(array('id' => $id), array());
instead of:
$em->getRepository('AcmeWhateverBundle:Something')->findOneBy(array('id' => $id), array());
So If you're not using any custom data transformers check that $entity in the following line is an object of the same class defined as data_class in your FormType:
Scope Controller:
$form = $this->createForm(new SomethingType(), $entity, array( ....
I'm bored of writing this at the end of every action in Symfony2:
return $this->render('Project:Bundle:view.twig', array(
'foo' => 1,
'bar' => 2
));
So I've attempted to hook into the request lifecycle just after an action has been run, in order to save myself some typing. I want to be able to do something similar to this in my controller instead:
$this->params = array(
'foo' => 1,
'bar' => 2
);
A listener would then pass the params to the render, and auto-detect the template using the action name. I realise I need to use Event Listeners to achieve this, but I can't seem to hook into the lifecycle at the right time...
kernel.controller is good, because I can get at the controller, but it's before the action has been run, so $this->params won't be set
kernel.response is after the action has run, but I can't seem to get at the controller itself from here
FYI - I've got a Zend background, and this is (obv) my first time using Symfony2... If I'm approaching this problem in entirely the wrong manner, shout!
If you're using the SensioFrameworkExtraBundle, you can use the #Template() annotation and return an array:
<?php
namespace Acme\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class BarController
{
/**
* #Template()
*/
public function bazAction()
{
return array(
'some_value' => $someValue;
);
}
}
The annotation tells it to look for the view in the default location based on bundle, controller and action name (in this case, AcmeFooBundle:Bar:baz.html.twig).