Search form with multiple criteria , Symfony2 - php

I'm trying to create a search form with multiple criteria but it doesn't work, and I think the controller does not receive the form because when I click Button Submit the page reloads only.
This is the Controller
/**
* #ParamConverter("agence", options={"mapping": {"agence_slug":"slug"}})
*/
public function indexAction(Agence $agence, Request $request)
{
$form = $this->createForm(new SearchTravelType());
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$criteria = $form->getData();
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->getListBy($criteria, $agence);
}
}
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}
and this is the queryBuilder
class TravelRepository extends EntityRepository
{
public function getListBy($criteria, $agence)
{
$qb = $this->createQueryBuilder('t');
$qb = $this->whereAgence($qb, $agence);
$qb ->leftJoin('t.airport', 'a')
->addSelect('a');
foreach ($criteria as $field => $value) {
if (!$this->getClassMetadata()->hasField($field)) {
// Make sure we only use existing fields (avoid any injection)
continue;
}
$qb ->andWhere($qb->expr()->eq('t.'.$field, ':t_'.$field))
->setParameter('t_'.$field, $value);
}
return $qb->getQuery()->getResult();
}
public function whereAgence (\Doctrine\ORM\QueryBuilder $qb, $agence)
{
$qb->where('a.agence = :agence')
->setParameter('agence', $agence);
return $qb;
}
}
and this is the page twig and the form
<form class="form-horizontal" role="form" method="post" >
<td>{{ form_widget(form.id) }}</td>
<td class="center">-</td>
<td>{{ form_widget(form.title) }}</td>
<td>{{ form_widget(form.country) }}</td>
<td>{{ form_widget(form.destination) }}</td>
<td>{{ form_widget(form.airport) }}</td>
<td>{{ form_widget(form.departureDate) }}</td>
<td>{{ form_widget(form.returnDate) }}</td>
<td>{{ form_widget(form.price) }}</td>
<td>{{ form_widget(form.enabled) }}</td>
<td><span class="input-group-btn">
<button type="submit" class="btn btn-purple btn-sm">Search
<i class="icon-search icon-on-right bigger-110"></i></button>
</span>
</form>
I think the first problem is that form is not recovered in the controller, I tried with
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->find(1);
instead
getListBy($criteria, $agence)
to see what gives. the controller ignores the form and it goes directly to the following queries
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);

You can remove $request = $this->getRequest(); from action as you are passing REquest object to this action;
You need to add action to your form,
like this:
$this->createForm(new SearchTravelType(), null, [
'action' => $this->generateUrl('ROUT')
]);
or like this:
<form class="form-horizontal" role="form" method="post" action="{{ path('ROUT') }}" >
EDIT:
We create a filter form with GET method (you should use POST method for inserting to DB, not for fetching results), disable CSRF protection. We create a controller action:
public function indexAction(Agence $agence, Request $request)
{
$form = $this->createForm(new SearchTravelType(), null, [
'action' => $this->generateUrl('ROUTE'),
'method' => 'GET'
]);
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$criteria = explode('&', $request->query->get($form->getName())); // I didn't test this line, you should check it..
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->getListBy($criteria, $agence);
// Terminate the request
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}

You can try this bundle:
https://github.com/petkopara/PetkoparaMultiSearchBundle
Provides form or service for multi criteria search in Doctrine Entity.
In your case the form solution will solve your issue:
First in your form type just only add the MultiSearchType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('search', MultiSearchType::class, array(
'class' => 'ProjectTravelBundle:Travel'));
}
Then in your Controller add the following code
public function indexAction(Request $request)
{
$search = $request->get('search');
$em = $this->getDoctrine()->getManager();
$queryBuilder = $em->getRepository('ProjectTravel:Travel')->createQueryBuilder('e');
$filterForm = $this->createForm('ProjectTravelBundle\Form\TravelType');
// Bind values from the request
$filterForm->handleRequest($request);
if ($filterForm->isValid()) {
// Build the query from the given form object
$queryBuilder = $this->get('petkopara_multi_search.builder')->searchForm($queryBuilder, $filterForm->get('search'));
}
..
}
At the last render your Travel form in your template
{{form_render(filter_form)}}

Related

Symfony2 - Requirement to provide a value value for an argument

