I have Laravel app with Vue on front end, and Vue calls update method from controller using PUT request.
Request works, model gets updated, but I have issue with redirecting as it is redirecting also as a PUT instead of simple GET?
public function update(MomentsValidationRequest $request, Project $project, Task $task, Moment $moment)
{
foreach($request->materials as $material){
$material_id_array[$material['id']] = ['quantity' => $material['quantity']];
}
$moment->update($request->all());
if(isset($material_id_array))
$moment->materials()->sync($material_id_array);
return redirect()->back()->with(['alert-type' => 'success', 'message' => 'Moment updated!']);
}
So naturally, I am getting a method not allowed exception because it is redirecting to a route which is supposed to get a previous view only.
Route itself is fine, request method isn't.
For non-believers :)
Also a route:
I know this is a bit late. But incase anyone stumbles across this.
You state that you're using Vue in the front end. This would suggest that the put request is being made through an axios call.
I can't see this call, so this is only an assumption. But I believe the solution would be to return a json object instead of a response in the controller, and then redirect trigger a redirect from the Vue component itself.
In the controller:
Session::flash('alert-type', 'success');
Session::flash('message', 'Moment updated!');
return response()->json(true);
In the component:
axios.post('/moments', this.moment).then(() => {
window.location.replace("moments");
});
I believe this is something to do with how axios handles patch requests, it seems to attempt to handle a redirect response automatically, I could be wrong though, so any reply is welcome to if there's a better explanation.
You can use:
redirect()->back(303)->with(...)
No, redirection is made always with GET but you don't have such route defined. So you should create GET route that will do something with this.
It's possible only to redirect to GET routes.
Related
I have been working on a simple Laravel Inertia Vue3 application. It has one resource route.
Route::resource('contact', \App\Http\Controllers\ContactController::class);
This provides the named routes contact.index .store .create .show .update .destroy and .edit
Nice and simple so far.
I have a useForm form variable in my Vue component with some assigned variables
let form = useForm({ ... });
I have a submit method in the same component
let submit = () => {
if(props.edit) {
form.patch(`/contact/${props.contact.id}`);
} else {
form.post(`/contact`);
}
}
Again nothing complex. The post method fires off correctly and redirects
Contact::query()->create($request->all());
return redirect()->to(route('contact.index'));
For full disclosure of the update method, please see below:
public function update(Request $request, Contact $contact): \Illuminate\Http\RedirectResponse
{
$contact->fill($request->all())->save();
return redirect()->to(route('contact.show', ['contact' => $contact]));
}
This works in the same way as store. Simple and then redirects... but it doesn't.
What happens is that it runs the patch and then calls redirect
The redirect carries the patch method through ending up with a 405 if I use the index route (declared as get). If I use back() I get the same thing. If I use the show route, it redirects in a loop because the patch route uses the same URL /contact/{contact}
I have built Laravel applications for the last 5 years and have not had an issue like this before when using a named route. Have I missed something basic? If not its possibly a configuration issue although I am not sure what.
I am running Laravel 9.19 with webpack manually installed as its been changed to Vite on the current release. I have no Vue errors or warnings and no Laravel logs.
there is a difference between PUT and PATCH requests on laravel-level.
Please run php artisan route:list - and see which one is used in your case, There is a big chance that you using PUT, not patch :)
So good ol' Laravel got me again.
Alexander Dyriavin got me on the right course with his answer about put and patch, however, it wasn't really the solution.
The solution:
form.transform((data) => ({
...data,
_method: 'PUT' //spoof added to request
})).post(`/contact/${props.contact.id}`); //sent as a post instead
The Laravel docs allow you to spoof methods https://laravel.com/docs/5.0/routing#method-spoofing by posting them with a _method field.
Simply put, a patch or put request would have always failed with a redirect from Laravel. In the past I would have used them with Axios and handled a JSON response directly.
This time I really am answering my own question.
I was a total idiot and missed a step when setting up inertia js. I was attempting to retrieve errors with the useform method and what happened was I received nothing.
So I though I would double check the docs.
Turns out I missed adding this middleware to the web middleware group in the kernel!
\App\Http\Middleware\HandleInertiaRequests::class,
I can now use the .patch method I had before and no need for any additional code
I do a post request to a route calculate-cars and display an other view with some data I query based on the data I entered in the form I do the post request from, but I want to redirect to the homepage when someone access this route directly.
Unfortunately I keep getting this error:
Symfony \ Component \ HttpKernel \ Exception \
MethodNotAllowedHttpException No message
my route:
Route::post('calculate-cars', 'CarsController#calculateCars');
I know I get this error because I access a post route directly but I try this in my controller method but I still get the same error:
if (!$request->isMethod('post')) {
return redirect()->to('/');
}
Add another route
Route::get('calculate-cars', function () {
return redirect()->to('/');
});
If you'r using Laravel 5.5, you can do it like this:
Route::redirect('/calculate-cars', '/', 301);
UPDATE:
The method Route::redirect will redirect the post route as well, it's not useful in your case.
Just put this in your routes/web.php file:
Route::get('calculate-cars', function () {
return redirect()->to('/');
});
You didn't explain why you expect a POST request. Do you save calculations to save time (cache)? Only in that case, POST is a right decision.
In addition to others: it's important to choose your http methods wisely.
GET: for READ (no system change)!
POST: for CREATE
etc.
Like I said, why did you choose for POST? Change only to GET if you want to read. MDN has a clear summary about the methods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
but I want to redirect to the homepage when someone access this route
directly.
That means when a user does a GET
Your route Route::post('calculate-cars', 'CarsController#calculateCars');
only allows POST
So change it to
Route::any('calculate-cars', 'CarsController#calculateCars');
I understand you are trying to redirect the user upon MethodNotAllowedHttpException
You can use Laravel's Exception Handler for this:
Open app/Exception/Handler.php and change render method to this:
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException)
{
return \Redirect::to('/');
}
return parent::render($request, $exception);
}
Rename your route to something different and test it again.
For instance
Route::post('calculate-cars', 'CarsController#calculateCars');
could be like
Route::post('calculate-test-cars', 'CarsController#calculateCars');
I have had cases where changing the route might do the trick. Not sure if it's always like that.
Let me know
Try using any method in route and place your condition in controller then,
Route::any('calculate-cars', 'CarsController#calculateCars');
Hope this will work.
I'm sending an ajax post request, and with Laravel it seems that is done by creating a post route for it. I've set it up so a csrf token is put in the header automaticaly for every ajax request using ajaxSetup. I'm attempting to then catch that header on the backend and verify the tokens match.
In my web routes (which automatically use the web middleware), this returns as expected:
Route::get('/test', function() {
return csrf_token();
});
However, when I post to a route via AJAX, like either of the below ways:
Attempt 1:
Route::post('/test', 'AjaxController#test');
In the AjaxController construct, followed by an alert in the view:
var_dump(csrf_token().',hi'); die;
Response: ',hi' (csrf_token was null).
Attempt 2:
Route::post('/test', ['test' => csrf_token().',hi', 'uses' => 'AjaxController#test']);
$test = $request->route()->getAction()['test'];
var_dump($test); die;
Response: ',hi' (csrf_token was null).
What I seem to be running into is, with get requests csrf_token() is populated, on my post request, it is not.
Any ideas?
check your route group it must apply the web middleware as
Route::group(['middleware' => 'web'], function () {
Route::get('/test', function() {
return csrf_token();
//or return $request->session()->token();
});
});
Finally figured this out.
CSRF can indeed be checked on an ajax post request. I wanted to make sure someone on their own site isn't hitting my ajax endpoint with any success of doing anything, especially for another user.
However, I ran into a Laravel order of operations issue, with the way Laravel sets up the session. I was trying to call a validation method (within in the same class) in the constructor, where I validated for CSRF and verified the requesting user all in one place. I wanted to do this so that any time someone hits this class, I didn't have to call the verification in each public method in the class, I'd only have to call it once.
However, csrf_token(), and the request session in general, is not available to me yet in my construct. It is, however, available to me in the method within the controller class that is called in the route.
For example, given the following route:
Route::post('/test', 'AjaxController#test');
If I injected Request into the construct and then tried to reference anything in the session (in the construct), or get the value of csrf_token(), it will throw an error, because Laravel hasn't set that stuff up yet. But if I reference either of those things in the test method, it'll be there and available just fine.
A bit of a weird Laravel order of operations issue.
csrf protections are managed by Laravel Forms. It won't be available when dealing with APIs.
You should have a look at how middlewares are used in Laravel
https://laravel.com/docs/5.4/middleware
Think using API middleware for your APIs ;)
If you run this command php artisan make:auth documented here https://laravel.com/docs/5.4/authentication#authentication-quickstart when going to resources/views/layouts/app.blade.php you'll see this:
<meta name="csrf-token" content="{{ csrf_token() }}">
And in app.js
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')
}
});
In 5.3 there was this cool feature which looks as though it has since been removed in 5.4.
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
So what you need to do is add the csrf field to every request. Do the first 2 code snippets and you'll be fine. The 3rd I believe is probably for Vue.
Answer to your question: no, no, no and no. CSRF tokens I wouldn't believe are generated in POST requests, it's a Cross site Reference token, not an authentication token. If you're looking for something like authentication token refreshing then checkout JWT although the packages for JWT for laravel are a bit unfinished at the moment; with a little work you can get them working.
https://github.com/tymondesigns/jwt-auth 1.0.*#dev is pretty good. You can then use their refresh middleware to generate new tokens on request but this is quite advanced and unless it's for authentication then I wouldn't bother really.
I believe Dingo (another work in progress I believe) https://github.com/dingo/api uses the above package
Anything else let me know!
When using laravel routing I write my links this way:
mywebsite.com/category/product
But to call ajax I would like instead:
mywebsite.com?author=&article=
And for _POST then
mywebsite.com/php/includes/dostuff.php
(The previous links are only examples)
As stated in the Laravel API documentation you could do something like
$query = $request->query();
to get all the url query ($_GET) items.
Source: https://laravel.com/api/5.3/Illuminate/Http/Request.html#method_query
Hope this helps
You just use normal routing when it comes to making ajax calls.. There is many ways you can go about it and two simple ways would be:
Set the post route for your '/' (Home page or root page) to aim at a controller if you dont have any other post requests that are made to the '/' page or route..
Route::post('/', 'AjaxController#WhatEverMethod');
If you already have other post requesting routes that point to ('/') then you can put a if statement to see if the request is Ajax and if it is then use the Ajax controller else use your normal controller.
My second simple solution would be to set a dedicated route that will handle your Ajax calls so for example:
Route::any('/Ajax/', 'AjaxController#index');
public function index(Request $request)
{
if($request->ajax()){
return "Is an ajax call..";
}
return "Is not an ajax call";
}
There should be no reason why you cant easily resolve this issue, Checkout laracasts, That might give you a good head start.
I'm using laravel 5 and this is my problem. User fill in form X and if he isin't logged in, he gets redirected to fill in more fields form OR he gets possibility to log in. Everything works just fine, if user fill in additional fields, but if he login, laravel redirects user to form X with GET method instead of POST.
This is how my middleware redirect looks like:
return redirect()->guest('user/additional-fields');
This redirect appears on successfull log in:
return redirect()->intended();
So on redirect intended i get error
MethodNotAllowedHttpException. URL is correct which is defined as POST method. What am I missing here? Why does laravel redirects intended as GET method? How could I solve this problem? Thanks!
EDIT:
Route::post('/user/log-in-post', ['as' => 'user-log-in-post', 'uses' => 'UserController#postUserLogIn']);
This is my route, I hope this is one you need.
You can use a named route to solve this issue:
Lets make a named route like this:
For Get
Route::get('user/additional-fields',array(
'uses' => 'UserController#getAdditionalFields',
'as' => 'user.getAdditionalFields'
));
For post
Route::post('user/additional-fields',array(
'uses' => 'UserController#postAdditionalFields',
'as' => 'user.postAdditionalFields'
));
So we can now ensure Laravel uses the right route by doing this
return redirect()->guest(route('user.getAdditionalFields'));
Also note that its not possible to redirect a POST because Laravel expects form to be submitted. SO you can't do this:
return redirect()->guest(route('user.postAdditionalFields'));
except you use something like cURL or GuzzleHttp simulate a post request
You have to trick Laravel router by passing an "_method" the inputs.
The best way I found is by adding tricking and rewriting the Authenticate middleware
You have to rewrite the handle method to allow your redirection with your new input.
redirect()->guest('your/path')->with('_method', session('url.entended.method', 'GET'));
When you want to redirect to a route using another method than GET, simply do a Session::flash('url.entended.method', 'YOUR_METHOD').
Tell me if it do the trick
Very Simple Approach for Post method Route form Controller.
The idea behind this is, every Route always calls the Action method of a Controller. so that in that case you can directly call that method in place of Redirect action performed.
check a code sample of XYZController
$registration = Registration::find($req->regId);
$registration->update([ 'STEP_COMPLETED' => 5]); // Step 5 completed.
# Call Post Method Route
return $this->admissionFinish($req);
Note that $req should have all parameter that required in next
action Method.
change the below code in app\exceptions\handler.php
use Exception;
use Request;
use Illuminate\Auth\AuthenticationException;
use Response;
protected function unauthenticated($request,AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
//return redirect()->guest(route('login'));
return redirect()->guest('http://127.0.0.1:8000/api/signinnew'); // change this part to your login router
}
And in routes(i.e api.php):
Route::Any('signinnew', [UserLogonController::class, 'signinNew']);
This will work in laravel 8x