Lumen 5.6 Error handling (throws exception twice) - php

This is my code to handle any error:
App\Exceptions\Handler::class
public function render($request, Exception $e)
{
$fe = \Symfony\Component\Debug\Exception\FlattenException::create($e);
$statusCode = $fe->getStatusCode();
$code = $fe->getCode();
$message = $fe->getMessage();
$errorObj = new \App\LOHDomain\Entities\Error($code, $message);
return response()->json(['data' => null, 'error' => $errorObj], $statusCode);
}
when I parse a fake WSDL URL to the SoapClient it throws two exceptions
{"data":null,"error":{"code":"0","message":"SOAP-ERROR: Parsing WSDL: Couldn't load from 'asdsd' : failed to load external entity \"asdsd\"\n"}}
{"data":null,"error":{"code":"1","message":"SOAP-ERROR: Parsing WSDL: Couldn't load from 'asdsd' : failed to load external entity \"asdsd\"\n"}}
So the json response became invalid
When commenting these line of codes in the vendor, it throws one exception:
Laravel\Lumen\Concerns\RegistersExceptionHandlers trait
protected function registerErrorHandling()
{
error_reporting(-1);
set_error_handler(function ($level, $message, $file = '', $line = 0) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
});
set_exception_handler(function ($e) {
$this->handleUncaughtException($e);
});
// register_shutdown_function(function () {
// $this->handleShutdown();
// });
}
So, What is the problem? and how to solve it without editing in the vendor?

The solution is to clear the last error, because it fired twice.
The error exception.
The second is the shutdown function.
So, the solution is:
App\Exceptions\Handler::class
public function render($request, Exception $e)
{
$fe = \Symfony\Component\Debug\Exception\FlattenException::create($e);
$statusCode = $fe->getStatusCode();
$code = $fe->getCode();
$message = $fe->getMessage();
$errorObj = new \App\Domain\Entities\ResponseEntites\Error($code, $message);
/**
* This line of code resolves the issue
*
* To reproduce the issue :
* 1) Comment this following line of code
* 2) Provide a fake WSDL URL to the SoapClient
*
* Recommendation: Remove this line if you aren't using the SoapClient
*/
error_clear_last();
return new \Illuminate\Http\JsonResponse(['data' => null, 'error' => $errorObj], $statusCode);
}
This isn't the best solution (but this is the best solution, that I tried in my case).
If you have a better tested solution please share it.
links:
Fatal exceptions are handled twice
Code change

Related

Fatal Error on using $exception variable in render() Method in App\Exceptions\Handler.php

