Symfony2/Twig: How to set allowed Values to dropDownField - php

I have 3 Entities: User , Report and ReportCategory.
A User can put Reports in ONE ReportCategory. In the User-Entity there is a list, which ReportCategories are allowed for the user. This works all fine - I made it with a connectionTable which has the userID and the reportCategoryId.
Now I make an Array in Controller to get all ReportCategories of the current logged in User:
public function newAction()
{
$entity = new Report();
$form = $this->createCreateForm($entity);
$userId = $this->get('security.context')->getToken()->getUser()->getId();
$user = $this->getDoctrine()->getRepository('MyBundle:User')->find($userId);
$userReportCategories = array();
foreach($user->getReportCategories() as $reportCategory)
{
$userReportCategories[] = $reportCategory->getId();
}
return array(
'entity' => $entity,
'form' => $form->createView(),
'userReportCategories' => $userReportCategories
);
}
How can I set only these Values to my twig template field? When I make an own field it is not managed form Doctrine!
{{ form_row(form.reportCategory, {'attr': {'class': 'form-control'}, 'label': 'Category'}) }}
THANKS FOR ANY HELP!!!
UPDATE:
My ReportType looks like this:
class ReportType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('reportCategory')
->add('creationDate', 'date', array(
'data' => new \DateTime()
))
->add('headline')
->add('text')
->add('user')
;
}
....

Create a form class and add an event listener so that the form is aware of the user. The following is an adaptation of How to dynamically Generate Forms Based on user Data in the Symfony docs. [It is not guaranteed to accurately capture your needs].
form class
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\ORM\EntityRepository;
// ...
class ReportFormType extends AbstractType
{
private $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// grab the user, do a quick sanity check that one exists
$user = $this->securityContext->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The ReportFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'class' => 'Acme\DemoBundle\Entity\ReportCategory',
'property' => 'category',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
// return $er->createQueryBuilder('c')
->select('category')
->where('user = $user);
},
);
// create the field, this is similar the $builder->add()
// field name, field type, data, options
$form->add('userReportCategories', 'entity', $formOptions);
}
);
}
// ...
}
new action
public function newAction()
{
$entity = new Report();
$form = $this->createForm(new ReportFormType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}

Related

Handle form on different route from where it's built Symfony 3

I'm actually creating a website (with symfony 3) where the login form is on the main page (route /). And I would like to handle this form on the /login route.
Unfortunately I don't know how to do that because my form is built in the indexAction() and my loginAction() has no visibility on the $form built in index...
/**
* #Route("/", name="home")
*/
public function indexAction()
{
$user = new User();
$form = $this->createFormBuilder($user)
->setAction($this->generateUrl('login'))
->setMethod('POST') //btw is this useless ?? Is it POST by default ?
->add('Login', TextType::class)
->add('Password', TextType::class)
->add('save', SubmitType::class, array('label' => 'Sign In'))
->getForm();
return $this->render('ShellCodeHomeBundle:Home:index.html.twig', array (
'login' => '',
'form_signin' => $form->createView(),
));
}
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request)
{
$user = new User();
//how do I handle the form ????
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$user = $form->getData();
//...
}
return $this->render('ShellCodeHomeBundle:Home:index.html.twig', array (
'login' => $user->getLogin(),
));
}
I guess it's useless to tell that, but I'm using twig and I insert the form like this :
<div class="col-lg-12" style="text-align: center;">
{{ form_start(form_signin) }}
{{ form_widget(form_signin) }}
{{ form_end(form_signin) }}
</div>
Hope you will able to help me ! Thanks !
Do not build your form within the controller, that's a bad practice, because you can't reuse this code.
You should create your form within FormType and define them as a service. That way, you'll be able to reuse this form as often as you need it to.
Taken from the docs linked below, here's an example for the FormType:
namespace AppBundle\Form;
use AppBundle\Entity\Post;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('summary', TextareaType::class)
->add('content', TextareaType::class)
->add('authorEmail', EmailType::class)
->add('publishedAt', DateTimeType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Post::class,
));
}
}
Then you could use the FormType as in this example:
use AppBundle\Form\PostType;
// ...
public function newAction(Request $request)
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
// ...
}
For detailed information, check the docs
Create a custom form type.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\{
PasswordType, SubmitType, TextType
};
use Symfony\Component\Form\FormBuilderInterface;
class LoginForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Login', TextType::class)
->add('Password', PasswordType::class)
->add('save', SubmitType::class, [
'label' => 'Sign In'
])
;
}
}
Here is the answer :
In your other function just build the same form, handle the request and check if it's valid in this second method.
#[Route('/url', name: 'app_url')]
public function index(Request $request, EntityManagerInterface $em) : Response {
$entity = new Entity();
$data = 'some data';
$form = $this->createForm(EntityFormType::class, $entity, [ 'data' => $data, 'action' => 'other_url' ]);
/** extra code and send the request **/
}
then in the second controller function :
#[Route('/otherUrl', name: 'other_url')]
public function validation(Request $request, EntityManagerInterface $em) : Response {
/** SAME FORM CODE HERE **/
$entity = new Entity();
$form = $this->createForm(EntityFormType::class, $entity, [ 'data' => $data, 'action' => 'other_url' ]);
/** extra code and send the request **/
}
The form will be received and the framework will understand it correctly, it's like it's the same form, each form data will be put in the right place in the new form instance faking it's the same.

