Symfony2 <a> link with 'post' or 'delete' or 'put' method - symfony

I'd like to generate a url with a 'PUT' or 'POST' method requirement, via a <a href> link.
Since I use a framework server-side, I use a fake form to do this - example with PUT method :
<form action="someActionUri" method="post">
<input type="hidden" name="_method" value="PUT" />
<input type="submit" value="GO PUT" />
</form>
But, I'd like to use a simple <a> link :(

An anchor <a href="#"> will always use a GET request. There is no way to modify that. But, it is possible to fake a PUT or DELETE request using Symfony.
Symfony Forms will fake the verb by adding a hidden field to forms.
<input type="hidden" name="_method" value="PUT" />
Then internally it checks if the verb is POST, checks for the _method parameter and changes the verb from POST to PUT. This only works for HTML forms using the POST method.
The same can be done for GET verbs, but it requires using an Event Listener. Here is an example:
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
class VerbListener
{
public function onKernelRequest ( GetResponseEvent $event )
{
$request = $event->getRequest();
if ( HttpKernel::MASTER_REQUEST === $event->getRequestType()
&& $request->getMethod() === 'GET' )
{
if ( $request->query->get('_method') === 'PUT' ) {
$request->setMethod( 'PUT' );
}
}
}
}
And the service must be registered with a higher priority than the router_listener service, which matches the route to a controller.
services:
acme.verb.listener:
class: Acme\Bundle\Listener\VerbListener
tags:
- { name: kernel.event_listener,
event: kernel.request,
method: onKernelRequest,
priority: 100 }
The link can now be generated with the _method parameter

In the routing file :
entity_edit:
pattern: /entity/{id}/edit
defaults: {_controller: MyBundle:Entity:put}
My controller (with fosRest):
/**
* #Rest\View()
*/
public function putAction(Request $request, $id)
{
...code ...
}
My TWIG:
<a href="{{ path('entity_edit', {'id': data.id} ) }}" >Edition</a>

Even if I like #twifty response, I think that is a bit overkill to listen to every single request just to adapt some of them.
It's much simpler to output a form instead of your link, so instead of link, you can simply use:
<form action="{{ path('my_route_with_put_method') }}" method="POST">
<button>link</button>
<input type="hidden" name="_method" value="PUT">
</form>
You just need to pay attention, because you're using a block element (form) instead of an inline element (a), but you can easily go around that (for example including other inline elements inside the form or using CSS)

Related

Can I use this Form in symfony 4?

I have added this form in twig, i need to know if this is okay and how can i recupere the input name="comments" in controller
<form action="{{ path('Update') }}" method="POST">
<input class="form-control" type="text" name="comments"
value=""></td>
<td>
<input type="submit" value="Save"/>
</form>
You can take a look at the Symfony Request Object section of the doc:
// retrieves $_GET and $_POST variables respectively
$request->query->get('id');
$request->request->get('category', 'default category');
So you can retrieve in the controller as:
$request->request->get('comments');
In your controller you can use the Request object to get all the parameters of your form, for example:
/**
* #Route("/Update")
*/
public function update(Request $request){
$comments = $request->request->get('comments');
...
}
But I recommend you to use the forms component.

How to dynamically set 'was-validated' class on form to show validation feedback messages with angular 5 after submit

