Where does laravel 5 handle the ValidationException? - php

If the incoming request was an AJAX request, no redirect will be
generated. Instead, an HTTP response with a 422 status code will be
returned to the browser containing a JSON representation of the
validation errors.
This is not working! I am trying to access the route via an ajax request and it redirects back.
If validation passes, your code will keep executing normally. However, if validation fails, an Illuminate\Contracts\Validation\ValidationException will be thrown. This exception is automatically caught and a redirect is generated to the user's previous location. The validation errors are even automatically flashed to the session!
Now I want to know where does laravel catch this exception so that I can modify it?

This is handled inside the FormRequest class:
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException($this->response(
$this->formatErrors($validator)
));
}
You can override this function in your own Request object and handle a failed validation any way you like.

After been researching for a while I will post my results so anyone with this problem saves a lot of time.
#Faiz, you technically shouldn't change a thing if you want to stick to laravel behavior (I'll always try to follow taylor's recommendations). So, to receive a 422 response code status you need to tell phpunit you will send a XMLHttpRequest. That said, this works on laravel 5
$response = $this->call('POST', $url, [], [], [],
['HTTP_X_Requested-With' => 'XMLHttpRequest']);
More information at Github Issues. Besides, if you look at Symfony\Component\HttpFoundation\Request#isXmlHttpRequest you will find that this header is used by "common JavaScript frameworks" and refers to this link
Haven't tested on the browser yet, but I think this should work too.

Related

Sub request in Laravel 5

I'm looking to do sub requests in my API, to other parts of my API. I have done this before in Symfony - but I'm not sure how to achieve this in Laravel.
$url = route('some.route', ['param' => $val]);
$request = Request::create($url, 'get', []);
Route::dispatch($request);
Always seems to fail giving something along the lines of
Class api does not exist
So I've tried
app()->handle($request);
This works, but processes a request, but I cant handle any exceptions thrown (e.g. validation as the app layer handles it and throws a html response)
Handle has a signature of the HttpKernelInterface, so can take a property of sub requests and catch exceptions - but these are not used....
...->handle($request, HttpKernelInterface::SUB_REQUEST, false);
Is it possible to do this in Laravel without having to send an actual http request?
Thanks
So after digging deep into the framework, it seems that this way is not possible as it stores some values as statics and they're not updated via the dipatch() method.
So ive created this package to handle setting and reapplying the original values.
https://github.com/myerscode/laravel-sub-request

How can I get the (real) HTTP response status code from a CORS request in AngularJS 1.5?

I have a frontend project using AngularJS 1.5.8, which communicates with an API in PHP (Laravel framework). Preflight OPTIONS requests are going as expected, but I'm facing a problem now because I need to know when requests specifically fail with a status code of 401 Unauthorized.
Through $http(request).then(successFn, errorFn), when errorFn is called, it has a single Object argument, whose status is -1. When I inspect the Network tab on Chrome developer tools, the request clearly fails, as expected, with 401. Is there a way to get this real status code inside the javascript? I've seen some related questions and answers but most were regarding older versions of angular (in which you had the .success method being called with multiple arguments, including status).
Am I missing something on the API side, with CORS? Or is it an AngularJS restriction? I tested it with Postman too, and it brings the correct status code, and even a whole JSON object, with message and debug properties (none of which shows up in that Network tab of Chrome).
After being given a little nudge in the right direction, I found out how to fix this problem between the front and back ends of my project. In Laravel file api_routes.php, I was inserting CORS as a middleware to each group, as in:
$api->group([ 'middleware' => ['cors'] ], function($api) {
// ...
});
This didn't ensure the CORS response headers to be included when requests went bad. So although the browser caught a status code 401 (or anything else), Angular complained something about missing Access-Control-Allow-Origin, thus the response object in javascript was pretty useless (it had no data, status was -1, and statusText was empty).
By setting CORS as a global middleware on my Laravel project, all necessary headers began showing up, even on error responses like 401.
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* #var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Barryvdh\Cors\HandleCors::class,
// ...
];
// ...
}
The response object then became actually useful as it all the forementioned properties as specified on the API and I managed to do what I needed with the status code.
Maybe this helps someone on a similar situation. Be aware, though, as it's a global setting, these headers might show up in responses you didn't intend them to. If anyone knows a more precise way, it'd be great!

Symfony2: force stop code execution after sendiing Response headers

