How to make a Symfony GET form redirect to route with parameter? - forms

I want to create a form for searching a profile by username which redirect then to the profile page of the user. Btw, I use Symfony 3.2.
I reckon the natural way for doing this would be a GET action form. It would even allow a customer to change the url directly with the good username to see its profile.
Here is the code of my controller :
ProfileController.php
//...
/** #Route("/profil/search", name="profil_search") */
public function searchAction() {
$builder = $this->createFormBuilder();
$builder
->setAction($this->generateUrl('profil_show'))
->setMethod('GET')
->add('username', SearchType::class, array('label' => 'Username : '))
->add('submit', SubmitType::class, array('label' => 'Search'));
$form = $builder->getForm();
return $this->render('profils/profil_search.html.twig', [
'form' => $form->createView(),
]);
}
/** #Route("/profil/show/{username}", name="profil_show") */
public function showAction($username) {
$repository = $this->getDoctrine()->getRepository('AppBundle:User');
$searchedUser = $repository->findOneByUsername($username);
return $this->render('profils/profil_show.html.twig', [
'searchedUser' => $searchedUser,
]);
}
//...
This code will lead to the following error message :
Some mandatory parameters are missing ("username") to generate a URL for
route "profil_show".
I read the documentation thoroughly but couldn't guess, how can I pass the username variable to the profil_show route as a parameter ?
If my way of doing is not the good one, thanks for telling me in comments but I'd still like to know how to use GET forms.
EDIT :
Thanks to #MEmerson answer, I get it now. So for future noobs like me, here is how I did it :
/** #Route("/profil/search", name="profil_search") */
public function searchAction(Request $request) {
$data = array();
$builder = $this->createFormBuilder($data);
$builder
//->setAction($this->generateUrl('profil_show'))
//->setMethod('GET')
->add('username', SearchType::class, array('label' => 'Username : '))
->add('submit', SubmitType::class, array('label' => 'Search'));
$form = $builder->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
return $this->redirectToRoute('profil_show', array('username' => $data["username"]));
}
return $this->render('profils/profil_search.html.twig', [
'method' => __METHOD__,
'form' => $form->createView(),
'message' => $message,
]);
}

If you take a look at the error message it says that the problem is where you are trying to generate the URL for the path 'profil_show'.
Your controller annotations require that the URL be populated with a user name
/** #Route("/profil/show/{username}", name="profil_show") */
this means that Symfony is expecting http://yoursite.com/profil/show/username for the route. But if you want to pass it as a GET form posting it really should be expecting http://yoursite.com/profil/show?username
you can add a second route or change your existing route to be
/** #Route("/profil/show", name="profil_show_search") */
that should solve your problem.

Related

Create a simple form with Symfony