symfony: access controller variables from form

Starting with Symfony is quite a learning curve. Even after reading for hours, I cannot get across this presumably simple problem. I want to load a choices-form with values from an entity.
Controller:
namespace AppBundle\Controller
class ItemController extends Controller
{
public function itemAction (Request $request)
{
$myItems = new Itemlist();
//some statements to fill $myItems
$form = $this->createForm (AllitemsType::class, $myItems);
// some more stuff
return $this->render (...);
}
}
Entity:
namespace AppBundle\Entity;
class Itemlist
{
protected $choices;
protected $defaultvalue;
public function __construct ()
{
$choices = array();
}
// all the get and set-methods to fill/read the $choices array and $defaultvalue
}
Form:
namespace AppBundle\Form
class AllitemsType extends AbstractType
{
public function buildForm (FormBuilderInterface $builder, array $options)
{
// and here is my problem: how can I fill next two lines with values from the Itemlist-Entity?
// The Itemlist instance has been build in the controller and is unknown here
$items = ??? // should be 'AppBundle\Entity\Itemlist->$choices
$defaultitem = ??? // should be 'AppBundle\Entity\Itemlist->$defaultvalue
$choices_of_items = array (
'choices' => $items,
'expanded' => true,
'multiple' => false,
'data' => $defaultitem,
);
$builder->add ('radio1', ChoiceType::class, $choices_of_items);
}
}
Any help appreciated,
Wolfram
$builder->add('choices', ChoiceType::class);
should be sufficient as you're binding an entity to the form, the process of getting values and setting them back is automatic. Of course you need to have setter and getter for choices field in AllitemsType
To give a complete answer - part above is the so called "best practice one" - you can also choose one of the following
$items = $options['data'];
or
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$allItems = $event->getData();
$form = $event->getForm();
$form->add('radio1', ChoiceType::class, [
'choices' => $allItems
]);
});
Second one should be preferred as, in options['data'], entity could change during form event's lifetime.
Pass variables with createForm object.
Controller:
namespace AppBundle\Controller
class ItemController extends Controller
{
public function itemAction (Request $request)
{
$myItems = new Itemlist();
$formVars = array("items" => array(1,2,3,4,6), "defaultItems" => 2); // Store variables
^^
//some statements to fill $myItems
$form = $this->createForm (new AllitemsType($formVars), $myItems);
^^
// some more stuff
return $this->render (...);
}
}
Now create constructor in form and set class variables items and defaultitem in form.
Form:
namespace AppBundle\Form
class AllitemsType extends AbstractType
{
$this->items = array();
$this->defaultitem = 0;
public function __construct($itemArr)
{
$this->items = $itemArr['items'];
$this->defaultitem = $itemArr['defaultItems'];
}
public function buildForm (FormBuilderInterface $builder, array $options)
{
$choices_of_items = array (
'choices' => $this->items, // User class variable
'expanded' => true,
'multiple' => false,
'data' => $this->defaultitem, // User class variable
);
$builder->add ('radio1', ChoiceType::class, $choices_of_items);
}
}
It should solve your problem.

Symfony 2 prefilled form with data from the database

