I'd like to able to catch an exception and continue with the execution of other subsequent functions (and possibly log an error in the catch section). In the code sample below, there are instances where $html->find doesn't find the element and returns error exception undefined offset. In such cases, the entire script fails. I don't want to specifically test for this error but rather any error that may occur within the code block in the try section.
public function parsePage1($provider)
{
$path = $this->getFile($provider);
$link = $this->links[$provider];
if (file_exists($path)) {
$string = file_get_contents($path);
$html = \HTMLDomParser::str_get_html($string);
$wrapper = $html->find('.classToLookFor')[0];
unset($string);
}
}
try {
$this->parsePage1('nameOfProvider');
} catch(Exception $e) {
// continue...
}
try {
$this->parsePage2('nameOfProvider');
} catch(Exception $e) {
// continue...
}
No, there is no way to make the code within the try block continue past an exception. An exception terminates the function just like a return would; there is no way to restore the state of the function afterwards.
Instead, avoid triggering the error in the first place:
$wrappers = $html->find('.classToLookFor'); # <-- no [0]!
if (count($wrappers)) {
$wrapper = $wrappers[0];
...
}
Just to be clear, the 'error' in this case was a notice. If your errorlevel does not include notices, which is typically the case in production, your code will continue past that point.
With that said, Notices and warnings are intended for developers to add checks for expected input, as in duskwuff's example.
Unfortunatley, duskwuff's answer is problematic with the most recent versions of php at 7.2+. This is because count() expects either an array or an object that implements countable.
With the newest version you will get a Warning:
Warning: count(): Parameter must be an array or an object that implements Countable in
You will be back where you were before using count() only. A simple fix for that is to add a check for is_array.
$wrappers = $html->find('.classToLookFor'); # <-- no [0]!
if (is_array($wrappers) && count($wrappers)) {
$wrapper = $wrappers[0];
...
}
I also want to point out, that per my original comment, the whole purpose of exception catching is to protect against program termination errors.
This was not a good example of the types of errors where you should apply try-catch, but to be clear, your original code does continue... just not within the try section of the code, but after the catch()
This simulation of your original problem illustrates that:
<?php
function findit($foo) {
return $foo[0];
}
try {
findit('');
} catch(Exception $e) {
var_dump($e);
}
echo 'Hey look we continued';
Output will be something like:
Notice: Uninitialized string offset: 0 in ... on line 4
Hey look we continued
I feel this needs to be added as a response because people in the future are going to probably find this question, which really has nothing much to do with try-catch handling, and really has to do with code that expects to work with an array, but might not get one.
Related
So the question is the same as in subject:
How to simulate and properly write test with error_get_last() function in phpunit
Sample code to write test to:
$lasterror = error_get_last();
if ($lasterror !== null) {
//do something
}
Thanks for asking this question, it was not clear to me at the very beginning where the problem resulted from writing a test for it. That is due to the nature of error and exception handling, especially in testing these things can be considered a bit harder and there are normally no ready-made "easy" solutions because at the end of the day you need to write the test yourself.
I hope this could be clarified in comments and the following can show how to approach them in a way.
NOTE: As we're writing test-code, I use assert() statements in the examples, and this is by intention as assert is a more pure language feature. It makes the examples more portable, too.
To make use of it, execute php having assertions in development mode and throwning (zend.assertions=1 and assert.exception=1) and run the example as it evolves and have later phpunit run with these settings as well.
You can certainly port this into a concrete test-case to implement it Phpunit specific, for the examples the concrete assertion library should not be of interest, but the assertions. They are expected in the examples to bring PHP to halt if they aren't fullfilled and this is how I wrote the examples (some people say those are expectations).
In general testing code that tests for the side-effect of there being an error:
$lasterror = error_get_last();
if ($lasterror !== null) {
//do something
}
and then in your test-suite you get not errors but exceptions:
#trigger_error('Oh no'); // ErrorException: Oh no
which will prevent further code to run (the code you want to put under test).
What to do now? Well, the naked error-trigger would make phpunit highlight you've got an ErrorException, what you can do is to assert the fixture-setup is correct (fixture is the "Oh no" error) and just declare it so.
What? Yes, we turn the obstacle in front of us into the good condition just by saying so:
try {
#trigger_error('Oh no');
} catch (ErrorException $good) {
assert('Oh no' === $good->getMessage(), get_class($good));
} finally {
assert(isset($good), 'an expected exception was thrown');
}
This example helps to better describe what is going on and what we know to deal with, however the original problem still remains: Due to some error handler turning errors into exceptions, error_get_last() returns NULL as there was no error, but an exception.
Execute this code. It will show it passes.
So what is to assert is the opposite. Let's do this, turn the good condition into this bad, bad failure by negating it:
try {
#trigger_error('Oh no');
} catch (ErrorException $fail) {
assert(false, sprintf('the "%s" exception must not be thrown', get_class($fail)));
} finally {
assert(!isset($fail), 'there must not have been any exception');
}
I hope you can follow so far. First flip.
Now the fixture will only be asserted properly set-up when the expected state is given, here asserting that no ErrorException is being thrown, as we learned that would be "bad" (ruin any test of code with the error_get_last() call).
This is normally what one would expect to be the first version. We actually have already done two test runs now. One green, one red.
As long as this code does not pass, we know the fixture was not set-up and hence there is no need to even run any further tests.
As you can see testing is as easy as programming. You write the test code as-if everything is already in order, pure magic. Even when it makes no sense at all (there is no test yet, right?!) and still this is what you want to achieve.
Why is something that simple so hard? One part might be that writing the code before the test can be irritating then when writing the test. The mind is filled with expectations about the code while it should be only concentrated on the test(-code).
Don't sweat, just don't get irritated that testing then does not work first of all, that would be the same otherwise, too. A good test starts with failing very soon at the beginning. And this is exactly where we are and what we did is just to turn condition red into green. Second flip.
In testing this is also called the Happy Path, that is where it goes happily ever after and the test just tests for that what when things are unspecifically just working and unspecifically just testing for it.
But this only to side-track you a bit.
So now let's make the fixture pass this happy-path (remove the existing error handler, set the error, restore the old handler, I know, you have read this up from the manual already, right? So I can spare the links, sorry for being lazy):
try {
set_error_handler(null); // <--- this
#trigger_error('Oh no');
restore_error_handler(); // <--- restore original runtime configuration
} catch (ErrorException $fail) {
assert(false, sprintf('the "%s" exception must not be thrown', get_class($fail)));
} finally {
assert(!isset($fail), 'there must not have been any exception');
}
Et voilá, the happy path is green!
But this is only the happy path. Good tests test for others paths, too. And rigorously.
Didn't we have one? A right, yes, the one with the first assertion that ErrorException were triggered, we have that test already. What once was good despite a failure is now good again (but despite the name, on the unhappy path). Third flip.
try {
set_error_handler(null);
#trigger_error('Oh no');
restore_error_handler();
try {
#trigger_error('Oh no');
} catch (ErrorException $good) {
assert('Oh no' === $good->getMessage(), get_class($good));
} finally {
assert(isset($good), 'an expected exception was thrown');
}
} catch (ErrorException $fail) {
assert(false, sprintf('the "%s" exception must not be thrown', get_class($fail)));
} finally {
assert(!isset($fail), 'there must not have been any exception');
}
Wow, now this certainly escalated as the story evolved. And it's not even complete yet. You've spotted the one or other problem with it, right?
The initial expectation that there is the ErrorException throwing error-handler on the happy path is missing. Let's put it on top as an early check (and unset the check variable afterwards as there is the re-use for the post-assertion).
And even more importantly, ensure error_get_last() returns NULL as otherwise the test could be easily tainted for the result:
try {
#trigger_error('Oh no');
} catch (ErrorException $good) {
assert('Oh no' === $good->getMessage(), get_class($good));
} finally {
assert(isset($good), 'an expected exception was thrown');
}
unset($good);
assert(NULL === error_get_last()); // <-- this easy to miss
try {
set_error_handler(null);
#trigger_error('Oh no');
restore_error_handler();
try {
#trigger_error('Oh no');
} catch (ErrorException $good) {
assert('Oh no' === $good->getMessage(), get_class($good));
} finally {
assert(isset($good), 'an expected exception was thrown');
}
} catch (ErrorException $fail) {
assert(false, sprintf('the "%s" exception must not be thrown', get_class($fail)));
} finally {
assert(!isset($fail), 'there must not have been any exception');
}
// continue here...
It is verbose but shows all the things that are done:
awareness that triggering an error alone does not work and why
the precondition that error_get_last(); returns NULL before setting an error
that there is an ErrorException error-handler that needs to get out of the way to trigger a ture PHP error
that this error handler is re-instated as likely the system under test needs it (or should be tested with it)
Thanks to assert() statements it documents its own expectations.
Wrap it in a function and give it a useful name and a short description for what it is good for. You can - but must not - safe-guard the assertions. I normally do that inside the Phpunit test-suite so that the expected runtime-configuration is not getting lost.
Here I come to an end which hopefully is another beginning for you.
This is only one way how you could approach such hard problems, and these problems are really hard to test because it's full of side-effects here. Not only would error_get_last() pose a problem for a straight forward handling.
Yes, error handling is hard, often forgotten or attacked with a "burn-it-with-fire" attiture. At the end of the day this is not helpful when you actually need to deal with it.
And by the definition of it, writing a test for that, is.
The full example incl. simulation of an error exception throwing error handler and some of the earlier flips on 3v4l.org: https://3v4l.org/8fopq
It should go without saying that turning all errors into exceptions is some of the stupidest idea some developers have fallen for. It is just another example that error handling is hard.
Errors and exceptions are a major source of complexity and bugs
If you want get know something talk with the another human.
Thank you a lot #hakre!
And compressed above answer:
protected function generateSafelySimulatedError(\Closure $errorGeneratingFn, string $generatedErrorMsg = null): void
{
try {
set_error_handler(null);
set_exception_handler(null);
$errorGeneratingFn();
restore_error_handler();
restore_exception_handler();
try {
$errorGeneratingFn();
} catch (\ErrorException $good) {
if ($generatedErrorMsg !== null) {
$this->assertTrue($generatedErrorMsg === $good->getMessage(), get_class($good));
}
} finally {
$this->assertTrue(isset($good), 'An expected exception was thrown.');
}
} catch (\ErrorException $fail) {
$this->assertTrue(false, sprintf('The "%s" exception must not be thrown.', get_class($fail)));
} finally {
$this->assertTrue(!isset($fail), 'There must not have been any exception.');
}
}
And sample usage:
public function testCriticalErrors(): void
{
$testObject = $this->getObjectUnderTest();//Here is creation of object and also here is set_error_handler() and other similar.
$this->generateSafelySimulatedError(function (): void {
#trigger_error('Oh no');
}, 'Oh no');
$testObject->criticalErrors();//tested method like:
/*
$lasterror = error_get_last();
if ($lasterror !== null) {
//do something
}
*/
//Below other assertions if needed.
$this->expectOutputString('<html class="error"><body><h1>500</h1>Error 500</body></html>');
}
I need some function that will accept a function as the parameter and will run it in try {} catch (Exception $e) {}. If it runs successfully, do nothing, otherwise, throw new Exception. That's something like function-checker, which checks functions if they ran successfully. Anybody can help me or give advice?
Thanks for any replies.
The function should work like that:
function itwillfail () {
echo 10 / 0;
}
check("itwillfail");
Output: Caught exception: Custom exception text here, because it has been thrown as custom.
("check" is that function I need)
What I tried:
function check($func) {
try {
call_user_func($func);
} catch (Exception $e) {
throw new Exception("Custom text here.");
}
}
EDIT: More explained: I need to create function, which do the same as "try" and a lot of different "catch"es for different types of exceptions.
Summarizing your question:
You want a way to call a custom function from a string variable (which you have already figured out that would be via call_user_func($var);.
You then want that function to throw a custom exception error
Confused
What is not clear is the reason you would opt to not define your error handler using the set_error_handler function which would effectively do what your asking and set a switch statement to output different messages based on the error generated.
Example
The following example is not using a call_user_func but it effectively allows you to write how the error will be handled
<?php
function myerror($error_no, $error_msg) {
echo "Error: [$error_no] $error_msg ";
echo "\n Now Script will end";
die();
}
// Setting set_error_handler
set_error_handler("myerror");
$a = 10;
$b = 0;
// Force the error
echo($a / $b);
?>
Not every function throws an exception when they fail. Many functions, especially ones that have been around for a long time, simply trigger PHP errors rather than exceptions.
To handle those, you would use a custom error handler:
https://www.php.net/manual/en/function.set-error-handler.php
So you could set up a custom error handler that would intercept those kinds of failures and throw them as exceptions. The whole point of that function is to do what you're trying to do - handle errors in a custom way.
I adapted a web script I wrote to fit my needs for some data I need to retrieve from a server. I run this script from a terminal, so error messages are useful information.
The main part of the code is a loop inside a loop, and in that loop I call a function. In this function there's a call to a database. If there is a problem with connecting to the database, I can catch that error with a simple try {} catch(){}, but how should I structure my code so that I can just skip this iteration and move to the next item in the loop? In other words, do a continue only from within a function.
Below is how I would do it, but I am not sure this is the correct way.
foreach ($flavours as $icecream) {
foreach ($sauces as $sauce) {
$amount = dessertServings($icecream, $sauce);
if ($amount != null) {
// Some other functions like orderDessert, makePricingList and so on
fwrite(STDOUT, "$amount servings of $icecream with $sauce remaining!\n");
}
}
}
dessertServings($icecream, $sauce) {
try {
$dbConnection = new Connection("user", "password", "db$icecream$sauce");
$amountOfServings = $dbConnection->query($icecream, $sauce);
return $amountOfServings;
}
// E.g database connection could not be made
catch(Exception $e) {
fwrite(STDERR, $e->getMessage() . "\n");
return;
}
}
Is there a better way to do this?
To make things harder, what if the function doesn't actually return anything and thus isn't assigning a value to a variable? How should you deal with that?
foreach ($flavours as $icecream) {
foreach ($sauces as $sauce) {
prepareDessert($icecream, $sauce);
// Other functions, most importantly: eatDessert($dessert)
}
}
prepareDessert($icecream, $sauce) {
try {
$dbConnection = new Connection("user", "password", "db$icecream$sauce");
$dbConnection->query($icecream, $sauce)->do("prepare");
}
// E.g database connection could not be made
catch(Exception $e) {
fwrite(STDERR, $e->getMessage() . "\n");
}
}
In such a case, how do I make sure that when the try fails, the block in the loop never reaches the other functions, we can't eat an ice cream that wasn't prepared in the first place!
Would I use an empty variable that simply returns true on success and false and fail, and execute the following code in the main block only on true? Or is there a better convention for this in PHP?
how should I structure my code so that I can just skip this iteration and move to the next item in the loop
The first rule of exception handling: do not do exception handling. Allow it to bubble up and catch it when you need it (in your case, in your loop).
You can also re-throw an exception in your catch, if you want to do some processing within the function (like print to STDERR), but let it bubble up!
The other more traditional method is to have your function return some kind of error code - the most basic being "true" on success, "false" or "null" on failure like in your first example. I don't see anything wrong with that.
To make things harder, what if the function doesn't actually return anything and thus isn't assigning a value to a variable? How should you deal with that?
Throw exceptions, that's their job!
how do I make sure that when the try fails, the block in the loop never reaches the other functions
Don't try/catch within the function or re-throw an exception from within your "catch".
Would I use an empty variable that simply returns true on success and false and fail, and execute the following code in the main block only on true? Or is there a better convention for this in PHP?
Yes there is a better convention : throw exceptions.
I have three text files containing the same set of error messages in three languages: English, French and German.
I need to extend the exception class so that when something goes wrong, the own exception object will be thrown, such as "throw new My-Exception("English", 4) - then 4th message in English file will be shown.
This is what I made so far:
<?php
class My_Exception extends Exception {
function __construct($lang, $errcode) {
$this->lang = $lang;
$this->errcode = $errcode;
}
function getMessageMap() {
$errors = file('errfiles/'.$this->lang.'.txt');
foreach($errors as $error) {
list($key,$value) = implode(',', $errors);
$errorArray[$key] = $value;
}
return $errorArray[$this->errcode];
}
}
try { throw new My_Exception('english', 3); }
catch (My_Exception $e) { echo $e->getMessageMap(); }
?>
This doesn't work properly. I will appreciate any help.
Instead of throwing specific exception messages for each language, I would stick to one normal exception. Then, in your application when you catch those errors, you can show an error page for specific languages. In other words, don't overhaul your application architecture (even in this minor way) for what is ultimately a font-end problem.
You mention that the error message looks like this:
file(errfiles/English.txt): failed to open stream
So try using the full path of the error file...
$errors = file('C:/xampp/htdocs/HW4/errfiles/'.$this->lang.'.txt');
Also can you confirm that the file really does start with a capital "E"?
The second error:
Invalid argument supplied for foreach() in
C:\xampp\htdocs\HW4\index.php on line 13
Is because of the first one. Fix that and all is well.
I think you have implode() and explode() mixed up. Assuming your errors.txt file looks like:
1,I am error one
2,I am error two
You want each $errors as $error to thusly explode(',', '1,I am error one'); as:
array("1", "I am error 1")
And yes, the "1" is a string until you specifically cast it as an int using either (int)$errno or intval($errno).
PHP is 'loosely typed' and usually does this in the background so you don't have to worry about it, but you should worry about it. Otherwise you'll run into the occasional situations where your code does very strange things.
I've been wondering why would I use Exceptions in my PHP. Let's take a look at a simple example:
class Worker
{
public function goToWork()
{
return $isInThatMood ?
// Okay, I'll do it.
true :
// In your dreams...
false;
}
}
$worker = new Worker;
if (!$worker->goToWork())
{
if (date('l',time()) == 'Sunday')
echo "Fine, you don't have to work on Sundays...";
else
echo "Get your a** back to work!";
}
else
echo "Good.";
Is there a reason for me to use Exceptions for that kind of code? Why? How would the code be built?
And what about code that may produce errors:
class FileOutputter
{
public function outputFile($file)
{
if (!file_exists($file))
return false;
return file_get_contents($file);
}
}
Why would I use Exceptions in the above case? I have a feeling that Exceptions help you to recognize the type of the problem, which occured, true?
So, am I using Exceptions appropriately in this code:
class FileOutputter
{
public function outputFile($file)
{
if (!file_exists($file))
return throw new Exception("File not found.",123);
try
{
$contents = file_get_contents($file);
}
catch (Exception $e)
{
return $e;
}
return $contents;
}
}
Or is that poor? Now the underlying code can do this:
$fo = new FileOutputter;
try
{
$fo->outputFile("File.extension");
}
catch (Exception $e)
{
// Something happened, we could either display the error/problem directly
echo $e->getMessage();
// Or use the info to make alternative execution flows
if ($e->getCode() == 123) // The one we specified earlier
// Do something else now, create "an exception"
}
Or am I completely lost here?
When should I use an exception?
You use an exception to indicate an exceptional condition; that is, something which prevents a method from fulfilling its contract, and which shouldn't have occurred at that level.
For example, you might have a method, Record::save(), which saves changes to a record into a database. If, for some reason, this can't be done (e.g. a database error occurs, or a data constraint is broken), then you could throw an exception to indicate failure.
How do I create custom exceptions?
Exceptions are usually named such that they indicate the nature of the error, for example, DatabaseException. You can subclass Exception to create custom-named exceptions in this manner, e.g.
class DatabaseException extends Exception {}
(Of course, you could take advantage of inheritance to give this exception some additional diagnostic information, such as connection details or a database error code, for example.)
When shouldn't I use an exception?
Consider a further example; a method which checks for file existence. This should probably not throw an exception if the file doesn't exist, since the purpose of the method was to perform said check. However, a method which opens a file and performs some processing could throw an exception, since the file was expected to exist, etc.
Initially, it's not always clear when something is and isn't exceptional. Like most things, experience will teach you, over time, when you should and shouldn't throw an exception.
Why use exceptions instead of returning special error codes, etc?
The useful thing about exceptions is that they immediately leap out of the current method and head up the call stack until they're caught and handled, which means you can move error-handling logic higher up, although ideally, not too high.
By using a clear mechanism to deal with failure cases, you automatically kick off the error handling code when something bad happens, which means you can avoid dealing with all sorts of magic sentinel values which have to be checked, or worse, a global error flag to distinguish between a bunch of different possibilities.
I'm not a PHP programmer, but this looks similar to C#.
Typically you'd want to throw errors if it is a point of no return. Then you would be able to log something to show that the impossible happened.
If you can tell that the file doesn't exist then you could just say that. No real need in my mind to also throw an exception.
Now if the file was found and you are processing it, and say only half the file was uploaded and you had no way of telling that without an exception, then it'd be nice to have around.
I would say it's a good design practice to avoid the catch all exceptions "catch (Exception $e)" and design by contract instead, just like you seem to be doing in the prior example. I would at first be more specific with the type of exception being thrown and then work from there if needed. Otherwise, stay away from the try->catch and exceptions.
In no way can you assume that a file exists just because you called file_exists()! The function file_exists doesn't open the file, so the file can potentially be deleted or renamed or moved at any time!
class FileOutputter
{
public function outputFile($file)
{
if (!file_exists($file))
return false;
///<--- file may be deleted right here without you knowing it
return file_get_contents($file);//<-- then this will throw an error
//if you don't catch it, you're program may halt.
}
}
I believe this it better:
class FileOutputter
{
public function outputFile($file)
{
try{
if (!file_exists($file))
return false;
return file_get_contents($file);
}catch(Exception e){
check_what_went_wrong_and_go_to_plan_B();
}
}
}
(Edit: And it's probably even better to actually try to open the file from the beginning. If you succeed then you have a 'lock' on the file and it won't just disappear. If not, then catch the exception and see what went wrong.
Then again, you might feel that this level of reduncancy is just silly. In that case I don't think you need to worry about try/catch at all :)
Just a note really, your code for file outputter is invalid, since file_get_contents($file)
does not throw an exception. It will however raise a warning if the file doesn't exist, or can't be accessed for some reason. Additionally, you are returning an exception from outputFile, when you should probably be just letting the error propagate up the call stack.
However, you can register the error handler in php to throw exceptions when an error occurs:
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
That way, any standard function calls that cause an error will throw an exception.
So, I would change FileOutputter to this (with that snippet above added):
class FileOutputter
{
public function outputFile($file)
{
if (!file_exists($file))
throw new Exception("File not found.",123);
return file_get_contents($file);
}
}
The calling code is then basically the same.
Basically rule is not to return an Exception when an error occurs - you can catch it if you want, and throw a custom exception, but let the exception go up the call stack until you want to use it.