I'm getting crazy with a veeery simple thing! I just want to make a small form with one text input field and a validation button and get the value entered by the user. And it doesn't work. Absolutely crazy... I'm sure it's a very small thing, but I cannot solve it!
Here is my code:
<?php
namespace PublicBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
/**
* Description of AccountRecoveryController
*
*/
class AccountRecoveryController extends Controller {
/**
*
* #Route("/account_recovery", name="account_recovery")
*/
function accountRecoveryAction(Request $request){
$form = $this->createFormBuilder()
->add('username', TextType::class, array(
'label' => 'Adresse e-mail',
'attr' => array('placeholder' => 'E-MAIL'),
'mapped' => false
))
->add('submit', SubmitType::class, array(
'label' => 'Submit'
))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted()){
echo 'ok';
$eMail = $form->get('username')->getData();
echo $eMail;
}
return $this->render('accountRecoveryRequest.html.twig', array(
'form' => $form->createView(),
));
}
}
The form is rendered and works. The "ok" is displayed after clicking on "Submit". Buuuut $eMail is always empty... Why??
Any idea?
Thanks in advance.
You should also check if the form is valid in your if
if ($form->isSubmitted() && $form->isValid()){
....
If it is not valid, you can check for errors with
$form->getErrors(true)
The profiler should also give you enough pointers as to why you have no data, for example invalid values etc.
And you can always use dump() to check what your values are at any point in your code.

Editing a form -> Forced to modify image [Easy to answer i think]

I am trying to modify my profile entity but in it i have a variable for the profile image path ($avatarPath). But when i am trying to modify my entity with a form i am forced to "upload" a new file for validated the form (the value of the path file is at null for default i think, so when i accept the form without an image the tells me that my form is not valid and it lack the image
so my goal is to set by default the image of the profile in the edit form or to not put the button upload but making the form work (and i will put the upload file in an other page)
(that's for sure an idiot error but i don't see where)
My $avatarPath
/**
* #var string
*
* #Assert\NotBlank(message="Please enter an image")
* #Assert\Image()
* #ORM\Column(name="avatar_path", type="string", length=255, nullable=true)
*/
protected $avatarPath;
My controller :
/**
* Creates a new profile entity.
*
* #Route("/edit/{id}", name="profile_edit")
*/
public function editProfileAction(Request $request, User $user, Profile $profile)
{
$loggedAs = $this->getUser();
$form = $this->createForm(ProfileType::class, $profile);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/**
* #var UploadedFile $file
*/
$file = $form->get('avatar_path')->getData();
$fileName = md5(uniqid()) . '.' . $file->guessExtension();
$file->move($this->getParameter('image_directory'), $fileName);
$profile->setAvatarPath($fileName);
if ($profile->getAvatarPath() == NULL)
$profile->setAvatarPath('NULL');
$em = $this->getDoctrine()->getManager();
$em->persist($profile);
$em->flush();
$user->setIdProfile($profile);
$em2 = $this->getDoctrine()->getManager();
$em2->persist($user);
$em2->flush();
return $this->redirectToRoute('user_list');
}
return $this->render('admin/user/new_profile.html.twig', array(
'profile' => $profile,
'form' => $form->createView(),
'loggedAs' => $loggedAs,
));
}
My form :
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('resume')
->add('is_male')
->add('birth', BirthdayType::class, array(
'years' => range(1930,2018)
))
->add('email', EmailType::class, array(
'label' => 'form.email'
))
->add('phone')
->add('language')
->add('travel')
->add('pets')
->add('avatar_path', FileType::class, array(
'data_class' => null
))
->add('avatar_path', FileType::class, array(
'data_class' => null,
'required' => false
));
}
Thx for anyone who will try to help :p
You could try to set the property mapped => false to the avatar_path field in your Form.
Doing this will cause symfony to do two things. The first is to ignore the avatar_path field when validating the form which happens in your controller when form->isValid() is called. Secondly symfony will not assign any value to the field which happens when you call $form->handleRequest($request).
If you choose to use the mapped => false property you will need to manually set the avatar_path on the Profile entity.
Here is the documentation from symfony http://symfony.com/doc/3.4/reference/forms/types/file.html (these docs are for symfony 3.4)
Hope this helps.
Edit: I assume the form is failing the validation checks because of the two #Assert Statements in your Entity. Looking at your code I think you can safely remove those since you are already allowing the property $avatarPath to be nullable anyway.
I put that :
->add('avatar_path', FileType::class, array(
'data_class' => null,
'mapped' => false,
'required' => false
));
but nothing appens always the same :
That just refresh the page so the form is not valid ...

Symfony form that generates another form

