(Laravel 5.8) How to get verbosity level inside Exception Handler class - laravel

I'm trying to get the verbosity level INSIDE the Exception Handler class so I can print more or less information.
I'm writing a batch script that runs over hundreds of thousands loops and so that my goal is to limit the verbosity of the stack trace, since in this case the only useful traces are the last 3 or 4.
I've tried some other answers given here in SO and have read all around Internet, but none of ther talk about working inside the exception handler.
The one that looks closer is this answer that states $this->getOutput()->getVerbosity();but that doesn't works inside the error handler.
This is my code:
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Mail;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\ExceptionHandler as SymfonyExceptionHandler;
use App\Mail\ExceptionOccured;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Output\OutputInterface;
class Handler extends ExceptionHandler
...
...
public function render($request, Exception $e)
{
// return parent::render($request, $e);
Log::error($this->buildMessage($e));
}
public function buildMessage($e)
{
$verbosity_level = $this->getOutput()->getVerbosity();
$is_verbose = ($verbosity_level >= OutputInterface::VERBOSITY_DEBUG);
$t0 = $e->getTrace()[0];
$tm = "Error: [{$e->getCode()}] {$e->getMessage()} # {$t0['file']}:{$t0['line']}".PHP_EOL;
// if ($is_verbose){
...(more code) ...
// }
return $tm;
}
}
And this is the output:
PHP Fatal error:
Uncaught Error: Call to undefined method App\Exceptions\Handler::getOutput() in ...\Handler.php:89
I've seen that the output property exists but it is protected and has no getter.
Maybe the cited answer is valid for earlier versions.
I'm really lost as I just have 2 months playing with Laravel.

The Exception handler has a specific method for the console: renderForConsole().
/**
* Render an exception to the console.
*
* #param \Symfony\Component\Console\Output\OutputInterface $output
* #param \Exception $e
* #return void
*/
public function renderForConsole($output, Exception $e)
{
(new ConsoleApplication)->renderException($e, $output);
}
It's a pretty simple wrapper around the Symfony Console component's default exception handler. Customize to your needs!

Related

Laravel not calling report method in custom exception