The following exception is being returned when trying to use the method editAction() of a custom ProfileController herited from FOSuserbundle:
Controller "UserBundle\Controller\ProfileController::editAction()" requires that you provide a value for the "$id" argument (because there is no default value or because there is a non optional argument after this one).
UserBundle\Controller\ProfileController::editAction():
/**
* Edit the user
*/
public function editAction($id)
{
$user = $this
->getDoctrine()
->getManager()
->getRepository('UserBundle:User')
->find($id);
if (!is_object($user) || !($user instanceof UserInterface)) {
throw new AccessDeniedException('This user does not have access to this section.');
}
/** #var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$form = $this->createForm(new RegistrationEditFormType(), $user);
$form->handleRequest($request);
if ($form->isValid()) {
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
// Updating the user
$userManager->updateUser($user);
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_SUCCESS, $event);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('lld_profile_show');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
$request->getSession()->getFlashBag()->add('info-edit', 'Your account has been updated!');
return $this->render('UserBundle:Profile:edit.html.twig', array(
'form' => $form->createView(),
'user' => $user
));
}
code snippet from admin.html.twig:
<tbody>
{% for user in users %}
<tr class="">
<td>{{ user.id }}</td>
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.amount }}</td>
<td>
{% if user.isAccountNonLocked %}
<span class="label label-success">Enabled</span>
{% else %}
<span class="label label-default">Disabled</span>
{% endif %}
</td>
<td>{{ user.date|date('m-d-Y H:i:s') }}</td>
<td>{{ user.updatedAt|date('m-d-Y H:i:s') }}</td>
<td></td>
<td>Delete Disable Enable Edit</td>
</tr>
{% endfor %}
</tbody>
The specific line being used:
<td>Delete Disable Enable Edit </td>
adminAction():
public function adminAction()
{
$user = $this->getUser();
if (!$this->container->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY') || !$this->container->get('security.context')->isGranted('ROLE_ADMIN') || !$this->container->get('security.context')->isGranted('ROLE_MANUFACTURER')) {
return $this->redirect($this->generateUrl('fos_user_security_login'));
}
$userManager = $this->container->get('fos_user.user_manager');
$users = $userManager->findUsers();
return $this->render('UserBundle:Admin:admin.html.twig', array(
'users' => $users
));
}
public function deleteAction(Request $request, $id)
{
if (!$this->container->get('security.context')->isGranted('ROLE_ADMIN')) {
return $this->redirect($this->generateUrl('fos_user_security_login'));
}
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->findUserBy(array('id' => $id));
if (null === $user) {
throw new NotFoundHttpException("User with id " . $id . " doesn't exist.");
}
$userManager->deleteUser($user);
$request->getSession()->getFlashBag()->add('info-delete', 'The user ' . strtoupper($user->getCompanyName()) . ' has been deleted successfully!');
return $this->redirect($this->generateUrl('admin'));
}
So, I got stuck and I'm not seeing where this issue is coming from since the argument is logically being retrieved from the line where the click is being made. Obviously the right action (the custom editAction()) is being called.
Is there something I'm missing? Any suggestions please? I tried the check out similar posts but none could help me.
The reason is that your are using native FOS routing, which does not have an id parameter at all.
<route id="fos_user_profile_show" path="/" methods="GET">
<default key="_controller">fos_user.profile.controller:showAction</default>
</route>
It's a Profile Edit page, which allows you to edit your own profile, so the id must be taken from a session.
If you are trying to provide some kind of CRUD functionality, you have to either switch to a ready-to-use datagrid / admin systems (e.g. SonataUserBundle) or write your custom routing and controller.

Laravel update method passing through model name instead of ID

I am having an issue with my resource route when calling the update method.
I get this error:
Creating default object from empty value
The controller:
public function update($id)
{
$input = Input::all();
$validation = Validator::make($input, Vehicle::$rules, Vehicle::$messages);
if ($validation->passes())
{
$this->vehicle->update($id, $input);
return Redirect::route('admin.vehicles.index')->with('success', 'Car Updated');
}
return Redirect::back()
->withInput()
->withErrors($validation);
}
repository:
public function update($id, $input)
{
$vehicle = Vehicle::find($id);
$vehicle->VRM = $input['VRM'];
$vehicle->make = $input['make'];
$vehicle->model = $input['model'];
$vehicle->description = $input['description'];
$vehicle->save();
}
Route:
Route::resource('/admin/vehicles', 'VehiclesController');
If I print the ID then it shows {vehicle}.
My form is this:
{{ Form::open(['route' => 'admin.vehicles.update', 'class' => 'form-horizontal edit-vehicle-form', 'method' => 'PATCH']) }}
// input fields etc
{{ Form::close() }}
I think there is something wrong with the form possibly? Since when the error is thrown the URL is:
http://localhost/admin/vehicles/%7Bvehicles%7D
Never had any issues before with using resource routes with CRUD applications and cant see where this is going wrong?
You need the id in update route...
{{ Form::open(['route' => array('admin.vehicles.update', $vehicle->id), 'class' => 'form-horizontal edit-vehicle-form', 'method' => 'PATCH']) }}

Retrieve data from a form with GET method using symfony2

I can't retrieve data from my form, I tried differents ways but no result. My repository :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('min_price', 'text', array('mapped' => false, 'label' => 'De la :', 'attr'=>
array(
'placeholder'=>'Pretul minim',
'class'=>'form-control')))
->add('max_price', 'text', array('mapped' => false, 'label' => 'Pina la :' , 'attr'=>
array(
'placeholder'=>'Pretul maxim',
'class'=>'form-control')))
)
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
// avoid to pass the csrf token in the url (but it's not protected anymore)
'csrf_protection' => false,
));
}
public function getName()
{
return '';
}
My controller :
public function showCategoryAction($id, $page, Request $request){
$em = $this->getDoctrine()->getManager();
$repositoryProduct = $em->getRepository('ShopDesktopBundle:Product');
$category = $em->getRepository('ShopDesktopBundle:Category')->findOneById($id);
if (!$category) {
throw $this->createNotFoundException('Category not found.');
}
$aFilter = array();
$entity = new Product();
$form = $this->createForm(new ProductType(), $entity,array(
'action' => $this->generateUrl('show_product_category',array("id" => $id, "name" => $category->getCategoryLink(), "page" => $page )), //Your url to generate
'method' => 'GET'
));
$form->handleRequest($request);
$aFilter['iMinPrice'] = $form["min_price"]->getData();
$aFilter['iMaxPrice'] = $form["max_price"]->getData();
print_r($aFilter);
//Searchs products
$aProducts = $repositoryProduct->getProductsOrderByDateDesc($id,null,$aFilter);
if (!$aProducts) {
throw $this->createNotFoundException('Products not found.');
}
//Create pagination
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$aProducts,
$page,
3
);
//Send data to view
return $this->render('ShopDesktopBundle:Category:category.html.twig',array(
'category' => $category,
'pagination' => $pagination,
'form' => $form->createView()
));
}
My view :
<form action="{{ path('show_product_category',{ 'id':category.getId(), 'name':category.getCategoryLink() }) }}" method="get" {{ form_enctype(form) }}>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="" href="#toggleOne">
<em class="icon-minus icon-fixed-width"></em>Pret
</a>
</div>
<div id="toggleOne" class="accordion-body collapse in">
<div class="accordion-inner">
{{ form_widget(form) }}
</div>
</div>
</div>
<input type="submit" class="btn btn-primary marg-left-20" value="Cautare"/>
</form>
The view :
show_product_category:
path: /{id}/{name}/{page}
defaults: { _controller: ShopDesktopBundle:Category:showCategory, page: 1}
requirements:
id: \d+
page: \d+
_method: GET|POST
So the problem is that I can't retrieve data from thi form. For all situations $aFilter is empty. If for example I put in the view in form POST method the filter is with data from form. My url look like this :
?min_price=10&max_price=50.
Help me please. Thx in advance.Exist a solution??
I would not set the form method as GET, just do not specify a method and it will default to POST, which is the usual way to submit a form.
Then handle the form submission in your Controller when the method is POST - like this:
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid() {
$aFilter['iMinPrice'] = $form->get('min_price')->getData();
$aFilter['iMaxPrice'] = $form->get('max_price')->getData();
}
}
More information on how to handle form submissions in Symfony2 here.
If you really need the form method to be GET, you should be able to get the query string parameters from the request:
$aFilter['iMinPrice'] = $request->query->get('min_price');
$aFilter['iMaxPrice'] = $request->query->get('max_price');

Save Form from another Action

I have Two Action, GetAllPost and newComment
I have a page with many Post and each Post have commentForm
PostController
public function getPostAction () {
return array(
);
}
Twig
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
CommentController
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
}
return array(
'post' => $post,
'form' => $form->createView(),
);
}
TwifFormController
{{ form(form, {'action': path('new_comment',{'id': post.id})})}}
When I insert a new comment I have redirect to new_comment even if my value isn't valid.
How can I redirect to GeTAllPost and show the correct Error or the new Comment?
I tried to use
return $this->redirect($this->generateUrl('get_post',array('error',$form->getErrors())));
and 'error_bubbling' => true,, but each time request a get_post ( GetAllPost ) I do a new render of my Form and I don't see the errors
For Example i'd like to use newCommentAction in several scenario.
For example i GetAllPost for each post, but even in GetSpecificPost, where I Have A specific post, where I Can insert a new comment, but the save ( and the Action ) is the same.
Do I have create a Service ?
UPDATE
After Bonswouar's answer. This is my Code
PostController
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$comment = new Comment();
return array(
'commentForms' => $this->createCreateForm($comment),
);
}
private function createCreateForm (Comment $entity) {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), $entity);
$commentForms[$post->getId()] = $form->createView();
}
return $commentForms;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
//Sometimes I Have only One Form
$commentForms = $this->createCreateForm($comment);
$form = $this->createForm(new CommentType($post->getId()), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
} else {
$commentForms[$post->getId()] = $form->createView();
}
return array(
'commentForms' => $commentForms,
);
}
And I Don't have any Render.
But, I want to re-use newCommentAction also in Single Post, and i Want to create Only one Form. I don't want use $commentForms = $this->createCreateForm($comment);, because i Want just one form,and I have to change template even. How can I do ?
If I'm not mistaking, your problem is that you're posting on new_comment, which is a "sub action".
You actually don't need this Twig render.
You could just generate all the forms you need in the main Action, with something like this :
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), new Comment());
$form->handleRequest($request);
if ($form->isValid()) {
//...
// Edited : to "empty" the form if submitted & valid. Another option would be to redirect()
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
$commentForms[$post->getId()] = $form->createView();
}
return array(
'posts' => $posts,
'commentForms' => $commentForms,
);
Not forgetting to set a dynamic Name in your Form class :
class CommentType extends AbstractType
{
public function __construct($id) {
$this->postId = $id;
}
public function getName() {
return 'your_form_name'.$this->postId;
}
//...
}
And then just normally render your forms in your Twig loop. You should get the errors.
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ form(commentForms[post.id]) }}
<hr>
{%endfor%}
If I didn't miss anything that should do the job.
UPDATE :
After seeing your update, this might be the controller you want (sorry if I didn't understand properly or if I did some mistakes):
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$commentForms[$post->getId()] = $this->createCommentForm($post);
}
return array(
'commentForms' => $commentForms
);
}
private function createCommentForm (Post $post, $request = null) {
$em = $this->getEm();
$form = $this->createForm(new CommentType($post->getId()), new Comment());
if ($request) {
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
}
return $form;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
return array(
'commentForm' => $this->createCommentForm($post, $request);
);
}
What about using a flash message to set your error messages? http://symfony.com/doc/current/components/http_foundation/sessions.html#flash-messages
EDIT: Modifying based on your comments. In your controller you could do this:
foreach ($form->getErrors() as $error) {
$this->addFlash('error', $post->getId().'|'.$error->getMessage());
}
or
$this->addFlash('error', $post->getId().'|'.(string) $form->getErrors(true, false));
What this will do is allow you to tie the error to the particular post you want, as you are passing it a string like 355|This value is already used. If you need to know the field you could add another delimiter for $error->getPropertyPath() in your flash message, or you could overwrite the error name in the entity itself.
Then in your controller you could parse out the flash messages, and add them to an array that your twig template would check:
$errors = array();
foreach ($this->get('session')->getFlashBag()->get('error', array()) as $error)
{
list($postId, $message) = explode('|', $error);
$errors[$postId][] = $message;
}
return array('errors' => $errors, ...anything else you send to the template)
Now your twig template can check for the existence of errors on that particular form:
{% for post in app.user.posts %}
{% if errors[post.id] is defined %}
<ul class="errors">
{% for error_message in errors[post.id] %}
<li>{{ error_message }}</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}

"The CSRF token is invalid" error in symfony 2 even using form_rest(form) function

I've been trying to create a simple form in symfony but each time I try to submit I get the following error:
ERROR: The CSRF token is invalid. Please try to resubmit the form.
After surfing on the Internet and reducing the code to almost empty. I still get that error. Most of the people who I've seen asking for that solved the error using the following twig code
{{ form_rest(form) }}
The problem is that I'm using it, it's like when I bind the request it doesn't do it correctly. I don't know what else can I do.
This is my small twig template:
<div><h2>Insert new activity</h2></div>
<div>
<form id="new-activity" action="{{ path('create') }}" method="post" {{ form_enctype(form) }}>
{{ form_rest(form) }}
<p>
<button type="submit">Submit</button>
</p>
</form>
</div>
As you can see it is pretty small code. This is my form render code:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/new", name="sucr_new")
* #Template()
*/
public function newAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
return array(
'data' => $data,
'form' => $form->createView()
);
}
And this is the code that should handle the submition:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/create", name="create")
* #Method("post")
* #Template("EusocSucrBundle:Sucr:new.html.twig")
*/
public function createAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
if ($this->getRequest()->getMethod() == 'POST') {
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
return $this->redirect($this->generateUrl('sucr_show', array('id' => 1)));
}
var_dump($form->getErrorsAsString());
}
return array(
'data' => $data,
'form' => $form->createView()
);
}
Also note that I can see the hidden token in my browser.
Any ideas?

Categories