Redirect back with Symfony and twig - php

In my Symfony project I have two views. Details view of specific entity and a button to lead me to a new view with with some other data by date param.
The problem with the code I have is of generating 'Return to previous' page button from second method back to first..
Code:
/**
* #Route("/details/{id}", name="first_method")
*/
public function firstMethod(Detail $id)
{
$workspace = $this->entityManager
->getRepository(Detail::class)
->find($id);
$build['detail'] = $detail;
$form = $this->createFormBuilder()
->add('date', DateTimeType::class, [
'data' => new \DateTime(),
'widget' => 'single_text'
])
->add(
'save',
SubmitType::class,
[
'attr' => ['class' => 'btn-submit']
]
)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$date = $data['date']
return $this->redirectToRoute('second_method', [
'date' => $date
]);
}
return $this->render('first-method.html.twig', [
'detail' => $detail,
]);
}
And there is that 'second' method:
/**
* #Route("/second-method/{date}", name="second_method")
*/
public function secondMethod($date)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
]);
}
I have a button on the second_method twig view which needs to return me back to method_one page.
How can accomplish that as the parameter $id is probably needed in second method but can not find a way to provide it. Maybe there is some other way? Can someone help?
I think in this first way it shoud me like:
{{ path('first_method', {'detailId':detail.id}) }}

As hinted by #msg, you should provide a second parameter to your second page. You will then need the #Entity annotation for Detail conversion and the #ParamConverter annotation for the date conversion :
/**
* #Route("/second_method/{id_detail}/{date}", name="second_method")
* #Entity("detail", expr="repository.find(id_detail)")
* #ParamConverter("date", options={"format": "!Y-m-d"})
*/
public function (Detail $detail, \DateTime $date)
// ...
The parameter conversion will perform a first sanity check and will return a 404 if the entity doesn't exist.
You will also be able to check in your controller that $detail is coherent with the date in the URL. Remember : never trust user input (this includes URLs).
And you can provide this new parameter when your redirect to your second controller :
return $this->redirectToRoute("second_method", [
"date" => $date,
"id_detail" => $workspace->getId() // You can also pass the whole object and fetch the ID in the template
]);
Then in your template :
{{ path('first_method', {'id': id_detail}) }}
Details
For ParamConverters you can find more details here : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
I personnaly prefer the #Entity annotation for conversion of an Entity since you are sure of how the conversion is done : I find it better for when your project grows and your entities have more and more parameters. Eventhough the #ParamConverter would work in your case.