I am using a template based form in angular. I also use bootstrap (v4) and I wish to show some validation messages when the form was submitted.
This is my form:
<form [ngClass]="{'was-validated': wasValidated}">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" class="form-control" [(ngModel)]="category.name" #name="ngModel" required maxlength="100"/>
<div *ngIf="name.invalid" class="invalid-feedback">
<div *ngIf="name.errors.required">
Name is required.
</div>
</div>
</div>
<button type="submit" class="btn btn-success" (click)="save()">Save</button>
</form>
My component looks as follows:
category: Category;
wasValidated: boolean = false;
ngOnInit() {
this.reset();
}
save() {
this.wasValidated = true;
this.categoriesService.createCategory(this.category).subscribe(
() => {
this.notificationService.add(notifications.category_saved, {name: this.category.name});
this.reset();
},
() => this.notificationService.add(notifications.save_category_failed)
);
}
reset() {
this.wasValidated = false;
this.category = {} as Category;
}
This works, but I have a feeling it's overly complex and more like a workaround rather than the right way. What is the best way to accomplish this?
Note: the class was-validated must be present on the form element in order to show the div with class invalid-feedback. I'm using this: https://getbootstrap.com/docs/4.0/components/forms/#validation
Note 2: I have currently no mechanism yet to prevent form submission on error. I'd like to know a good solution for that as well!
With the answer from #Chellappan V I was able to construct the solution I wanted.
I have applied to following changes:
First added #form="ngForm" to the form tag in the template. Secondly I changed the ngClass expression to reference the submitted state of the form, rather than referring to a boolean which was set to true manually when form was submitted. Last but not least I pass the form in the submit method on the save button.
<form novalidate #form="ngForm" [ngClass]="{'was-validated': form.submitted}">
<!-- form controls -->
<button type="submit" class="btn btn-success" (click)="submit(form)">Save</button>
</form>
In the component I injected the template variable in the component with #ViewChild.
#ViewChild("form")
private form: NgForm;
The submit method now takes a form parameter of type NgForm which is used to check if the form was valid before sending a request to the backend:
submit(form: NgForm) {
if (form.valid) {
this.categoriesService.createCategory(this.category).subscribe(
() => {
this.notificationService.add(notifications.category_saved, {name: this.category.name});
this.reset();
},
() => this.notificationService.add(notifications.save_category_failed)
);
} else {
this.notificationService.add(notifications.validation_errors);
}
}
Finally the reset method resets the form and the model so it can be re-entered to submit a next instance:
reset() {
this.form.resetForm();
this.category = {} as NewCategoryDto;
}

Slugs not working in Forms with GET method