I am new to Laravel and have an Issue regarding the Handler.php File.
I am trying to create a class that takes an exceptions and transforms it into a JSON Response.
Sadly though, upon calling the constructor a series of Errors are thrown:
(ErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorErrorSymfony\Component\ErrorHandler\Error\FatalError)
My code:
render() in Handler.php:
public function render($request, Throwable $exception)
{
$errorResource = new ErrorResource($exception);
return $errorResource->getJsonResponse();
}
class ErrorResource in ErrorResource.php:
<?php
namespace Transformers;
use Throwable;
class ErrorResource
{
private $exception;
private $defaultCodes = [TypeError::class => 400];
private $defaultMessages = [TypeError::class => 'Untgültige URL Parameter'];
function __construct(Throwable $exception)
{
$this->exception = $exception;
}
public function getJsonResponse($exception)
{
$codeToThrow = 500;
$messageToThrow = "Internal Server Error";
$type = get_class($this->exception);
if (empty($exception->getCode())) {
$codeToThrow = $this->defaultCodes[$type];
} else {
$codeToThrow = $exception->getCode();
}
if (empty($exception->getMessage())) {
$messageToThrow = $this->defaultMessages[$type];
} else {
$messageToThrow = $exception->getMessage();
}
return response()->json(array(
'Type' => $type,
'Message' => $messageToThrow
), $codeToThrow);
}
}
I have also tried to move the method getJsonResponse() to the Handler.php file and call it from there, but without any luck.
I am really confused as to why I am not allowed to do certain things with the $exception variable (I have also tried to create a clone of this object - but the same error occures)
I hope you can help me resolving this issue,
Greetins,
Franz
The issue is, that PHP is call by value. That is why it is implicitely trying to clone an unclonable object -> Error. To resolve this issue one can use wrapper objects, but I decided to simply use call by reference (https://www.javatpoint.com/php-call-by-reference)

How to change PHPUnit error messages to show custom errors

I have extended PHP Exception with my own to add additional data:
class MyException extends Exception {
public $foo = [];
public function __construct($message = '', $data = null, $code = 0) {
$this->foo = $data;
paret::__construct($message, $code);
}
}
When doing normal requests these errors logs correctly and I do not want to add any additional content to $this->message.
When running test I can throw it:
if (!$this->save()) {
throw new MyException('Internal Server Error', ['log' => 'missing data'], 500);
}
and PHPUnit will output:
MyException: Internal Server Error
I want:
MyExeption: Internal Server Error; {"log": "missing data"}
How to extend PHPUnit to be able to show $myException->foo along with error message?
Sample code:
<?php
class SampleTest extends CTestCase
{
public function testIndex()
{
$this->assertTrue($this->save());
}
protected function save()
{
$model = new Orders();
$model->addError('id', 'ID is Required');
if (!$model->validate()) {
throw new HttpApiModelException(500, 'Failed to save', $model);
}
return true;
}
}
Executed with command common/vendor/bin/phpunit --configuration tests/phpunit.xml --verbose tests/functional/SampleTest.php
And output:
I am not sure if this is the best option but you could implement test results printer, smth like:
<?php
namespace Tests;
use PHPUnit\TextUI\ResultPrinter;
class TestPrinter extends ResultPrinter
{
protected function printDefect(\PHPUnit\Framework\TestFailure $defect, $count)
{
$this->printDefectHeader($defect, $count);
$ex = $defect->thrownException();
// you can do whatever you need here,
// like check exception type, etc,
// printing just line number here
$this->write('Line #' . $ex->getLine() . "\n");
$this->printDefectTrace($defect);
}
}
and register as a printer to be used (assume xml config but could be done via command line as well):
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
printerClass="Tests\TestPrinter"
>
<!-- note printerClass attribute above -->
</phpunit>
Doing so, you would get output looking similar with:
There was 1 error:
1) Tests\SomeTest::testStuff
Line #16
LogicException: whatever
(I just made a simple test doing throw new \LogicException('whatever');)
So if you need this data to be printed everytime when you are running tests but not when it's on production then why not to extend the base Exception class and to check if you are under test environment then to concatenate the message and the data. And then all of your custom exceptions to extend this new Exception class.
class BaseException extends Exception {
public function __construct($message = '', $data = null, $code = 0) {
if (env('ENVIRONMENT') === 'test') {
$message .= ' ' . json_encode($data);
}
paret::__construct($message, $code);
}
}
But this implementation will require to change your MyException class to call the parent constructor with this data.
Instead of this
paret::__construct($message, $code);
Now you are going to have this
paret::__construct($message, $data, $code);
And also to extend the new BaseException class to these exception which you want to have this functionality

Why does PHPUnit "receives" an Error object instead of an Exception object

When I test a constructor that throws an exception using expectExceptionMessage('exception message') the test fails and the actual message text is prefixed with "Class 'namespace\Class_Name'".
It also states that an exception of type Error was thrown instead of type Exception as expected.
Am I throwing my exception incorrectly?
I was able to make the test pass by leaving out the expectExceptionMessage function and enclosing the code in a try catch statement.
Which doesn't make sense to me.
Using the annotations
/**
* #expectedException Exception
* #expectedExceptionMessage no_file.json not found
*/
Also didn't solve the problem.
PHPUnit test code:
public function testNonExistingFileInConstructor() {
$this->expectException( 'Exception' );
$this->expectExceptionMessage( 'no_file.json not found');
new Json_File('no_file.json');
}
Code throwing the exception:
public function __construct( $path_to_file ) {
$json_file = file_get_contents( $path_to_file );
if( false === $json_file ) {
throw new Exception( $json_file . ' not found', 500 );
}
$data = json_decode( $json_file, true );
if ( is_null( $data) ) {
throw new Exception( 'invalid json in ' . $json_file, 600 );
}
$this->data = $data;
}
Do not use annotations to expect exceptions.
Always use fully qualified class names with expectException(), for instance $this->expectException(YourNamespace\Exception::class); instead of $this->expectException('Exception');. 'Exception' is just a string that cannot be resolved to the correct, fully qualified name of the exception you're expecting.