I have a controller action the prompts the user for some free text input. When submitted the text is parsed into some number of objects that I want to put out on another form for the user to confirm that the initial parsing was done correctly.
Normally after dealing with the response to a form submission we call $this->redirectToRoute() to go off to some other path but I have all these objects laying around that I want to use. If I redirect off someplace else I lose them.
How can I keep them? I tried building my new form right there in the controller action method but then its submission does not seem to be handled properly.
/**
* #Route( "/my_stuff/{id}/text_to_objects", name="text_to_objects" )
*/
public function textToObjects( Request $request, Category $category ) {
$form = $this->createForm( TextToObjectsFormType::class, [
'category' => $category,
]);
$form->handleRequest( $request );
if( $form->isSubmitted() && $form->isValid() ) {
$formData = $form->getData();
$allTheStuff = textParserForStuff( $formData['objectText'] );
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
return $this->render( 'my_stuff/confirmation.html.twig', [
'form' => $nextForm->createView(),
'category' => $category,
] );
}
return $this->render( 'my_stuff/text.html.twig', [
'form' => $form->createView(),
'category' => $category,
] );
}
This does fine to the point of displaying the confirmation form but when I submit that form I just end up displaying the original TextToObjects form?
To answer albert's question, the TextToObjectsFormType just has three fields, a way to set the date & time for the group of generated objects, a way to select the origin of the objects and a textarea for the textual description. I do not set a data_class so I get an associative array back with the submitted information.
class TextToObjectsFormType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder
->add( 'textSourceDateTime', DateTimeType::class, [
'widget' => 'single_text',
'invalid_message' => 'Not a valid date and time',
'attr' => [ 'placeholder' => 'mm/dd/yyyy hh:mm',
'class' => 'js-datetimepicker', ],
])
->add( 'objectsOrigin', EntityType::class, [
'class' => ObjectSourcesClass::class,
])
->add( 'objectText', TextareaType::class, [
'label' => 'Copy and paste object description text here',
]);
}
}
How can I get the confirmed, potentially revised, objects back to put them into the database?
Thanks.
Using your current architecture
$nextForm = $this->createForm( StuffConfirmationFormType::class, $allTheStuff );
does not bear enough information. It should contain the action parameter to tell the submission to where route the post request.
In your StuffConfirmationFormType add an 'objectText' field hidden.
Create a confirmation_stuff route
Create an action for this route
In this action if the form is valid => save your stuff ELSE re render TextToObjectsFormType with an action linked to text_to_objects
Be aware that using this technique would not prevent a user to enter non functional data by manually editing the hidden field.
Hope this helps

Symfony form EntityType::class - How use it to edit data in form (3.2.13)

A dropdown selection menu in a Form is available by using a EntityType form type. It is usefull if you add data. But when you try to edit data with the same FormType class, your $request data are overwritten by your EntityType.
How use the same FormType class when editing the data? (eg. editAction in controller) How pass Request $request data to FormType fields as "defaults, or selected" for element EntityType::class in FormBuilder? Is there something in $builder->add() method I can use like if(['choice_value'] !=== null ) xx : yy?
How to get something like html select selected, from Request object and pass it to xxxFormType class and bind to right EntityType::class element there.
<select>
<option value="volvo">Volvo</option>
<option value="vw">VW</option>
<option value="audi" selected>Audi</option>
</select>
I've looked at EntityType Field,
How to Dynamically Modify Forms Using Form Events and lots of StackOverflow posts, but can't find proper solution.
Controller:
public function editProdPackageAction(Request $request, ProdPackage $prodPackage)
{
$form = $this->createForm(ProdPackageFormType::class, $prodPackage);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$prodPackage = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($prodPackage);
$em->flush();
$this->addFlash('success', 'MSG#011E');
return $this->redirectToRoute('admin_package_list');
}
return $this->render(':admin/forms/ProdPackage:edit.html.twig',
[
'ppg' => $form->createView(),
]
);
}
Formtype:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'idCatBig',
EntityType::class,
[
'label' => '* category',
'class' => 'AppBundle\Entity\ProdCategoryBig',
'choice_value' => 'id',
'choice_label' => 'shortName',
'multiple' => false,
'expanded' => false,
]
)
->add(
'dateStart',
DateType::class,
[
'label' => '* some date',
'data' => new \DateTime('now'),
'choice_translation_domain' => true,
]
)
->add(
'dateEnd',
DateType::class,
[
'label' => '* till',
'data' => new \DateTime('now'),
]
)
->add(
'packageName',
TextType::class,
[
'label' => '* package',
'attr' => ['placeholder' => 'default Name'],
'data' => 'your default value',
]
)
This is what I do in my form, I set a "pre set data listener" to check whether or not this is an edit or a create
function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add(
'dateStart',
DateType::class,
[
'label' => '* some date'
//I don't set a data field here because it is an edit
'choice_translation_domain' => true,
]
)
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (!$data || null === $data->getId()) {
//if the entity does not have an ID it means that this is a new entity not an edit. because all edits have been in the database and have an id
$builder->add(
'dateStart',
DateType::class,
['label' => '* some date',
'data' => new \DateTime('now'), //this is a create so I override my previous setting and set a default data
'choice_translation_domain' => true,]
)
}
});
}
I mainly use this trick to change form fields from required to non required between edits and creates for password fields for example,and sometimes if there is something exceedingly complicated. To change data, honestly, setting default data in constructor is cleaner as Stephan suggests
Problem
You are overriding an object by using the data option.
https://symfony.com/doc/3.2/reference/forms/types/entity.html#data:
( ! ) The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose its persisted value when the form is submitted.
Solution 1: use the controller
So the solution is pretty simple: don't use data. Instead, set default values in your addProdPackageAction action (or whatever it is called):
public function editProdPackageAction(Request $request)
{
$prodPackage = new ProdPackage();
$prodPackage->setDateEnd(new Datetime('now'));
//example: use Request or other controller methods to modify your entity
if ($this->getUser()->hasRole('ROLE_ADMIN')) {
$prodPackage->setCreatedByAdmin(true);
}
//your form
}
Solution 2: use your entity constructor
Alternatively, you can use your entity constructor method:
class ProdPackage
{
//your attributes
private $dateEnd;
public function __construct()
{
$this->dateEnd = new Datetime('now');
}
}
Found another interesting solution
in formTypeclass at $builder
->add(
'trBegin',
DateTimeType::class,
[
'label' => 'Tourney Begins',
'required' => true,
'date_widget' => 'single_text',
'time_widget' => 'single_text',
'date_format' => 'dd.MM.yyyy',
]
)
and continuing building form add:
$builder->get('trBegin')->addModelTransformer(
new CallbackTransformer(
function ($value) {
if (!$value) {
return new \DateTime('now + 60 minutes');
}
return $value;
},
function ($value) {
return $value;
}
)
);
it sets default date at moment when form is created. This method is very usefull also for EntityType object, where you can pass id of field in form and get selected your current real choice from database (not all list from the begining) very useful when using EntityField also for editing forms
$builder->get('productCategory')
->addModelTransformer(new CallbackTransformer(
function ($id) {
if (!$id) {
return;
}
return $this->em->getRepository('AppBundle:ProductCategory')->find($id);
},
function($category) {
return $category->getId();
}
));

