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
Related
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)
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
I want to test a public method which calls a method from different class with methods as the arguments.
public function __construct(LeadData $leadData,LeadRepository $leadRepository){....}
public function proceedtosave($leads,$payout,$received)
{
try {
//save the lead
$this->savedLead = $this->leadRepository->leadCreate($leads,$payout,$received);
$this->responseData['lead_id'] = $this->savedLead->id;
} catch (QueryException $exception) {
$errorCode = $exception->errorInfo[1];
Log::info(['Lead insert error code: '.$errorCode,'Error message: '.$exception->getMessage()]);
if ($this->validator->errorCodeChecker($errorCode) === false) {
$this->leadDuplicateRepository->leadDuplicateCreate($this->leads, $payout, $received);
return $this->validator->getErrors();
}
}
}
This is how I wrote the test
/**
* #test
*
*/
public function save_leads_to_leads_table_if_not_duplicate()
{
$this->getLeadsForValidator();
$leadData = $this->getMock('App\Helpers\..\..Data');
$leadIn = $this->getMockbuilder('App\Helpers\Repositories\..Interface')
->setMethods(array('leadCreate'))
->getMock();
$leadIn->expects($this->once())
->method('leadCreate')
->will($this->returnValue(1));
$leadIn->leadCreate($this->results,1,2);
$this->SUT = new LeadStore($leadData,$leadIn);
$this->SUT->proceedtosave($this->results,1,2);
}
I updated my original question here since I realized that I have to refactor my code. How would you pass this test?I got the error which I am trying to solve
1) StoreLeadsTest::save_leads_to_leads_table_if_not_duplicate
.....::leadCreate(Array (...), 1, 2) was not expected to be called more than once.
/home/vagrant/Code/l.../../LeadStore.php:87
/home/vagrant/Code/./../StoreLeadsTest.php:46
And the lines of code
$this->savedLead = $this->leadRepository->leadCreate($leads,$payout,$received);//LeadStore.php:87
$this->SUT->proceedtosave($this->results,1,2);//StoreLeadsTest.php:46
Swapping Mockery expectations from
->expects($this->once())
To
->expects($this->any())
Will result ErrorException
ErrorException: Trying to get property of non-object
That point to this line of code
$this->responseData['lead_id'] = $this->savedLead->id;
If I remove that line above, my test pass, how to make phpunit skip that line so that I can continue to test the other lines?
I am testing an Custom Exception class LoggedException responsible for logging the message.
But its not working as expected, my directory structure for the log file is logs/exceptions.log
I have implemented certain checks for the existence of file and even the check for the implementation of error_log(), which tells the file is written but when I open exceptions.log there is nothing in there and the message I want to write is which thrown.
class LoggedException extends exception{
public function __construct($message,$code=0,$file='logs/exceptions.log')
{
if(file_exists($file)){
if(error_log($this->getMessage(),0,$file)){
echo "<br/>File Wirtten error message: ".$this->getMessage();
} else {
echo"<br/>cannot write";
}
} else {
echo "</br>No such file there";
}
}
}
class someClass{
private $prop="on";
public function checkState($device){
if(($this->prop)=="on"){
throw new LoggedException("The $device is ON");
}
else{
echo ("THE $device IS OFF");
}
}
}
$bulb=new SomeClass();
try{
$bulb->checkState("bulb");
}
catch(LoggedException $e){
echo "Exception message: ".$e->getMessage();
}
Browser Display:
exceptions.log:(also not complete)
Check the manual for the proper way to extend a class Extending Exceptions
First:
You are missing a parameter on your class constructor
public function __construct($message,$code=0,$file='logs/exceptions.log')
The third parameter should be Exception $previous = null like this:-
public function __construct($message, $code = 0,
Exception $previous = null,
$file='./logs/exceptions.log')
Second:
you do not call parent::__construct($message, $code, $previous); before doing your own code in the __construct method of your class.
So the Exception class is not initialised properly, but you attempt to use its properties, which of course are not yet loaded.
Third:
with the option set to 0 in the error_log() function call, the message will go to the standard PHP error log and not the one you intended.
So change 0 to 3 and it will go to the file you specify in param 3.
Fourth:
You need to add your own Newlines manually to the messages written to the log.
Change your code to
class LoggedException extends Exception{
public function __construct($message, $code = 0, Exception $previous = null, $file='./logs/exceptions.log')
{
parent::__construct($message, $code, $previous);
if(file_exists($file)){
if(error_log($this->getMessage() . PHP_EOL, 3, $file)){
echo "<br/>File Wirtten error message: ".$this->getMessage().PHP_EOL;
} else {
echo"<br/>cannot write";
}
} else {
echo "</br>No such file there";
}
}
}
And with any luck it will do roughly what you intended.
I need catch libxml errors. But I want to use my class for this. I know about libxml_get_errors and other function. But I need something like libxml_set_erroc_class("myclass") and in all case for error will call my class. I don't want in each case after use $dom->load(...) create some construction like foreach(libxml_get_errors as $error) {....}. Can you help me?
libxml errors are mostly generated when reading or writing xml document because automatic validation is done.
So this is where you should concentrate and you don't need to overwrite the set_error_handler .. Here is a prove of concept
Use Internal Errors
libxml_use_internal_errors ( true );
Sample XML
$xmlstr = <<< XML
<?xml version='1.0' standalone='yes'?>
<movies>
<movie>
<titles>PHP: Behind the Parser</title>
</movie>
</movies>
XML;
echo "<pre>" ;
I guess this is an example of the kind of what you want to achieve
try {
$loader = new XmlLoader ( $xmlstr );
} catch ( XmlException $e ) {
echo $e->getMessage();
}
XMLLoader Class
class XmlLoader {
private $xml;
private $doc;
function __construct($xmlstr) {
$doc = simplexml_load_string ( $xmlstr );
if (! $doc) {
throw new XmlException ( libxml_get_errors () );
}
}
}
XmlException Class
class XmlException extends Exception {
private $errorMessage = "";
function __construct(Array $errors) {
$x = 0;
foreach ( $errors as $error ) {
if ($error instanceof LibXMLError) {
$this->parseError ( $error );
$x ++;
}
}
if ($x > 0) {
parent::__construct ( $this->errorMessage );
} else {
parent::__construct ( "Unknown Error XmlException" );
}
}
function parseError(LibXMLError $error) {
switch ($error->level) {
case LIBXML_ERR_WARNING :
$this->errorMessage .= "Warning $error->code: ";
break;
case LIBXML_ERR_ERROR :
$this->errorMessage .= "Error $error->code: ";
break;
case LIBXML_ERR_FATAL :
$this->errorMessage .= "Fatal Error $error->code: ";
break;
}
$this->errorMessage .= trim ( $error->message ) . "\n Line: $error->line" . "\n Column: $error->column";
if ($error->file) {
$this->errorMessage .= "\n File: $error->file";
}
}
}
Sample Output
Fatal Error 76: Opening and ending tag mismatch: titles line 4 and title
Line: 4
Column: 46
I hope this helps
Thanks
There is no facility to do this directly. Your options would be:
extend the PHP class that uses libxml and wrap your custom error handling logic around the stock implementation (not that good), or
write your own class that aggregates an instance of that PHP class and create your own public interface around it (better, because you are in control of the public interface and you don't risk problems if the PHP class is extended in the future), or
replace the global error handler for the duration of your parsing and restore it afterwards (not as powerful, may be problematic if your code calls into other code, however easier to do)
Solutions 1 and 2 have the advantage that they do not modify the behavior of any other code in your application no matter what.
edit (confused exceptions with errors):
Use set_exception_handler to catch global exceptions.
Have your code throw these exceptions in cases like $dom->load(). Because libxml doesn't seem to throw exceptions on its own, your other option is to create a wrapper around it, use the wrapper in your code and have it check libxml for errors and throw them in necessary cases.
Handle the exceptions inside "myclass".
Be wary though that the set_exception_handler will be catching all your exceptions.
Here's an example of what you could do:
//inheritance example (composition, though, would be better)
class MyDOMWrapper extends DOMDocument{
public function load($filename, $options = 0){
$bool = parent::load($filename, $options);
if (!$bool){
throw new MyDOMException('Shit happens. Feeling lucky.', 777);
}
}
}
class MyDOMException extends DOMException{
public $libxml;
public function __construct($message, $code){
parent::__construct($message, $code);
$this->libxml = libxml_get_errors();
}
}
class MyDOMExceptionHandler{
public static function handle($e){
//handle exception globally
}
}
libxml_use_internal_errors(true);
set_exception_handler(array('MyDOMErrorHandler', 'handle'));
//global exception handler
$myDom = new MyDOMWrapper();
$myDom->load('main.xml');
//when special handling is necessary
try {
$myDom = new MyDOMWrapper();
$myDom->load('main.xml');
} catch (MyDOMException $e) {
//handle or
throw $e; //rethrow
}