Alternative without changing the second_method route parameters, relying only on the referer.
Add the helper method to the controller:
/**
* Returns the referer url to redirect back if the request came from first_method
* Otherwise returns FALSE.
*/
private function getBackUrl(Request $request, UrlMatcherInterface $matcher)
{
$referer = $request->headers->get('referer');
if (null === $referer) {
return false;
}
$url = parse_url($referer);
if ($url['host'] != $request->getHost()) {
return false;
}
// Remove the baseurl if we're in a subdir or there's no Rewrite
$path = str_replace($request->getBaseUrl(), '', $url['path']);
try {
$route = $matcher->match($path);
} catch (ResourceNotFoundException $e) {
// Doesn't match any known local route
return false;
}
// A route has been determined, check if it matches the requirement
if ('first_method' == $route['_route']) {
return $referer;
}
return false;
}
Change the method to (The Route annotation doesn't change, just the method signature):
public function secondMethod($date, Request $request, UrlMatcherInterface $matcher)
{
return $this->render('second-method.html.twig', [
'someData' => $someData,
'date' => $date,
'back_url' => $this->getBackUrl($request, $matcher),
]);
}
In your template:
{# Show the button only if the request came from first_method (otherwise will be false) #}
{% if back_url %}
<a class="btn" href="{{ back_url }}">Return</a>
{% endif %}

Related

Symfony: ManyToOne Form Issue

I have two entities called, Ticket & TicketUpdate.
Each Ticket can have many TicketUpdates, but every TicketUpdate can only have 1 Ticket.
Next I have a form which shows the current Ticket, but also allows me to add 1 TicketUpdate & change attributes of Ticket.
This is my controller:
//TicketController.php
...
/**
* #Route("/ticket/{id}", name="app_ticket")
*/
public function ticket(Request $request, Ticket $ticket)
{
$ticketUpdate = new TicketUpdate();
$ticketUpdate->setTicket($ticket);
$form = $this->createForm(TicketUpdateType::class, $ticketUpdate); //custom form type
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($ticketUpdate);
$em->persist($ticket);
$em->flush();
}
return $this->render('ticket/view.html.twig', ['ticket' => $ticket, 'form' => $form->createView()]);
}
...
TicketUpdateType:
//TicketUpdateType.php
...
class TicketUpdateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', TextareaType::class, ['label' => 'update', 'required' => false, 'attr' => ['class' => 'textarea-sm'])
->add('ticket', TicketType::class, ['label' => false, 'by_reference' => false]) //custom Type for Tickets
->add('submit', SubmitType::class, ['label' => 'save']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => TicketUpdate::class
]);
}
}
...
However, this solution does not work for me. Symfony always wants to create a new Ticket entry, instead of changing the old one.
Is there any way to fix this?
May you know, a magic with symfony forms, with you can get an Entity (Ticket), like in your example, I dont know... but this will working:
/**
* #Route("/ticket/{ticketId}", name="app_ticket", requirements={"ticketId"="\d+"})
*/
public function ticket(Request $request, int $ticketId = 0)
{
$em = $this->getDoctrine()->getManager();
$ticket = $em->getRepository(Ticket::class)
->findOneBy([
'id' => $ticketId
]);
if ($ticket instanceof Ticket === false)
{
die('Ticket dont exist with the requested ID.'); #Just return here some error message
}
$ticketUpdate = new TicketUpdate();
//Because your setTicket() setter inside your TicketUpdate Entity
//sure have nullable typehinted argument (?Ticket $ticket)
//if this is a valid doctrine relationship
//(but if i'm wrong, please show the touched parts of your TicketUpdate entity)
$ticketUpdate->setTicket($ticket); #<-- here is an "old" Ticket $ticket
$form = $this->createForm(TicketUpdateType::class, $ticketUpdate); //custom form type
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$em->persist($ticketUpdate);
//I'm working a lot with doctrine relationships
//In many time necessary using a setter in the both Entity
$ticket->addTicketUpdate($ticketUpdate); #You know this setter! Change, if my tip wrong
$em->persist($ticket);
$em->flush();
}
return $this->render('ticket/view.html.twig', [
//Now, this is an instance of the Ticket
//not an int ID!
//so if you need the ID, you can get in twig, like:
//{{ ticket.id }}
'ticket' => $ticket, #Or: $ticket->getId()
'form' => $form->createView()
]);
}
The requirements inside the #route means, the method will running only on pages, where the {ticketId} is numeric.
Update: I changed by_reference to true and removed every logic in my TicketType, which seemed to cause the issue.
Atleast for now I got it running. Here is my controller:
//TicketController.php
...
/**
* #Route("/ticket/{id}", name="app_ticket")
*/
public function ticket(Request $request, Ticket $ticket)
{
$ticketUpdate = new TicketUpdate();
$ticketUpdate->setTicket($ticket);
$form = $this->createForm(TicketUpdateType::class, $ticketUpdate); //custom form type
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($ticketUpdate);
//$em->persist($ticket); -> removed, will be automatically updated by symfony
$em->flush();
}
return $this->render('ticket/view.html.twig', ['ticket' => $ticket, 'form' => $form->createView()]);
}
...

Symfony 4 TextType required = false getting Expected argument of type "string", "NULL" given

