I want to test the response when I try to send invalid data to the API.
/** #test */
public function request_schedule_with_invalid_bdate()
{
$response = $this->json('GET', '/api/schedule', [
'bdate' => 'thisaintadate',
]);
$response->assertStatus(422);
}
According to the documentation it should return a 422
If the validation rules pass, your code will keep executing normally; however, if validation fails, an exception will be thrown and the proper error response will automatically be sent back to the user. In the case of a traditional HTTP request, a redirect response will be generated, while a JSON response will be sent for AJAX requests.
Also,
When using the validate method during an AJAX request, Laravel ... generates a JSON response containing all of the validation errors. This JSON response will be sent with a 422 HTTP status code.
It sounds like Laravel should automatically handle the exception thrown and proceed with sending the response.
However, running this in PHPUnit would just cause an error.
There was 1 error:
1) Tests\Feature\ScheduleTest::request_schedule_with_invalid_bdate
Illuminate\Validation\ValidationException: The given data was invalid.
I read this question but using $this->expectException(...); will make the test pass but not run my assertions. If I assert the status to be something else other than 422, it will still pass.
My controller has this:
public function show(Request $request)
{
$attributes = $request->validate([
'bdate' => 'required|date'
]);
return ['data' => "It's valid."]
}
Here is my ExceptionHandler class (as requested by Martin H.) This is just what's out of the box. I haven't touched this class yet.
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
}
I have confirmed the following:
Laravel Framework 5.7.21 (php artisan --version output)
It runs the appropriate code/controller based on the stack trace:
.../vendor/laravel/framework/src/Illuminate/Validation/Validator.php:315
.../vendor/laravel/framework/src/Illuminate/Validation/Factory.php:136
.../vendor/laravel/framework/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php:53
.../vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:108
.../app/Http/Controllers/MyController.php:35
What am I missing?
I was having a problem with the ValidationException crashing my test because I disabled exception handling. I disabled exception handling so I can debug my tests better (ironic, I know) and I forgot that I did this.
class ... extends TestCase
{
protected function setUp()
{
/**
* This disables the exception handling to display the stacktrace on the console
* the same way as it shown on the browser
*/
parent::setUp();
$this->withoutExceptionHandling();
}
Removing $this->withoutExceptionHandling(); now allows me to do assertions on the response.
There was 1 failure:
1) Tests\Feature\ScheduleTest::request_schedule_with_invalid_bdate
Expected status code 200 but received 422.
Failed asserting that false is true.
Relevant links:
- https://github.com/laravel/framework/issues/26013
- laravel phpunit withexceptionhandling
Related
I have an old Laravel app that is upgraded to v7. Currently exceptions are returned as an HTML page. But I would like them to be returned as json response. I understand that exceptions are handled in Exceptions\Handler.php:
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
});
}
}
How can I return the whole exception sack as json?
Edit: The app is old and was using a custom exception handler. Now after the upgrade to V.7 it would be nice to use the default exception handler so that exceptions are returned in json.
Edit 2:
When I use
return response()->json([
'exception' =>exception,
]);
I get an empty object. I don't want to only return the message of the exception but the whole exception stack.
You can override render function inside App\Exceptions\Handler.php
and you can read more about it https://laravel.com/docs/7.x/errors#render-method
I have tried to handle the exceptions on Handler class in my laravel project
if(!env('APP_DEBUG', false)){
return view('errors.500');
} else {
return parent::render($request, $exception);
}
Errors are redirecting to my page. but in the login page user name or password mismatch also redirecting to that page.
in the login error need to redirect to the login page,not to the common error page. how can handle it?
i have using default laravel auth login.
this is my Handler.php file,
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
// Render well-known exceptions here
// Otherwise display internal error message
if(!env('APP_DEBUG', false)){
return view('errors.500');
} else {
return parent::render($request, $exception);
}
}
}
no need to check for 500 errors manually .If you want to show custom error message for 500 erorrs in production then publish default laravel views for errors
php artisan vendor:publish --tag=laravel-errors
This will generate views for errors in following path
resources/views/errors
To customize 500 erorrs .Edit following path
resources/views/errors/500.blade.php
Also make sure this will only show when
APP_DEBUG=false
In my Laravel project I'm encountering the following behavior which I can't isolate and what is pretty annoying e.g. when I'm sending a request to a controller and either the route or the controller does not exist, Laravel is neither logging the error nor showing the error and but always redirecting to the login page - I've searched around a lot and i may misconfigured something in the project, but can't find out what's the issue.
My Laravel Version: 7.3.4
System: Windows
Server: Wamp with Apache 2.4.39, Mysql 5.7.26, Php Version: 7.3.5
route/web
// Route url
Route::get('/', 'DashboardController#dashboard');
//.. custom routes
Auth::routes();
The Controller looks like this
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
// Dashboard - Ruwido
public function dashboard(){
$pageConfigs = [
'pageHeader' => false
];
return view('/pages/dashboard', [
'pageConfigs' => $pageConfigs
]);
}
}
The Error Handler
<?php
namespace App\Exceptions;
use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Throwable $exception
* #return void
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Throwable $exception)
{
return parent::render($request, $exception);
}
}
Has anybody ever had such a problem?
You are using the middleware auth in the constructor which will redirect to login if the user is not authenticated and will not let the request proceed in the dashboard method. If the user is authenticated that check your default guard, maybe its pointing to api authentication and now that you are using api auth it will not authenticate via session but using api tokens; first try commenting out the middleware in the constructor;
How do you retrieve the middlewares for the current route?
I'm trying to set the exception handler to work differently based on if you're within a particular part of the website by checking if a middleware has been added to the route.
<?php
namespace App\Exceptions;
//use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Abrigham\LaravelEmailExceptions\Exceptions\EmailHandler as ExceptionHandler;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use Illuminate\Support\Facades\Route;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Throwable $exception
* #return void
*
* #throws \Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Throwable
*/
public function render($request, Throwable $exception)
{
switch(true) {
case $exception instanceof \Illuminate\Session\TokenMismatchException:
// Redirect back if form invalid
return redirect()
->back()
->withInput($request->except($this->dontFlash))
->withErrors('The form has expired due to inactivity. Please try again');
break;
case $exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException:
// Redirect back with message if model not found error
$redirect = app('redirect');
// Check for different url to prevent redirect loop
if (request()->fullUrl() === $redirect->back()->getTargetUrl()){
// $currentRouteMiddleware = request()->route()->controllerMiddleware() returns empty array
// $currentRouteMiddleware = Route::current()->router->getMiddleware(); router is private
$response = $redirect->to(isset($currentRouteMiddleware['admin.user']) ? '/admin' : '/');
} else {
$response = $redirect->back();
}
return $response
->withInput($request->except($this->dontFlash))
->withErrors('That page could not be found. Please try again or report the broken link: ' . $request->getRequestUri());
break;
}
return parent::render($request, $exception);
}
}
If I dump the current route inside the router it shows the middleware array that I need to check against: dd(Route::current()) but there doesn't appear to be a way of accessing the current router e.g: $currentRouteMiddleware = Route::current()->router->getMiddleware();
There are a couple options based on what you're looking for.
If you want all the middleware aliases assigned to the route, you can use:
Route::current()->gatherMiddleware();
This does not expand assigned middleware groups, so the result from this might look something like:
[
'web',
'admin.user'
]
If you want all of the middleware classes assigned to the route, you can use:
Route::gatherRouteMiddleware(Route::current());
This will give you the classes, but it will not have the associated aliases or groups for the classes. The result from this might look like:
[
"App\Http\Middleware\EncryptCookies",
"Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse",
"Illuminate\Session\Middleware\StartSession",
"Illuminate\View\Middleware\ShareErrorsFromSession",
"App\Http\Middleware\VerifyCsrfToken",
"Illuminate\Routing\Middleware\SubstituteBindings",
"TCG\Voyager\Http\Middleware\VoyagerAdminMiddleware"
]
The Route::getMiddleware() in the Handler is simply a list of \App\Http\Kernel::$routeMiddleware.
dd(Route::getMiddleware());
array:7 [▼
"auth" => "App\Http\Middleware\Authenticate"
"auth.basic" => "Illuminate\Auth\Middleware\AuthenticateWithBasicAuth"
"guest" => "App\Http\Middleware\RedirectIfAuthenticated"
"admin" => "App\Http\Middleware\AdminMiddleware"
// ...
]
It is recommended to declare a constant (or global variable) and check whether the corresponding constant is declared in the Handler to check whether AdminMiddleware has passed.
// app/Http/Middleware/AdminMiddleware.php
public function handle($request, Closure $next)
{
// if ... return ...
define('__APP_IS_ADMIN', true);
return $next($request);
}
// app/Exceptions/Handler.php
public function render($request, Exception $e)
{
// ...
$response = $redirect->to(defined('__APP_IS_ADMIN') ? '/admin' : '/');
request()->route()->controllerMiddleware();
List middlewares applied in the current route inside App\Exceptions\Handler. Using in unauthenticated method.
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(in_array('admin', $request->route()->middleware(), true) ? route('admin.login') : route('login'));
}
I used this to get the "admin" middleware and redirect the user to the correct login page.
I am using Laravel v 5.7.15.
I wrote a validation helper which validates an API request - this works successfully, and prior I was using a try/catch to surround it.
I have moved on to handling the exception in the handler, however I cannot get the function 'render' to run - it goes straight into 'report' and throws in the exception in my tinker console.
Handler: (full class as requested)
<?php
namespace App\Exceptions;
use Illuminate\Validation\ValidationException;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Log;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* #param Exception $exception
* #return mixed|void
* #throws Exception
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
dd($exception);
$log = new Log();
$log->status = 2;
// Validate API incoming data
if ($exception instanceOf ValidationException) {
foreach ($exception->errors() as $error) {
// collect multiple validation errors
$message[] = implode('', $error);
}
$message = implode('', $message);
$log->message = $message;
$log->save();
$response = [
'message' => $message,
'status' => 400,
];
} else {
$response = [
'message' => $exception->getMessage(),
'status' => '500',
];
}
return parent::render($request, $exception);
}
}
This fails to die and dump, however I can dd in the report function and this works fine. The rest of this file has been left untouched, save for the includes at the top of the file.
This is how I call my validator in my controller:
$this->validate($request, BlueparkValidatorArrays::$getOrders);
If anybody could point me in the right direction, I would be most grateful.
This may be caused by a problem in your log configuration.
The call to parent::report($exception); runs the following from the laravel source code:
public function report(Exception $e)
{
...
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception $ex) {
throw $e; // throw the original exception
}
...
}
Note throw $e not throw $ex. So if creating the logger implementation fails, the original exception that was being processed is thrown.
To test this, comment out parent::report($exception); in your report function and see if render() is called as expected.
If it is, your log configuration is not working. Make sure you have the correct permissions on your log location and that your .env file doesn't override any of laravel's logging settings. See How to debug Laravel error 500 with no logs, no information