How to set as a default value in a Symfony 2 form field the authentified username from the FOSUserBundle

i find this snippet useful indeed to put a default value in my form while creating it
$builder
->add('myfield', 'text', array(
'label' => 'Field',
'data' => 'Default value'))
;
what if i want to replace 'default value' with an authentified person from the FOSUser bundle? ( that return true to is_granted("IS_AUTHENTICATED_REMEMBERED"))
i can retrieve that name on a twig file with
{{ app.user.username }}
i have also done it in a controller method with
$username=$this->container->get('security.context')->getToken()->getUser()->getUsername()
but i can't manage to make this working in my form!
i am not sure i understand that container thing well ...neither how to transfer variables betweenn classes and controller...
something around this maybe??
->add('myfield', 'text', array(
'label' => 'Field',
'data' => FOS\UserBundle\Model::$this->getUsername()))
You can passe variable from your controller to your form :
in your controller :
$username=$this->container->get('security.context')->getToken()->getUser()->getUsername()
$form = $this->createForm(new MyFormType($username), $entity);
in your form :
protected $username;
public function __construct (array $username = null)
{
$this->username = $username ;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('myfield', 'text', array(
'label' => 'Field',
'data' => $this->username))
}
Another way to set default values into a form is to set them on the underlying data object for the form, as in this example from the Symfony documentation on building a form:
public function newAction()
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
In this example, the form's underlying data object is a Task and the values set on the task are the default values to be displayed in the form. The task object is not persistent. This approach works just as well with a form class and assuming the underlying object for your form is a User would look something like this:
$username = $this->container->get('security.context')->getToken()->getUser()->getUsername();
$user = new User();
$user->setUsername($username);
// could set any other default values on user here
$form = $this->createForm(new MyFormClass(), $user);
The disadvantage of this approach is if the form is used in many places requiring the same defaults the code would be repeated. This is not a situation I've come across so far - my User forms are re-used for create/edit but edit doesn't require the defaults.