I have a TextType field which is set as below
Type:
->add('zipCode',TextType::class, array(
'attr' => array('maxlength' => 10),
'required' => false,
'empty_data' => null
)
)
When the data loads from the database the field is null. I just attempt to submit the form and I get
Expected argument of type "string", "NULL" given.
at vendor/symfony/property-access/PropertyAccessor.php:174 at
Symfony\Component\PropertyAccess\PropertyAccessor::throwInvalidArgumentException('Argument
1 passed to App\Entity\Patients::setZipCode() must be of the type
string, null given,
I'm unsure how to fix this? I WANT the value to be null...? Perhaps it's cause I'm not loading the initial data right on the form?
Related code below, let me know if you need something further:
Twig:
{{ form_widget(view_form.zipCode, { 'attr': {'class': 'mb-2 mb-sm-0 w-100'} }) }}
Controller:
public function view(Request $request)
{
$patientId = $request->query->get('id');
if (!empty($patientId)) {
$search = $this->getDoctrine()
->getRepository(Patients::class)
->find($patientId);
if (is_null($search))
$this->addFlash('danger', 'Invalid Patient');
$form = $this->createForm(PatientViewType::class,$search);
}
else
$form = $this->createForm(PatientViewType::class);
dump($request);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$view = $form->getData()();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($view);
$entityManager->flush();
$this->addFlash('success', 'Patient record updated successfully!');
}
return $this->render('patient/view.html.twig', [
'view_form' => $form->createView()
]);
}
I WANT the value to be null...? Perhaps it's cause I'm not loading the initial data right on the form?
According to the exception message, the method Patients::setZipCode does not accept null values. It would seem that the value is correctly passed as null to your entity, but the Patients class does not accept the value.
I assume the setZipCode signature looks somewhat like this:
public function setZipCode(string $zipCode): void
You can make it accept null values by adding a question mark:
public function setZipCode(?string $zipCode): void

How to redirectToRoute() with arguments which are not part of the route?

I have a Controller with two ...Action()-methods. When I call the route /newTask with the route name newTask inside the browser I have a form to set a Task-object. After submitting via Submit-Button I want to redirect to the route /success with the route name task_success:
class FormController extends Controller {
/**
* #Route("/newTask", name="newTask")
*/
public function newAction(Request $request)
{
// 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', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, array('label' => 'Create Task'))
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Here I want to redirect...
return $this->redirectToRoute('task_success', array('task' => $task));
}
return $this->render('default/new.html.twig', array(
'form' => $form->createView(),
));
}
/**
* #Route("/success", name="task_success")
*/
public function successAction($task){
return $this->render('default/success.html.twig',array('task' => $task));
}
}
As you can see the method successAction has a parameter $task which I need to to show the values with the success.html.twig, but I donĀ“t need task as a part of the route (e.g. \success\{task}).
So how can I redirect with the argument $task without using it inside the route?
If you don't want display the task number in URL, you can pass its ID to the session flashBag before redirect and store it temporally:
$this->addFlash('success_task_id', $task->getId());
return $this->redirectToRoute('task_success');
Then, get this ID from session in you successAction method:
/**
* #Route("/success", name="task_success")
*/
public function successAction(){
$taskId = $this->get('session')->getFlashBag()->get('success_task_id');
$task = $this->getDoctrine()->getRepository('AppBundle:Task')->find($taskId);
return $this->render('default/success.html.twig', array('task' => $task));
}
However, if the visibility of the task number is not important, simply use it in newAction:
return $this->redirectToRoute('task_success', array('task' => $task->getId()));
This generate one URL like /success?task=1 and get the number from request query parameter bag in successAction:
public function successAction(Request $request){
$taskId = $request->query->get('task');
// ...
}
Another solution by using serialize and unserialize functions:
$this->addFlash('success_task', serialize($task));
return $this->redirectToRoute('task_success');
Then, get the serialized data from session in you successAction method:
/**
* #Route("/success", name="task_success")
*/
public function successAction(){
$task = unserialize($this->get('session')->getFlashBag()->get('success_task'));
return $this->render('default/success.html.twig', array('task' => $task));
}

Can the method redirectToRoute() have arguments like render()?