how to get exception code in laravel5.5?

I want to get the exception code in Hanlder.php . But i get the code , it is 0.
the code follow list :
public function render($request, Exception $exception)
{
if ($exception->getCode() >= 500) {
return response()->json(['error' => 'server error!'], '500');
}
return parent::render($request, $exception);
}
I find the code in the exception , the default value of code is 0.
public function __construct($message = "", $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, $previous) { }
how can I get the code of exception?
And i am sorry ,my native language is chinese.
Thank you!
You need to check if the exception is an instance of HttpException
// If this exception is an instance of HttpException
if ($this->isHttpException($e)) {
// Grab the HTTP status code from the Exception
$status = $e->getStatusCode();
}

error log truncated in laravel 5.3

i have this entry in my laravel 5.3 log
2016-12-22 17:23:37] local.ERROR:
GuzzleHttp\Exception\ClientException: Client error: POST
https://api.sparkpost.com/api/v1/transmissions resulted in a 400 Bad
Request response: { "errors": [ { "message": "Message generation
rejected", "description": "recipient address suppressed due to
customer p (truncated...)
why does it truncate an important error message? now i cannot figure out what is going wrong...
Because your request throws a Guzzle Exception, as a workaround, instead of calling $e->getMessage(), You can simply try:
$e->getResponse()->getBody()->getContents();
If you don't want to modify the report() method.
Worked nice for me.
The truncating is done by the Guzzle library. It only shows the first 120 characters of the response. I am assuming this is because responses could potentially be very long.
If you would like to see the full message, you should be able to customize how guzzle exceptions are handled.
Update the report() method in your app/Exceptions/Handler.php to something like:
public function report(Exception $exception)
{
// this is from the parent method
if ($this->shouldntReport($exception)) {
return;
}
// this is from the parent method
try {
$logger = $this->container->make(\Psr\Log\LoggerInterface::class);
} catch (Exception $ex) {
throw $exception; // throw the original exception
}
// this is the new custom handling of guzzle exceptions
if ($exception instanceof \GuzzleHttp\Exception\RequestException) {
// get the full text of the exception (including stack trace),
// and replace the original message (possibly truncated),
// with the full text of the entire response body.
$message = str_replace(
rtrim($exception->getMessage()),
(string) $exception->getResponse()->getBody(),
(string) $exception
);
// log your new custom guzzle error message
return $logger->error($message);
}
// make sure to still log non-guzzle exceptions
$logger->error($exception);
}
Note: this is done in the report method, so it only affects what is written to the log. If the exception is dumped to the terminal or to the browser, it will still show the truncated message.
as alternative solution:
hotfix RequestException.php
ta_integration/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php
replace
$size = $body->getSize();
$summary = $body->read(120);
$body->rewind();
if ($size > 120) {
with for example:
$size = $body->getSize();
$summary = $body->read(999);
$body->rewind();
if ($size > 999) {
function
getResponseBodySummary
Edit vendor/guzzlehttp/psr7/src/Message.php
public static function bodySummary(MessageInterface $message, $truncateAt = 999)
Edit vendor/guzzlehttp/psr7/src/functions.php
function get_message_body_summary(MessageInterface $message, $truncateAt = 999)
None of these solutions here helped me. I found a solution here that helped. By the user sidk2020. Here is his solution in case the link breaks:
I did something very adventurous. I modified guzzel's exception handler
guzzel on purpose only reads up 120 bytes of info and prints truncated next to it.
The file is located at : /vendor/guzzlehttp/guzzle/src/Exception/RequestException.php
So I modified that function and below is what my function looks like:
public static function getResponseBodySummary(ResponseInterface $response) {
$body = $response->getBody();
if (!$body->isSeekable() || !$body->isReadable()) {
return null;
}
$size = $body->getSize();
if ($size === 0) {
return null;
}
// Matches any printable character, including unicode characters:
// letters, marks, numbers, punctuation, spacing, and separators.
if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $body)) {
return null;
}
return $body;
}
In vendor/guzzlehttp/psr7/src/functions.php
there's this function:
function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
{
return Message::bodySummary($message, $truncateAt);
}
just change the $truncateAt = 120 to whatever you are confortable with

Categories