This is my routing:
myapp_upgradeAccount:
path: /upgradeAccount
defaults: { _controller: myapp:UpgradeAccount:index }
myapp_checkUsernameForUpgrade:
path: /upgradeAccount/check/{username}
defaults: { _controller: myapp:UpgradeAccount:checkUsername }
methods: [GET]
and this is my form
<form method="get" action="upgradeAccount/check">
<label for="username">Insert your username:</label>
<input name="username" type="text">
<input id="chech-username-for-upgrade" class="green" type="submit" value="Check">
...
But everytime I submit the form I get this error:
No route found for "GET /upgradeAccount/check"
The problem is that when I submit the form, I get the following URL:
http://localhost/app_dev.php/upgradeAccount/check?username=123
when i think I should be getting
http://localhost/app_dev.php/upgradeAccount/check/123
If I trz the latter manually, it works allright. What am I missing?
This is the way HTML forms work.
From w3.org:
get: With the HTTP "get" method, the form data set is appended to the
URI specified by the action attribute (with a question-mark ("?") as
separator) and this new URI is sent to the processing agent.
To do what you want to do keeping the GET method, you have to define your route as:
myapp_checkUsernameForUpgrade:
path: /upgradeAccount/check
defaults: { _controller: myapp:UpgradeAccount:checkUsername }
methods: [GET]
And in your controller, you access the query string parameter with:
if(!is_null($this->getRequest()->get('username'))
{
//Do your stuff
}
EDIT:
If you want your user to be redirected to a url formatted as upgradeAccount/check/{username}, you can either do it with JavaScript (as suggested in #usoban answer) or inside the controller using Redirect:
//path: /upgradeAccount/check
public function check()
{
if(!is_null($this->get('request')->request->get('username')))
return $this->redirect($this->generateUrl('myapp_checkUsernameForUpgrade', array('username' => $this->get('request')->request->get('username'))));
//By default, return the view with your form asking the username
return $this->render('AcmeHelloBundle:Hello:index.html.twig');
}
//path: /upgradeAccount/check/{username}
public function checkUsername($username)
{
//Do some fun stuff with $username coming from the url
}
Then in your view, the form signatures becomes:
<form method="post" action="upgradeAccount/check">
<label for="username">Insert your username:</label>
<input name="username" type="text">
<!-- additionnal fields -->
<input id="chech-username-for-upgrade" class="green" type="submit" value="Check">
</form>
And your route:
myapp_check:
path: /upgradeAccount/check
defaults: { _controller: myapp:UpgradeAccount:check }
methods: [POST]
myapp_checkUsernameForUpgrade:
path: /upgradeAccount/check/{username}
defaults: { _controller: myapp:UpgradeAccount:checkUsername }
methods: [GET]
You need to generate routes that take parameters.
so you would need to do
<form method="get" action="{{path('your_route_name', {username:'usernmae'})">
However this obviously won't quite work for you since you do not know the username until they enter it. You could look into the FOSJsRoutingBundle but this will require javascript. Not the best solution, but it will work. Otherwise you will need to remove the username parameter
As #cheesemacfly mentioned, that is how forms work :)
To get it woking myself, I used some javascript. Let's assume you're using jQuery:
<form method="get" action="upgradeAccount/check/___username___" id="my_form">
...
<input name="username" type="text" id="username">
$(document).ready(function(){
$('#my_form').submit(function(evt){
var username = $('#username').val(),
action = $(this).attr('action').replace(/___username___/g, username);
if (/* empty or whatever 'validation' */) {
evt.preventDefault(); // stop the form from submitting
return false;
}
$(this).attr('action', action);
return true;
});
});

How to pass data from twig to Symfony2 controller using form

I would like to know if there is a method to pass some variables (text from textarea) from Twig to Symfony2 controller via form.
<form action="{{ path('admin_core_save') }}" method="post">
<div id="edit-template">
{% if template.getData() is defined %}
<textarea id="template">{{ template.getData() }}</textarea>
{% endif %}
</div>
<input type="submit" value="Save" />
</form>
When I press save button it is going to saveAction() method
public function saveAction(Request $request)
{
var_dump($request);
return new Response('abc');
}
but response does not contain any textarea text. Is there a way to get this there?
I know I can build form inside the controller and send it to Twig, but I would like to know if this way is possible.
you can access POST values through request object like:
$this->get('request')->request->get('name');
I'm sure you have to learn a bit about Symfony2 Form Component. You will find that symfony already has built-in system for rendering forms handling user data posted through them.
Answering your question. There is a Request object that provides you full access to all request data, including POST variables.
To access POST values use Request::get() method:
$request->get('variable_name');
To pass any data to the twig template, use TwigEngine::renderResponse
$this->container->get('templating')->renderResponse('AcmeDemoBundle:Demo:template.twig,html',
array(
'someVar' => 'some value'
)
);
This var will be avaliable in your template as:
{{ someVar }}

Loging on a cached page in an ESI component redirects on /_internal/secure/.../none.html

I've just setup varnish and i've done some tests on port 8080 before switching in production.
I've noticed that if i'm on a cached page :
/**
* #Cache(smaxage="10800")
* #Route("/{_locale}/", name="homepage2", requirements={"_locale" = "en|fr"})
* #Template()
*/
public function indexAction()
{
return array();
}
And I try to login (not using external services, but with normal login) via the component included via an ESI
{% render "GamerCertifiedHomeBundle:Home:login" with {}, { 'standalone': true } %}
It ends up redirecting me on a page with no style and no head with the url ...:8080/_internal/secure/MyBundleHomeBundle:Home:login/none.html
Step1 Screenshot / Step2 Screenshot
If I go back on the homepage, i'm logged in.
How can I avoid that please ?
EDIT :
Apache2.conf
Apache vhost
Varnish VCL
After analyzing the problem in the chat I found that _target_path for security successful redirect is generated in form in the next way:
<input type="hidden" name="_target_path" value="{{ app.request.uri }}" />
And since this part is rendered with standalone view - it has specific uri (with _internal prefix).
You can avoid this by applying changed logic for your app.request.uri injection.
Pass it to controller:
{% render yourAction with {'uri': app.request.uri}, {'standalone': true} %}
In your controller just pass it to your view
public function yourAction ($uri)
{
...
return array('uri' => $uri);
}
Use it in your template
<input type="hidden" name="_target_path" value="{{ uri }}" />
Enjoiy! ;)

Resources