I need to access an entity in twig from symfony2.
Inside the controler, I can do something as:
return $this->render('frontendBundle::carrodecompras.html.twig', array(
'entity' => $entity
));
And then in twig I can access the entity properties with entity.name and such.
I need to accomplish the same thing but with the function redirectToRoute()
return $this->redirectToRoute('frontend_carrodecompras', array(
'entity' => $entity,
));
But it doesn't seem to work.
I'm getting the following error:
Variable "entity" does not exist in frontendBundle::carrodecompras.html.twig at line 32
EDIT: I'm using Symfony 2.7
The variable $entity exists (it's actually called $cortina in the app I was using $entity for simplification), just before the redirectToRoute function I did this to test it
echo "<pre>";
var_dump($cortina);
echo "</pre>";
return $this->redirectToRoute('frontend_carrodecompras', array(
'cortina' => $cortina,
));
And the result is this:
object(dexter\backendBundle\Entity\cortina)#373 (16) {
["id":"dexter\backendBundle\Entity\cortina":private]=>
int(3)
...
This is the Twig code:
<tr>
{% set imagentela = "img/telas/" ~ cortina.codInterno ~ ".jpg" %}
<td><img src="{{ asset(imagentela | lower ) }}" alt="" width="25" height="25">
</td>
<td>{{ cortina.nombre }}</td>
<td>{{ "$" ~ cortina.precio|number_format('0',',','.') }}</td>
</tr>
When you call redirectToRoute($route, array $parameters) from a controller, $parameters is used to generate the url tokens, not variables to render in view, this is done by the controller assigned to the route you are redirecting to.
example :
class FirstController extends Controller
{
/**
* #Route('/some_path')
*/
public function someAction()
{
// ... some logic
$entity = 'some_value';
return $this->redirectToRoute('some_other_route', array('entity' => $entity)); // cast $entity to string
}
}
class SecondController extends Controller
{
/**
* #Route('/some_other_path/{entity}', name="some_other_route")
*/
public function otherAction($entity)
{
// some other logic
// in this case $entity equals 'some_value'
$real_entity = $this->get('some_service')->get($entity);
return $this->render('view', array('twig_entity' => $real_entity));
}
}
$this->redirectToRoute('something', array('id' => 1) is a convenience wrapper to $this->redirect($this->generateUrl('something', array('id' => 1))). It builds a URL with your params and is expecting the value of the params to be a string or a number.
http://symfony.com/blog/new-in-symfony-2-6-new-shortcut-methods-for-controllers
You need to either pass the id of the entity to then fetch the data in the new action or break it down into individual pieces of data before it hits the redirectToRoute() call.
class MyController extends Controller {
public function myAction(){
$cortina = new Cortina();
$cortina->something = "Some text";
$em = $this->getDoctrine()->getManager();
$em->persist($cortina);
$em->flush();
return $this->redirectToRoute('frontend_carrodecompras', array(
'id' => $cortina->getId()
);
}
}

Laravel PHP 4: 'Put' method generates 'MethodNotAllowedHttpException'

I am trying to modify a form used for editing and updating data. However when I try submitting the 'edit' form, I keep getting a 'MethodNotAllowedHttpException'. I'm not sure if this is because I am using the 'PUT' method incorrectly or my 'EditAlbumsController.php' file is not defined correctly.
edit-album.blade.php:
{{ Form::model($album, array('method' => 'PUT', 'route' => array('edit_album', $album->album_id))) }}
/* Form code here */
{{ Form::close() }}
routes.php:
Route::get('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));
EditAlbumsController.php:
class EditAlbumsController extends AlbumsController {
public function __construct()
{
parent::__construct();
}
public function update($id)
{
$input = \Input::except('_method');
$validation = new Validators\Album($input);
if ($validation->passes())
{
$album = Album::find($id);
$album->album_name = $input['album_name'];
/* Additional database fields go here */
$album->touch();
return $album->save();
return \Redirect::route('gallery.album.show', array('id' => $id));
}
else
{
return \Redirect::route('gallery.album.edit', array('id' => $id))
->withInput()
->withErrors($validation->errors)
->with('message', \Lang::get('gallery::gallery.errors'));
}
}
Any help is greatly appreciated!
You need to define the PUT route (you are incorrectly using GET)
Route::put('gallery/album/{id}/edit', array('as'=>'edit_album', 'uses'=>'EditAlbumsController#update'));

Categories