Trying to use custom exception:
namespace App\Exceptions\Cloud;
use Exception;
class CantConfirmOrder extends Exception
{
public function report()
{
info('test exception');
}
}
But when I throwing it in tinker - nothing writes to log:
>>> throw new CantConfirmOrder('test');
[!] Aliasing 'CantConfirmOrder' to 'App\Exceptions\Cloud\CantConfirmOrder' for this Tinker session.
App\Exceptions\Cloud\CantConfirmOrder with message 'test'
Handler.php:
public function report(Throwable $exception)
{
parent::report($exception);
}
Does I need to call report() manually with try catch? I thinked it will be called automatically when I throwing.
In the context of an HTTP-request, the routing will pick this up and call the report method. So if you throw the error in a Controller method or another route action it should be called. You can try it in a test like this :
Route::get('x', fn() => throw new CantConfirmOrder('test)); $this->get('x');
The Route class/method that invokes the method is
https://laravel.com/api/9.x/Illuminate/Routing/Pipeline.html#method_handleException

Phpunit : hadling the validation exception in laravel

I have turned throw Exception in handler.php so that I can catch exceptions and see the errors but what happened is when I try to do the validation checks it throws me an exception which is correct but in my test case instead of catching the exception i'm asserting that the session has errors.
/** #test*/
public function a_thread_requires_a_title(){
$thread = make('App\Thread', ['title'=> null]);
$this->post('threads', $thread->toArray())
->assertSessionHasErrors('title');
}
Since, validation error is an exception so it throws me an exception because I've modified the handler.php file as
if(app()->environment() === "testing") throw $exception;
so, what I'm trying to do is change the env for this one test so that it wont throw me an 'Exception'
There are 2 helper methods which you can write at the top of your test method:
$this->withoutExceptionHandling(); and $this->withExceptionHandling();
They are included in Laravel's 'Illuminate\Foundation\Testing\Concerns\InteractsWithExceptionHandling' trait which is used by the abstract TestCase that you should be extending from your test. (as mentioned here)
/** #test*/
public function a_thread_requires_a_title() {
$this->withExceptionHandling();
$thread = make('App\Thread', ['title'=> null]);
$this->post('threads', $thread->toArray())
->assertSessionHasErrors('title');
}

laravel: Argument 1 passed to App\Exceptions\CustomException::report() must be an instance of Exception,

I have created a custom exception class in Laravel 5.2. It works well till laravel 5.4.
When Im trying to use the same custom exception class with laravel 5.5 it is throwing following error.
Type error: Argument 1 passed to App\Utility\Exceptions\CustomException::report() must be an instance of Exception, none given, called in /var/www/html/bubbles/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php on line 102 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Type error: Argument 1 passed to App\\Utility\\Exceptions\\CustomException::report() must be an instance of Exception, none given, called in /var/www/html/bubbles/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php on line 102 at /var/www/html/bubbles/app/Utility/Exceptions/CustomException.php:39)
Here is the custom exception class I've been using
<?php
namespace App\Utility\Exceptions;
use Illuminate\Support\Facades\Lang;
use Exception;
class CustomException extends Exception
{
private $error_code = NULL;
private $error_info = NULL;
function __construct($type = NULL, $errors = NULL)
{
$this->error_code = $type['error_code'];
$this->error_info = $errors;
$message = Lang::get('exceptions.'.$this->error_code);
parent::__construct($message, $type['code'], NULL);
}
public function report(Exception $exception)
{
parent::report($exception);
}
public function getErrorCode()
{
return $this->error_code;
}
public function getErrorInfo()
{
return $this->error_info;
}
}
// end of class CustomException
// end of file CustomException.php
Could anybody will explain me why it is throwing argument must be instance of exception ? Any help would be greatly appreciated.
My programming environment
PHP 7.0.1
Laravel 5.5
The exception handler in Laravel 5.5 checks if the exception has a report method, and if so, let the exception handle the reporting itself. This means that the handler will see your report method, and call $e->report();, but your report method requires a parameter.
This is done in Handler::report.
You either need to remove the parameter in your report method (it should be reporting itself; $this) if you want to use this functionality, or rename the method if you don't want Laravel to call it (and fail).
Relevant: Laravel 5.5 Adds Support for Custom Exception Reporting

Laravel 5.3 Passing AuthorizationException a message when a Policy fails

I'm trying to find a clean way to override the AuthorizationException to take a dynamic string that can be passed back when a Policy fails.
Things I know I can do are:
Wrap the Policy in the Controller with a try-catch, then rethrow a custom exception that takes a specific string, which seems a bit verbose
abort(403, '...') in the Policy prior to returning, which seems a bit hacky since policies are already doing the work
and then in /Exceptions/Handler::render I can send back the response as JSON
Is there a nicer way to do this to get a message in the response of a policy failure? Or is 1 or 2 my best choices.
I noticed if you throw AuthorizationException($message) in a policy using Laravel's exception it jumps you out of the policy, but continues execution in the controller, and doesn't progress to Handler::render. Which I'm assuming this is them handling the exception somehow, but I couldn't find where they were doing it... so if anyone finds where this is happening I'd still like to know.
If you create your own AuthorizationException and throw it, it will stop execution as expected, and drop into Handler::render so I ended up adding this method to my policy:
use App\Exceptions\AuthorizationException;
// ... removed for brevity
private function throwExceptionIfNotPermitted(bool $hasPermission = false, bool $allowExceptions = false, $exceptionMessage = null): bool
{
// Only throw when a message is provided, or use the default
// behaviour provided by policies
if (!$hasPermission && $allowExceptions && !is_null($exceptionMessage)) {
throw new \App\Exceptions\AuthorizationException($exceptionMessage);
}
return $hasPermission;
}
New exception for throwing in policies only in \App\Exceptions:
namespace App\Exceptions;
use Exception;
/**
* The AuthorizationException class is used by policies where authorization has
* failed, and a message is required to indicate the type of failure.
* ---
* NOTE: For consistency and clarity with the framework the exception was named
* for the similarly named exception provided by Laravel that does not stop
* execution when thrown in a policy due to internal handling of the
* exception.
*/
class AuthorizationException extends Exception
{
private $statusCode = 403;
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
}
Handle the exception and provide the message in a JSON response in Handler::render():
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException && $request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage()
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
and I also removed it from being logged in Handler::report.
What I found was not "passing" a custom message to authorize, just defining a custom message in the policy it selfs, so, for example, if you have the method "canUseIt", in your UserPolicy, like the following:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
return false;
}
You can change it and do something like this:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
$this->deny('Sorry man, you are not Chuck Norris');
}
It uses the deny() method from the HandlesAuthorization trait.
Then when you use it like $this->authorize('canUseIt', $user) and it fails, it will return a 403 HTTP error code with the message "Sorry man, you are not Chuck Norris".
Laravel does have an option to pass arguments to customize the errors in the authorize() method of a Controller Class accessed through the Gate Class's implementation of the GateContract made available by the Gate Facade.
However, it seems that they forgot to pass those arguments to the allow()/deny() methods responsible for returning error messages, implemented in the HandlesAuthorization Trait.
You need to pass those arguments by following these steps:
Modify the authorize method in the vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php file
public function authorize($ability, $arguments = []) {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny($arguments);
}
Call authorize from the controller with an extra argument, ie: your custom $message -
$message = "You can not delete this comment!";
$response = $this->authorize('delete', $message);
I have made a pull request to fix this, hopefully someone will merge it soon.
I think the best way to think about Policies is they are simply a way to split controller logic, and move all authorization related logic to a separate file. Thus abort(403, 'message') is the right way to do this, in most cases.
The only downside is you may want some policies to be 'pure' logic for use in business logic only, and thus not to have any response control. They could be kept separate, and a commenting system could be used distinguish them.

Trying to create my first package, but getting undefined method callAction() exception

I'm trying to develop a package, so I've followed this tutorial until Creating a Facade section because I don't need a facade.
The problem is:
/app/routes.php
Route::get('test', 'Aristona\Installer\Installer#install');
throws an exception: Call to undefined method Aristona\Installer\Installer::callAction()
My Installer.php is like this:
workbench/aristona/installer/src/Aristona/Installer/Installer.php
<?php namespace Aristona\Installer;
class Installer
{
public static function install()
{
return "Hello";
}
}
The class is loading. I've added it to my service providers list. Also I can confirm it is loading by adding one more install method, because PHP throws a fatal error about redeclaring same method twice.
I've tried different combinations on my method prefixes (e.g without static) Doesn't solve.
Anyone know what am I doing wrong?
Your getting an error because you're trying to use routing to controller where none exists. To be more specific, Laravel is trying to perform this method from it's core Controller class:
/**
* Execute an action on the controller.
*
* #param string $method
* #param array $parameters
* #return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
$this->setupLayout();
$response = call_user_func_array(array($this, $method), $parameters);
// If no response is returned from the controller action and a layout is being
// used we will assume we want to just return the layout view as any nested
// views were probably bound on this view during this controller actions.
if (is_null($response) && ! is_null($this->layout))
{
$response = $this->layout;
}
return $response;
}
So unless the class you're specifying in Route::get() is extending either BaseController or Controller, this exception will be thrown. If you tested the same method inside a closure, it would work.
More about Laravel controller routing can be found here.
To fix this, you should either add a controller to your package or use the Installer class inside another controller.

Resources