I am trying to populate form fields with data from the database in order to edit them. I already searched on google.
Here is my controller which returns empty fields
public function userViewAction($id,Request $request){
$em = $this->getDoctrine()->getManager()->getRepository('BFVMailingBundle:MailingList');
$user = $em->findById($id);
$form = $this->get('form.factory')->createBuilder('form',$user)
->add('unsubscribed','checkbox')
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
->getForm();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// perform some action, such as save the object to the database
$em->flush();
return $this->redirect($this->generateUrl('user_view',array('id'=>$id)));
}
}
and this is my template
<div class="cell">
{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
{{ form_end(form) }}
</div>
Did I miss something?
EDIT - READ THIS FOR THE SOLUTION
As John Noel Implied I build an externalised form with the command
php app/console doctrine:generate:form BFVMailingBundle:MailingList
my entity was MailingList instead of User
the MailingListType is a form template which is generated in BFV\MailingBundle\Form. I've added the data types myself.
<?php
namespace BFV\MailingBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class MailingListType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$str = date("U");
$codeValue = sha1($str);
$builder
->add('secretCode','hidden',array( 'data' => $codeValue ))
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('unsubscribed','checkbox')
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BFV\MailingBundle\Entity\MailingList'
));
}
/**
* #return string
*/
public function getName()
{
return 'bfv_mailingbundle_mailinglist';
}
}
In the reformated controller I add to add the the form generator the instance of MailingList $user[0] instead of $user. I read in many websites that usually you put $variable directly in the form builder but that generated the following error:
The form's view data is expected to be an instance of class
BFV\MailingBundle\Entity\MailingList, but is a(n) array. You can avoid
this error by setting the "data_class" option to null or by adding a
view transformer that transforms a(n) array to an instance of
BFV\MailingBundle\Entity\MailingList
Thus in the controller:
public function userViewAction($id,Request $request){
if (!$id) {
throw $this->createNotFoundException('No id !!');
}
$em = $this->getDoctrine()->getManager()->getRepository('BFVMailingBundle:MailingList');
$user = $em->findById($id);
if (!$user){
throw $this->createNotFoundException('No user with the id selected');
}
$form = $this->createForm(new MailingListType(), $user[0]);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('user_view',array('id'=>$id)));
}
}
return $this->render('BFVMailingBundle:Default:user_view.html.twig',array(
'user'=>$user,
'form'=>$form->createView()
));
}
Conclusion: I got the view form rendering with populated data from the database.
To do this you'll want to look into form classes which will then act as a view (and will also populate) the data you provide. So in your example you'd create a form class UserType:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('unsubscribed','checkbox')
->add('name','text')
->add('givenName','text')
->add('additionalName','text',array('required'=>false))
->add('familyName','text',array('required'=>false))
->add('emailValue','text')
->add('language','choice',array(
'choices' => array('en_GB' => 'en_GB', 'es_ES' => 'es_ES', 'fr_FR' => 'fr_FR'),
'required' => true,
))
->add('commentary','textarea',array('required'=>false))
->add('save','submit')
;
}
public function getName()
{
return 'user';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Your\Entity\Class',
));
}
}
Then within your controller you'd do something along the lines of:
$form = $this->createForm(new UserType(), $user);
Then the rest of your controller as you have. Definitely read up on form classes though as that's the starting point for a lot of the advanced functionality of Symfony forms.
I'm not sure if this is what makes the difference, but did you try with:
$form = $this->createFormBuilder($user)
->add(...)
->getForm()
You may also check that the User you get is correctly read from the DB.
For example:
if (!is_object($user)) {
$this->createNotFoundException('The user does not exist');
}

Symfony form dropdown for entries

