Symfony forms from entity with additional fields - php

As I know I could create form using form type: $form = $this->createForm(new RegistrationType(), $user);
And here is form type:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('email', 'email');
$builder->add('terms', 'checkbox', array(
'mapped' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\UsersBundle\Entity\User'
));
}
public function getName()
{
return 'user_registration';
}
}
So I could add field term and don't map it to entity. But what is the way to validate this field? Sure I can do something like if ($form->get('terms')->getData()) in my controller but I want to use one function $form->isValid() to validate all fields (mapped and don't mapped)? May be any validate hooks or events exists?

It's covered in the Adding Validation section of the Forms chapter.

Related

Symfony Form CollectionType with API post of new/existing entities

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?

Symfony2 - Condition in FormType

I have a class RegisterType for my form:
<?php
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('price');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
The Register Entity has a relation with an Entity called Product
I would like to add this condition to the buildForm function:
<?php
if ($product->getTitle() == 'SuperProduct') {
$builder->add('amount', 'money', [
'required' => FALSE,
]);
}
?>
If the product title has the value 'SuperProduct' then I add a field in the the form.
But I have no idea about the syntax I have to use to call another Entity value in a FormType.
Thanks in advance for your help.
Ah good you've implemented your FormType as an independent class rather than in a controller, so what I'd do is pass in your product entity as an option to a private variable within your FormType class. I'm not sure what class your product is or the name space it resides in, so you'll have to fill that in.
I've also written this in the expectation that you could have more than one option to pass through in the future, so it'll be useful to other FormType classes you write. Each variable you define in the class scope can be set when you create an instance of the FormType class. Be sure to initialise these variables as false instead of null, or the code in the constructor function won't see them.
<?php
class RegisterType extends AbstractType
{
private $product = false;
public function __construct(array $options = [])
{
foreach ($options as $name => $value) {
if (isset($this->$name)) {
$this->$name => $value;
}
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price')
;
if ($this->product && is_a("\Namespace\To\Your\Entity\Called\Product", $this->product)) {
if ($this->product->getTitle() == 'SuperProduct') {
$builder
->add('amount', 'money', [
'required' => FALSE,
])
;
}
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
That "is_a()" function checks that the product variable passed in through the form type class constructor really is an instance of your product entity, so you can be sure that the getTitle() function exists.
Now when you create a new instance of your RegisterType class, you need to pass through an options array including your product. Two conceptual variable names for you to rename here.
<?php
$form = $this->createForm(new RegisterType([
"product" => $instanceOfProductEntityOrNull,
]), $theEntityTheFormWillBeHandling);
?>
Try something like this:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
if ($product->getTitle() == 'SuperProduct') {
$form->add('name', 'text');
}
});
And do not forget the specific use
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

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: Fill some fields of a form with post data

I have a contact form that works fine. But I want to fill some of this fields when an user click in one link. I receipt the information of some fields in the form controller, but I don't know how can I show this form with this fields with these datas.
Thanks!
You can pass an object, which corresponds to your data fields, when you create your form :
In your controller:
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->add('save', 'submit')
->getForm();
Or if you have your own FormType class, bind it to your Entity :
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Build form here
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// Bind entity here
$resolver->setDefaults(array(
'data_class' => 'BlueLinea\Bundle\BlueHomeCareBundle\Entity\MyEntity',
));
}
}
And in your controller :
$myEntity = new MyEntity();
$form = $this->createForm(new MyFormType(), $myEntity);
See Forms (Building the Form) - Symfony

Categories