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.
Related
I want to create a form in Symfony. The form needs a collection of task fields which are loaded from the database. Every single field should have a dropdown to select a person who is supposed to do this task.
What I want to do is to map those two entities together. What I have so far is this:
createFormType.php
class CreateFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("dateValidFrom", DateTimeType::class)
->add("dateValidUntil", DateTimeType::class)
->add("generalTasks", CollectionType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
"data_class" => DTOPlan::class
]);
}
}
DTOPlan.php
class DTOPlan
{
public \DateTimeImmutable $dateValidFrom;
public \DateTimeImmutable $dateValidUntil;
public array $tasks;
public function setGeneralTasks(array $generalTasks): void
{
$this->generalTasks = array_fill_keys($generalTasks, null);
}
...getter and setter
}
Controller.php
public function newPlan(Request $request)
{
$generalTasks = $this->generalTaskRep->findAll();
$dtoPlan = new DTOPlan();
$dtoPlan->setGeneralTasks($generalTasks);
$form=$this->createForm(CreateFormType::class, $dtoPlan);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->planRepository->updatePlan($form->getData());
$this->redirectToRoute("app.plan.show");
}
return $this->render("/Plan/plan_create.html.twig", [
"createForm" => $form->createView()
]);
}
The labels are now correct: Form
Now I want to insert a dropdown into them for selecting firstName and lastName of Person
How can I manage that?
Building an internal API endpoint which allows another service to update specific fields for users, identified by email addresses. If the user does not exist, it needs to be created.
The code is working perfectly fine providing only new users are submitted.
This is the POST request to the API endpoint.
[
{
"email":"existing#user.com",
"favouriteFood": "pizza"
},
{
"email":"new#user.com",
"favouriteFood": "sweets"
}
]
Controller action
public function postUsersAction(Request $request)
{
$form = $this->createForm(UserCollectionType::class);
$form->submit(['users' => json_decode($request->getContent(), true)], true);
if (!$form->isValid()) {
return $form;
}
/** #var User $user */
foreach ($form->getData()['users'] as $user) {
$this->userManager->updateUser($user);
}
$this->em->flush();
return $form->getData()['users'];
}
UserCollectionType
class UserCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('users', 'collection', [
'allow_add' => true,
'by_reference' => false,
'type' => UserType::class
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
'cascade_validation' => true
]);
}
public function getBlockPrefix()
{
return '';
}
}
UserType
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('favouriteFood', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'validation_groups' => ['user_api'],
'cascade_validation' => true
]);
}
}
How can I update the above code so that it will check to see if the user exists first, otherwise continue to create a new one how it's currently working.
I'd have assumed a DataTransformer could have been used, but not exactly how.
EDIT: Proposed solution by ShylockTheCamel
Inside the controller.
$post = json_decode($request->getContent(), true);
$users = [];
foreach ($post as $userRaw) {
$user = $this->em->findOneBy($userRaw['email']); // example search logic in the DB
if (!$user) {
$user = new User();
}
$users[] = $user;
}
$form = $this->createForm(UserCollectionType::class, $users);
$form->submit(['users' => $post], true);
If I understand correctly, your user(s) entity(ies) exist when you enter the foreach loop. So you must create them in the form creation process. In that case, why not check th existence of a user in one of your form validators?
I'm am new to Symfony 2 framework and i'm trying, without success, to create a registration form to add users to my db.
I followed this tutorial and everything seems to work fine, except that my form won't show up.
This is my UserType.php class:
class UserType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
echo "builder";
$builder->add('user', 'user_form');
$builder->add('password', 'repeated', array(
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password',
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'XXX\Entity\User'
));
}
RegistrationType.php:
class RegistrationType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
echo "1";
$userType=new UserType();
$builder->add('user', $userType);
echo "2";
$builder->add('Aggiungi', 'submit');
echo "3";
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'register_form';
}
}
And my AccountControllorer.php :
class AccountController extends Controller
{
public function registerAction()
{
$registration = new Registration();
$form = $this->createForm(new RegistrationType(), $registration, array(
'action' => $this->generateUrl('account_create'),
));
return $this->render(
'xxx:Account:register.html.twig',
array('form' => $form->createView())
);
}
public function createAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
echo "controller 1 ok";
$rt=new RegistrationType();
$r=new Registration();
$form = $this->createForm($rt, $r);
echo "controller 2 ok";
$form->handleRequest($request);
echo "all ok";
if ($form->isValid()) {
$registration = $form->getData();
$em->persist($registration->getUser());
$em->flush();
echo "yess";
}
return $this->render(
'xxx:Account:register.html.twig',
array('form' => $form->createView())
);
}
}
After some echoes in the code, i've found that the controller stops after "controller 1 ok" (i.e. the form is't created), so I echoed more in deep, and i've found that method builForm in UserType goes in a loop, it echoes "builder" infinite times, giving 500 internal Error in browser.
So, where is the problem? I'm stuck here from all the day. If you want more code snippet just ask!
Thanks in advance,
Panc
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. :)
In my Symfony2, I have a table of a user's emails (entities in my database):
{% for email in emails %}
...
{{ email.subject }}
...
{% endfor %}
I would like to make these selectable by wrapping the table in a form and adding a checkbox to each of these rows.
What's the best way to approach this in Symfony2? I can only think that I'll have to create a Type inside a Type inside a Type, which seems less than ideal:
class SelectableEmailsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('selectableEmails', 'collection', [ 'type' => new SelectableEmailType() ]);
}
public function getName()
{
return 'selectableEmails';
}
}
class SelectableEmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', new EmailType());
$builder->add('selected', 'checkbox');
}
public function getName()
{
return 'selectableEmail';
}
}
class EmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('subject', 'text');
...
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'EmailOctopus\Bundle\ListsBundle\Entity\ListEntity',
]);
}
public function getName()
{
return 'email';
}
}
This is answering the binding question.
// Query a list of emails
$emails = $emailRepository->findAll();
// Turn them into selectable emails
$selectableEmails = array();
foreach($emails as $email)
{
$selectableEmails[] = array('email' => $email, 'selected' => false);
}
// Pass this to the root form
$formData = array('selectableEmails' => $selectableEmails);
After processing a posted form you would pull out the list of selectableEmails and run through it to get the list actually selected.
This is just one approach. It works well for adding additional form related attributes to an entity without changing the entity itself.
I ended up assigning the id. of each email to a checkbox, using this solution:
Build a form having a checkbox for each entity in a doctrine collection