I am starting with coding in Symfony and I have the following problem:
Assume I have two Entities 'Client' and 'Project'. They are stored with Doctrine.
A Client has a Id, Name and an Email
A Project has a Id, client_id, name
So basically a project belongs to a Client and a Client has many projects.
My problem now:
When I'm creating a project, I want a dropdown with all possible clients. As I might be using a client drowpown somewhere else in my project I'm asking myself if there is a smart way to something like this:
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('client', new ClientListType());
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Project'
));
}
public function getName()
{
return 'project';
}
}
class ProjectController extends Controller
{
public function createAction(Request $request)
{
$project = new Project();
$options = array( ... );
$form = $this->createForm(new ProjectType(), $project, $options);
$form->handleRequest($request);
if($form->isValid()){
// persist project
return $this->redirectToRoute('show_projects');
}
return $this->render('AppBundle:Client:create.html.twig', array(
'form' => $form->createView()
));
}
}
Where ClientListType adds a select statement for all possible Clients to the form.
And $form->isValid() checks if the client (id) is valid or not.
At the moment I have the followin code in ProjectType to generate the dropdown entries:
function __construct($clients)
{
$this->clients = $clients;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('client', 'choice', array(
'choices' => $this->buildChoices()
));
}
public function buildChoices()
{
$res = array();
foreach ($this->clients as $client) {
$res[$client->getId()] = $client->getName();
}
return $res;
}
But I'm assuming there is a much better way to do this, because this seems like a common problem.
What I would do is simply this :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text')
->add('client', 'entity', array(
'class'=>'AppBundle\Entity\Client',
'property'=>'name'
));
}
Hope this helps.

Symfony2 custom form type key pair

I'm currently trying to implement a key-pair value for a form type, which is used together with the FOSRestBundle to allow for sending a request like the following:
{
"user": {
"username": "some_user",
"custom_fields": {
"telephone": "07777",
"other_custom_field": "other custom value"
}
}
}
The backend for this is represented as follows:
User
id, username, customFields
CustomUserField
id, field
CustomUserFieldValue
user_id, field_id, value
I've currently made a custom form as follows:
<?php
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add(
'custom_fields',
'user_custom_fields_type'
)
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\DemoBundle\Entity\User',
'csrf_protection' => false,
)
);
}
public function getName()
{
return 'user';
}
}
And my user_custom_fields_type:
<?php
class CustomUserFieldType extends AbstractType
{
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$fields = $this->em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field) {
$builder->add($field->getField(), 'textarea');
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'invalid_message' => 'The selected custom field does not exist'
)
);
}
public function getParent()
{
return 'collection';
}
public function getName()
{
return 'user_custom_fields_type';
}
}
This keeps giving me the error that there are extra fields. Which are the ones I've added in the CustomUserFieldType. How can I get this working?
Note: this is a simplified version of the actual code, I've tried removing all the irrelevant code.
You need to use form listeners to add dynamic fields to your forms:
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
In your case a PRE_SET_DATA should suffice. Something like this:
$em = $this->em;
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ( $em )
{
// get the form
$form = $event->getForm();
// fetch your data here
$fields = $em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field)
{
// make sure you add the new fields to $form and not $builder from this event
$form->add($field->getField(), 'textarea');
}
});
I had exactly the same issue and finally solved it by parsing the custom fields manually. If there is another solution, please share :)
In the UserType form:
$builder->addEventListener(FormEvents::POST_SET_DATA,
function (FormEvent $event) use ( $oEm, $oUser )
{
$oForm = $event->getForm();
$aFields = $oEm->getRepository('MyDBBundle:CustomUserField')->findAll();
/** #var CustomUserField $oField */
foreach($aFields as $oField)
{
$oForm->add(
'custom__'.$oField->getKey(),
$oField->getType(),
array(
'label' => $oField->getField(),
'mapped' => false,
'required' => false
)
);
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oUser, 'field' => $oField));
if(null !== $oFieldValue) {
$oForm->get('custom__' . $oField->getKey())->setData($oFieldValue->getValue());
}
}
}
);
Then, in your controller action which handles the request of the submitted form:
// Handle custom user fields
foreach($oForm->all() as $sKey => $oFormData)
{
if(strstr($sKey, 'custom__'))
{
$sFieldKey = str_replace('custom__', '', $sKey);
$oField = $oEm->getRepository('MyDBBundle:CustomUserField')->findOneBy(array('key' => $sFieldKey));
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oEntity, 'field' => $oField));
if($oFieldValue === null)
{
$oFieldValue = new CustomUserFieldValue();
$oFieldValue->setUser($oEntity);
$oFieldValue->setField($oField);
}
$oFieldValue->setValue($oFormData->getData());
$oEm->persist($oFieldValue);
}
}
(Assuming that there is both a "field" property and a "key" in the CustomUserField entity; key is a unique, spaceless identifier for your field and field is the human friendly and readable field label.)
This works so hope it can be helpful. However, wondering if someone has a better solution. :)

Categories