What is the correct way to stop code execution after sending Response headers, but without using exit()?
I know the script SHOULD return a Response, but how can I force it to be returned from outside of a Controller, for example from a service?
Lets say my service's method does return a Response this way:
return RedirectResponse($url)->send();
But in another place it can return other things. So what I can do is to check what it does actually return:
$result = $myService->doSomething();
if ($result instanceof RedirectResponse) {
return $result;
}
What I want to achieve is to avoid checking result type in every place where I use my service, BUT I would like to see the returned response in Profiler/logs (if I use exit() I can't).
Is there a way to force kernel terminate?
EDIT:
Actually the service is used in event before any controller, so I want to do redirect before any controller execution. So maybe a way to omit controller execution?
A controller is a PHP callable you create that takes information from the HTTP request and creates and returns an HTTP response (as a Symfony Response object).
The only concern for a Controller is to process a request and return a response. Having a Service handle a Response object is probably a bad design choice.
In any case, you could just die/exit your controller and use Kernel events to hook in the Request/Response flow and inspect the returned Response. Probably the terminate event is the right choice http://symfony.com/doc/current/components/http_kernel/introduction.html
Ok, I found a way to do it. All I had to do is to terminate the kernel before exit - it does dispatch all after-response events (like profiling, logging etc.).
$response = new RedirectResponse($url);
$response->send();
$kernel->terminate($request, $response);
exit();
If anyone would found better way do to this, please answer so I can switch the mark.

Laravel Ajax validation

I understand how to validate requests by type-hinting the class name in the controller method. However for Ajax requests, According to the documentation, I should validate data in the controller, because using a validator class will redirect rather than send a response.
The main part I'm looking at is this:
If the incoming request was an AJAX request, no redirect will be
generated. Instead, an HTTP response with a 422 status code will be
returned to the browser containing a JSON representation of the validation errors.
However, my controller is as follows:
public function update(App\Permission $permission, Request $request)
{
$this->validate($request, [
'permission_description' => 'required|string'
]);
...
}
And I can't for the life of me get it to respond with JSON. The documentation states that if it fails, it throws an Illuminate\Contracts\Validation\ValidationException exception, but I can't catch it.
Whenever it fails, it always redirects back to the edit page. Obviously I don't want this, I want the json response.
I have just tried "manually writing it out" with the whole $v = Validator::make($request->all(), ...); which does work, but what's the point in using the $this->validate() way if it doesn't work?
Does the $this->validate() method just not work with AJAX and I have to write it the long way each time? Am I doing something wrong?!
Below is what I've tried:
public function update(App\Permission $permission, UpdatePermissionRequest $request)
{
/** Redirects rather than returns JSON if the validation fails **/
}
----------------------------------
public function update(App\Permission $permission, Request $request)
{
$this->validate($request, [
'permission_description' => 'required|string'
]);
/** AND I've also tried: **/
try {
$this->validate($request, ['permission_description' => 'required|string']);
} catch (\Illuminate\Contracts\Validation\ValidationException $e {
echo $e; /** Echoing for debug reasons **/
exit;
}
...
/** Still redirects the browser, even if it is an AJAX request **/
}
-----------------------------------------
use Validator;
...
public function update(App\Permission $permission, Request $request)
{
$v = Validator::make($request->all(), [
'permission_description' => 'required|string'
]);
if($v->fails())
{
return response()->json(['reply' => false]);
}
/** Works **/
}
UPDATE
The documentation is incorrect. It states that the $this->validate() method throws a Illuminate\Contracts\Validation\ValidationException but it doesn't. It throws a Illuminate\Http\Exception\HttpResponseException exception.
Simply telling that you want json in the header should also fix this. Laravel checks if the request is ajax of if json is requested.
if ($this->ajax() || $this->wantsJson())
{
return new JsonResponse($errors, 422);
}
Solution:
Add header
Accept: application/json
Your statement that the docs say it is best to validate AJAX requests in the controller is simply incorrect.
If you scroll a bit further down from what you linked - you'll see this under the FormValidation section
If validation fails, a redirect response will be generated to send the
user back to their previous location. The errors will also be flashed
to the session so they are available for display. If the request was
an AJAX request, a HTTP response with a 422 status code will be
returned to the user including a JSON representation of the validation
errors.
In other words - there is no reason you cannot do this in a simple FormRequest and simply your code significantly. It will automatically handle the fact it is an AJAX call and return the appropriate HTTP responses.
I do this all the time in my L5 apps - it works flawlessly.
Ok, so it looks like there were 2 contributing factors.
The reason it was redirecting instead of responding with JSON is because the X-Requested-With field wasn't set on the AJAX request. This the AJAX wrapper for the application was setup to deal with Cross Domain requests, which seems to strip out the X-Requested-With field, which makes the ultimate request look non-ajax to the server - hence the redirection.
The reason why it wasn't catching the Illuminate\Contracts\Validation\ValidationException exception is because that exception is not thrown. If you open up Illuminate\Foundation\Validation\ValidatesRequest.php, and search for the function throwValidationException(), it actually throws a HttpResponseException instead. I attempted to catch the HttpResponseException and it was caught successfully - I assume the documentation is wrong.
The solution was to either remove the cross domain attributes on the ajax request, add the X-Requested-With header manually, as per the answer in this post. This would make the application see that it was an AJAX request.
And if you wanted to manually catch the exception, you need to catch the HttpResponseException, not the ValidationException.

Zend _forward() does not work in preDispatch()?

Im currently building a controller from my Zend MVC application which would only be used as json service to populate the page. I want to restrict the users to use only GET method to access this end point(for some security reasons).
I followed this post _forward() in Zend does not work? but could not get working.
I am using the preDispatch to detect the non-get requests and would like to forward to errorAction in the same controller. My code looks likes this,
public function preDispatch(){
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender();
//Restrict this Controller access to Http GET method
if(!($this->getRequest()->isGet())){
return $this->_forward('error');
}
}
public function errorAction(){
$this->getResponse()->setHttpResponseCode(501);
echo "Requested Method is not Implemented";
}
When I test the page with a post request, it throws
PHP Fatal error: Maximum execution time of 30 seconds exceeded
I got it working with
$this->_redirect("service/error");
Wondering if it is the only/best way to handle this situation.
Any help would be really appreciated. Thanks in advance.
The reason that calling _forward doesn't work is because the request method doesn't change so you end up in an infinite loop trying to forward to the error action since the request is always POST.
_forward works by modifying the module, controller, and action that will be called when the request is dispatched, _redirect actually returns a 302 redirect and results in an additional HTTP request by the browser.
Either method is okay, but I'd prefer to go with _forward since it won't require an additional HTTP request (but you still guarantee the POST request is denied).
This code should work for you:
if(!($this->getRequest()->isGet())){
// change the request method - this only changes internally
$_SERVER['REQUEST_METHOD'] = 'GET';
// forward the request to the error action - preDispatch is called again
$this->_forward('error');
// This is an alternate to using _forward, but is virtually the same
// You still need to override $_SERVER['REQUEST_METHOD'] to do this
$this->getRequest()
->setActionName('error')
->setDispatched(false);
}

Categories