Exception error undefined function

Learn you some Erlang Errors and Exceptions Not so fast! There's no right place for a chapter like this one. By now, you've learned enough that you're

Learn you some Erlang

Errors and Exceptions

Not so fast!

There’s no right place for a chapter like this one. By now, you’ve learned enough that you’re probably running into errors, but not yet enough to know how to handle them. In fact we won’t be able to see all the error-handling mechanisms within this chapter. That’s a bit because Erlang has two main paradigms: functional and concurrent. The functional subset is the one I’ve been explaining since the beginning of the book: referential transparency, recursion, higher order functions, etc. The concurrent subset is the one that makes Erlang famous: actors, thousands and thousands of concurrent processes, supervision trees, etc.

Because I judge the functional part essential to know before moving on to the concurrent part, I’ll only cover the functional subset of the language in this chapter. If we are to manage errors, we must first understand them.

Note: Although Erlang includes a few ways to handle errors in functional code, most of the time you’ll be told to just let it crash. I hinted at this in the Introduction. The mechanisms that let you program this way are in the concurrent part of the language.

A Compilation of Errors

There are many kinds of errors: compile-time errors, logical errors, run-time errors and generated errors. I’ll focus on compile-time errors in this section and go through the others in the next sections.

Compile-time errors are often syntactic mistakes: check your function names, the tokens in the language (brackets, parentheses, periods, commas), the arity of your functions, etc. Here’s a list of some of the common compile-time error messages and potential resolutions in case you encounter them:

module.beam: Module name ‘madule’ does not match file name ‘module’ The module name you’ve entered in the -module attribute doesn’t match the filename. ./module.erl:2: Warning: function some_function/0 is unused You have not exported a function, or the place where it’s used has the wrong name or arity. It’s also possible that you’ve written a function that is no longer needed. Check your code! ./module.erl:2: function some_function/1 undefined The function does not exist. You’ve written the wrong name or arity either in the -export attribute or when declaring the function. This error is also output when the given function could not be compiled, usually because of a syntax error like forgetting to end a function with a period. ./module.erl:5: syntax error before: ‘SomeCharacterOrWord’ This happens for a variety of reason, namely unclosed parentheses, tuples or wrong expression termination (like closing the last branch of a case with a comma). Other reasons might include the use of a reserved atom in your code or unicode characters getting weirdly converted between different encodings (I’ve seen it happen!) ./module.erl:5: syntax error before: All right, that one is certainly not as descriptive! This usually comes up when your line termination is not correct. This is a specific case of the previous error, so just keep an eye out. ./module.erl:5: Warning: this expression will fail with a ‘badarith’ exception Erlang is all about dynamic typing, but remember that the types are strong. In this case, the compiler is smart enough to find that one of your arithmetic expressions will fail (say, llama + 5 ). It won’t find type errors much more complex than that, though. ./module.erl:5: Warning: variable ‘Var’ is unused You declared a variable and never use it afterwards. This might be a bug with your code, so double-check what you have written. Otherwise, you might want to switch the variable name to _ or just prefix it with an underscore (something like _Var ) if you feel the name helps make the code readable. ./module.erl:5: Warning: a term is constructed, but never used In one of your functions, you’re doing something such as building a list, declaring a tuple or an anonymous function without ever binding it to a variable or returning it. This warning tells you you’re doing something useless or that you have made some mistake. ./module.erl:5: head mismatch It’s possible your function has more than one head, and each of them has a different arity. Don’t forget that different arity means different functions, and you can’t interleave function declarations that way. This error is also raised when you insert a function definition between the head clauses of another function. ./module.erl:5: Warning: this clause cannot match because a previous clause at line 4 always matches A function defined in the module has a specific clause defined after a catch-all one. As such, the compiler can warn you that you’ll never even need to go to the other branch. ./module.erl:9: variable ‘A’ unsafe in ‘case’ (line 5) You’re using a variable declared within one of the branches of a case . of outside of it. This is considered unsafe. If you want to use such variables, you’d be better of doing MyVar = case . of .

This should cover most errors you get at compile-time at this point. There aren’t too many and most of the time the hardest part is finding which error caused a huge cascade of errors listed against other functions. It is better to resolve compiler errors in the order they were reported to avoid being misled by errors which may not actually be errors at all. Other kinds of errors sometimes appear and if you’ve got one I haven’t included, send me an email and I’ll add it along with an explanation as soon as possible.

No, YOUR logic is wrong!

Logical errors are the hardest kind of errors to find and debug. They’re most likely errors coming from the programmer: branches of conditional statements such as ‘if’s and ‘case’s that don’t consider all the cases, mixing up a multiplication for a division, etc. They do not make your programs crash but just end up giving you unseen bad data or having your program work in an unintended manner.

You’re most likely on your own when it comes to this, but Erlang has many facilities to help you there, including test frameworks, TypEr and Dialyzer (as described in the types chapter), a debugger and tracing module, etc. Testing your code is likely your best defense. Sadly, there are enough of these kinds of errors in every programmer’s career to write a few dozen books about so I’ll avoid spending too much time here. It’s easier to focus on those that make your programs crash, because it happens right there and won’t bubble up 50 levels from now. Note that this is pretty much the origin of the ‘let it crash’ ideal I mentioned a few times already.

Run-time Errors

Run-time errors are pretty destructive in the sense that they crash your code. While Erlang has ways to deal with them, recognizing these errors is always helpful. As such, I’ve made a little list of common run-time errors with an explanation and example code that could generate them.

function_clause All the guard clauses of a function failed, or none of the function clauses’ patterns matched. case_clause Looks like someone has forgotten a specific pattern in their case , sent in the wrong kind of data, or needed a catch-all clause! if_clause This is pretty similar to case_clause errors: it can not find a branch that evaluates to true . Ensuring you consider all cases or add the catch-all true clause might be what you need. badmatch Badmatch errors happen whenever pattern matching fails. This most likely means you’re trying to do impossible pattern matches (such as above), trying to bind a variable for the second time, or just anything that isn’t equal on both sides of the = operator (which is pretty much what makes rebinding a variable fail!). Note that this error sometimes happens because the programmer believes that a variable of the form _MyVar is the same as _ . Variables with an underscore are normal variables, except the compiler won’t complain if they’re not used. It is not possible to bind them more than once. badarg This one is really similar to function_clause as it’s about calling functions with incorrect arguments. The main difference here is that this error is usually triggered by the programmer after validating the arguments from within the function, outside of the guard clauses. I’ll show how to throw such errors later in this chapter. undef This happens when you call a function that doesn’t exist. Make sure the function is exported from the module with the right arity (if you’re calling it from outside the module) and double check that you did type the name of the function and the name of the module correctly. Another reason to get the message is when the module is not in Erlang’s search path. By default, Erlang’s search path is set to be in the current directory. You can add paths by using code:add_patha/1 or code:add_pathz/1 . If this still doesn’t work, make sure you compiled the module to begin with! badarith This happens when you try to do arithmetic that doesn’t exist, like divisions by zero or between atoms and numbers. badfun The most frequent reason why this error occurs is when you use variables as functions, but the variable’s value is not a function. In the example above, I’m using the hhfuns function from the previous chapter and using two atoms as functions. This doesn’t work and badfun is thrown. badarity The badarity error is a specific case of badfun : it happens when you use higher order functions, but you pass them more (or fewer) arguments than they can handle. system_limit There are many reasons why a system_limit error can be thrown: too many processes (we’ll get there), atoms that are too long, too many arguments in a function, number of atoms too large, too many nodes connected, etc. To get a full list in details, read the Erlang Efficiency Guide on system limits. Note that some of these errors are serious enough to crash the whole VM.

Raising Exceptions

In trying to monitor the execution of code and protect against logical errors, it’s often a good idea to provoke run-time crashes so problems will be spotted early.

There are three kinds of exceptions in Erlang: errors, throws and exits. They all have different uses (kind of):

Errors

Calling erlang:error(Reason) will end the execution in the current process and include a stack trace of the last functions called with their arguments when you catch it. These are the kind of exceptions that provoke the run-time errors above.

Errors are the means for a function to stop its execution when you can’t expect the calling code to handle what just happened. If you get an if_clause error, what can you do? Change the code and recompile, that’s what you can do (other than just displaying a pretty error message). An example of when not to use errors could be our tree module from the recursion chapter. That module might not always be able to find a specific key in a tree when doing a lookup. In this case, it makes sense to expect the user to deal with unknown results: they could use a default value, check to insert a new one, delete the tree, etc. This is when it’s appropriate to return a tuple of the form or an atom like undefined rather than raising errors.

Now, errors aren’t limited to the examples above. You can define your own kind of errors too:

Here, custom_error is not recognized by the Erlang shell and it has no custom translation such as «bad argument in . «, but it’s usable in the same way and can be handled by the programmer in an identical manner (we’ll see how to do that soon).

Exits

There are two kinds of exits: ‘internal’ exits and ‘external’ exits. Internal exits are triggered by calling the function exit/1 and make the current process stop its execution. External exits are called with exit/2 and have to do with multiple processes in the concurrent aspect of Erlang; as such, we’ll mainly focus on internal exits and will visit the external kind later on.

Internal exits are pretty similar to errors. In fact, historically speaking, they were the same and only exit/1 existed. They’ve got roughly the same use cases. So how to choose one? Well the choice is not obvious. To understand when to use one or the other, there’s no choice but to start looking at the concepts of actors and processes from far away.

In the introduction, I’ve compared processes as people communicating by mail. There’s not a lot to add to the analogy, so I’ll go to diagrams and bubbles.

Processes here can send each other messages. A process can also listen for messages, wait for them. You can also choose what messages to listen to, discard some, ignore others, give up listening after a certain time etc.

These basic concepts let the implementors of Erlang use a special kind of message to communicate exceptions between processes. They act a bit like a process’ last breath; they’re sent right before a process dies and the code it contains stops executing. Other processes that were listening for that specific kind of message can then know about the event and do whatever they please with it. This includes logging, restarting the process that died, etc.

With this concept explained, the difference in using erlang:error/1 and exit/1 is easier to understand. While both can be used in an extremely similar manner, the real difference is in the intent. You can then decide whether what you’ve got is ‘simply’ an error or a condition worthy of killing the current process. This point is made stronger by the fact that erlang:error/1 returns a stack trace and exit/1 doesn’t. If you were to have a pretty large stack trace or lots of arguments to the current function, copying the exit message to every listening process would mean copying the data. In some cases, this could become unpractical.

Throws

A throw is a class of exceptions used for cases that the programmer can be expected to handle. In comparison with exits and errors, they don’t really carry any ‘crash that process!’ intent behind them, but rather control flow. As you use throws while expecting the programmer to handle them, it’s usually a good idea to document their use within a module using them.

The syntax to throw an exception is:

Where you can replace permission_denied by anything you want (including ‘everything is fine’ , but that is not helpful and you will lose friends).

Throws can also be used for non-local returns when in deep recursion. An example of that is the ssl module which uses throw/1 as a way to push tuples back to a top-level function. This function then simply returns that tuple to the user. This lets the implementer only write for the successful cases and have one function deal with the exceptions on top of it all.

Another example could be the array module, where there is a lookup function that can return a user-supplied default value if it can’t find the element needed. When the element can’t be found, the value default is thrown as an exception, and the top-level function handles that and substitutes it with the user-supplied default value. This keeps the programmer of the module from needing to pass the default value as a parameter of every function of the lookup algorithm, again focusing only on the successful cases.

As a rule of thumb, try to limit the use of your throws for non-local returns to a single module in order to make it easier to debug your code. It will also let you change the innards of your module without requiring changes in its interface.

Dealing with Exceptions

I’ve already mentioned quite a few times that throws, errors and exits can be handled. The way to do this is by using a try . catch expression.

A try . catch is a way to evaluate an expression while letting you handle the successful case as well as the errors encountered. The general syntax for such an expression is:

The Expression in between try and of is said to be protected. This means that any kind of exception happening within that call will be caught. The patterns and expressions in between the try . of and catch behave in exactly the same manner as a case . of . Finally, the catch part: here, you can replace TypeOfError by either error , throw or exit , for each respective type we’ve seen in this chapter. If no type is provided, a throw is assumed. So let’s put this in practice.

First of all, let’s start a module named exceptions . We’re going for simple here:

We can compile it and try it with different kinds of exceptions:

As you can see, this try . catch is only receiving throws. As stated earlier, this is because when no type is mentioned, a throw is assumed. Then we have functions with catch clauses of each type:

And to try them:

The next example on the menu shows how to combine all the types of exceptions in a single try . catch . We’ll first declare a function to generate all the exceptions we need:

Here is_function/2 is a BIF which makes sure the variable Attack is a function of arity 0. Then we add this one for good measure:

And now for something completely different :

The expression on line 9 demonstrates normal behavior for the black knight, when function execution happens normally. Each line that follows that one demonstrates pattern matching on exceptions according to their class (throw, error, exit) and the reason associated with them ( slice , cut_arm , cut_leg ).

One thing shown here on expressions 13 and 14 is a catch-all clause for exceptions. The _:_ pattern is what you need to use to make sure to catch any exception of any type. In practice, you should be careful when using the catch-all patterns: try to protect your code from what you can handle, but not any more than that. Erlang has other facilities in place to take care of the rest.

There’s also an additional clause that can be added after a try . catch that will always be executed. This is equivalent to the ‘finally’ block in many other languages:

No matter if there are errors or not, the expressions inside the after part are guaranteed to run. However, you can not get any return value out of the after construct. Therefore, after is mostly used to run code with side effects. The canonical use of this is when you want to make sure a file you were reading gets closed whether exceptions are raised or not.

We now know how to handle the 3 classes of exceptions in Erlang with catch blocks. However, I’ve hidden information from you: it’s actually possible to have more than one expression between the try and the of !

By calling exceptions:whoa() , we’ll get the obvious , because of throw(up) . So yeah, it’s possible to have more than one expression between try and of .

What I just highlighted in exceptions:whoa/0 and that you might have not noticed is that when we use many expressions in that manner, we might not always care about what the return value is. The of part thus becomes a bit useless. Well good news, you can just give it up:

And now it’s a bit leaner!

Note: It is important to know that the protected part of an exception can’t be tail recursive. The VM must always keep a reference there in case there’s an exception popping up.

Because the try . catch construct without the of part has nothing but a protected part, calling a recursive function from there might be dangerous for programs supposed to run for a long time (which is Erlang’s niche). After enough iterations, you’ll go out of memory or your program will get slower without really knowing why. By putting your recursive calls between the of and catch , you are not in a protected part and you will benefit from Last Call Optimisation.

Some people use try . of . catch rather than try . catch by default to avoid unexpected errors of that kind, except for obviously non-recursive code with results that won’t be used by anything. You’re most likely able to make your own decision on what to do!

Wait, there’s more!

As if it wasn’t enough to be on par with most languages already, Erlang’s got yet another error handling structure. That structure is defined as the keyword catch and basically captures all types of exceptions on top of the good results. It’s a bit of a weird one because it displays a different representation of exceptions:

What we can see from this is that throws remain the same, but that exits and errors are both represented as <‘EXIT’, Reason>. That’s due to errors being bolted to the language after exits (they kept a similar representation for backwards compatibility).

The way to read this stack trace is as follows:

  • The type of error is undef , which means the function you called is not defined (see the list at the beginning of this chapter)
  • The list right after the type of error is a stack trace
  • The tuple on top of the stack trace represents the last function to be called ( ). That’s your undefined function.
  • The tuples after that are the functions called before the error. This time they’re of the form .
  • That’s all there is to it, really.

You can also manually get a stack trace by calling erlang:get_stacktrace/0 in the process that crashed.

You’ll often see catch written in the following manner (we’re still in exceptions.erl):

And as expected:

This sounds compact and easy to catch exceptions, but there are a few problems with catch . The first of it is operator precedence:

That’s not exactly intuitive given that most expressions do not need to be wrapped in parentheses this way. Another problem with catch is that you can’t see the difference between what looks like the underlying representation of an exception and a real exception:

And you can’t know the difference between an error and an actual exit. You could also have used throw/1 to generate the above exception. In fact, a throw/1 in a catch might also be problematic in another scenario:

And now the killer problem:

Because we’re behind a catch , we can never know if the function threw an exception or if it returned an actual value! This might not really happen a whole lot in practice, but it’s still a wart big enough to have warranted the addition of the try . catch construct in the R10B release.

Try a try in a tree

To put exceptions in practice, we’ll do a little exercise requiring us to dig for our tree module. We’re going to add a function that lets us do a lookup in the tree to find out whether a value is already present in there or not. Because the tree is ordered by its keys and in this case we do not care about the keys, we’ll need to traverse the whole thing until we find the value.

The traversal of the tree will be roughly similar to what we did in tree:lookup/2 , except this time we will always search down both the left branch and the right branch. To write the function, you’ll just need to remember that a tree node is either > or when empty. With this in hand, we can write a basic implementation without exceptions:

The problem with this implementation is that every node of the tree we branch at has to test for the result of the previous branch:

This is a bit annoying. With the help of throws, we can make something that will require less comparisons:

The execution of the code above is similar to the previous version, except that we never need to check for the return value: we don’t care about it at all. In this version, only a throw means the value was found. When this happens, the tree evaluation stops and it falls back to the catch on top. Otherwise, the execution keeps going until the last false is returned and that’s what the user sees:

Of course, the implementation above is longer than the previous one. However, it is possible to realize gains in speed and in clarity by using non-local returns with a throw, depending on the operations you’re doing. The current example is a simple comparison and there’s not much to see, but the practice still makes sense with more complex data structures and operations.

That being said, we’re probably ready to solve real problems in sequential Erlang.

Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License

Источник

Not so fast!

A green man with a huge head and tiny body on a bicycle

There’s no right place for a chapter like this one. By now, you’ve learned enough that you’re probably running into errors, but not yet enough to know how to handle them. In fact we won’t be able to see all the error-handling mechanisms within this chapter. That’s a bit because Erlang has two main paradigms: functional and concurrent. The functional subset is the one I’ve been explaining since the beginning of the book: referential transparency, recursion, higher order functions, etc. The concurrent subset is the one that makes Erlang famous: actors, thousands and thousands of concurrent processes, supervision trees, etc.

Because I judge the functional part essential to know before moving on to the concurrent part, I’ll only cover the functional subset of the language in this chapter. If we are to manage errors, we must first understand them.

Note: Although Erlang includes a few ways to handle errors in functional code, most of the time you’ll be told to just let it crash. I hinted at this in the Introduction. The mechanisms that let you program this way are in the concurrent part of the language.

A Compilation of Errors

There are many kinds of errors: compile-time errors, logical errors, run-time errors and generated errors. I’ll focus on compile-time errors in this section and go through the others in the next sections.

Compile-time errors are often syntactic mistakes: check your function names, the tokens in the language (brackets, parentheses, periods, commas), the arity of your functions, etc. Here’s a list of some of the common compile-time error messages and potential resolutions in case you encounter them:

module.beam: Module name ‘madule’ does not match file name ‘module’
The module name you’ve entered in the -module attribute doesn’t match the filename.
./module.erl:2: Warning: function some_function/0 is unused
You have not exported a function, or the place where it’s used has the wrong name or arity. It’s also possible that you’ve written a function that is no longer needed. Check your code!
./module.erl:2: function some_function/1 undefined
The function does not exist. You’ve written the wrong name or arity either in the -export attribute or when declaring the function. This error is also output when the given function could not be compiled, usually because of a syntax error like forgetting to end a function with a period.
./module.erl:5: syntax error before: ‘SomeCharacterOrWord’
This happens for a variety of reason, namely unclosed parentheses, tuples or wrong expression termination (like closing the last branch of a case with a comma). Other reasons might include the use of a reserved atom in your code or unicode characters getting weirdly converted between different encodings (I’ve seen it happen!)
./module.erl:5: syntax error before:
All right, that one is certainly not as descriptive! This usually comes up when your line termination is not correct. This is a specific case of the previous error, so just keep an eye out.
./module.erl:5: Warning: this expression will fail with a ‘badarith’ exception
Erlang is all about dynamic typing, but remember that the types are strong. In this case, the compiler is smart enough to find that one of your arithmetic expressions will fail (say, llama + 5). It won’t find type errors much more complex than that, though.
./module.erl:5: Warning: variable ‘Var’ is unused
You declared a variable and never use it afterwards. This might be a bug with your code, so double-check what you have written. Otherwise, you might want to switch the variable name to _ or just prefix it with an underscore (something like _Var) if you feel the name helps make the code readable.
./module.erl:5: Warning: a term is constructed, but never used
In one of your functions, you’re doing something such as building a list, declaring a tuple or an anonymous function without ever binding it to a variable or returning it. This warning tells you you’re doing something useless or that you have made some mistake.
./module.erl:5: head mismatch
It’s possible your function has more than one head, and each of them has a different arity. Don’t forget that different arity means different functions, and you can’t interleave function declarations that way. This error is also raised when you insert a function definition between the head clauses of another function.
./module.erl:5: Warning: this clause cannot match because a previous clause at line 4 always matches
A function defined in the module has a specific clause defined after a catch-all one. As such, the compiler can warn you that you’ll never even need to go to the other branch.
./module.erl:9: variable ‘A’ unsafe in ‘case’ (line 5)
You’re using a variable declared within one of the branches of a case ... of outside of it. This is considered unsafe. If you want to use such variables, you’d be better of doing MyVar = case ... of

This should cover most errors you get at compile-time at this point. There aren’t too many and most of the time the hardest part is finding which error caused a huge cascade of errors listed against other functions.
It is better to resolve compiler errors in the order they were reported to avoid being misled by errors which may not actually be errors at all. Other kinds of errors sometimes appear and if you’ve got one I haven’t included, send me an email and I’ll add it along with an explanation as soon as possible.

No, YOUR logic is wrong!

An exam with the grade 'F'

Logical errors are the hardest kind of errors to find and debug. They’re most likely errors coming from the programmer: branches of conditional statements such as ‘if’s and ‘case’s that don’t consider all the cases, mixing up a multiplication for a division, etc. They do not make your programs crash but just end up giving you unseen bad data or having your program work in an unintended manner.

You’re most likely on your own when it comes to this, but Erlang has many facilities to help you there, including test frameworks, TypEr and Dialyzer (as described in the types chapter), a debugger and tracing module, etc. Testing your code is likely your best defense. Sadly, there are enough of these kinds of errors in every programmer’s career to write a few dozen books about so I’ll avoid spending too much time here. It’s easier to focus on those that make your programs crash, because it happens right there and won’t bubble up 50 levels from now. Note that this is pretty much the origin of the ‘let it crash’ ideal I mentioned a few times already.

Run-time Errors

Run-time errors are pretty destructive in the sense that they crash your code. While Erlang has ways to deal with them, recognizing these errors is always helpful. As such, I’ve made a little list of common run-time errors with an explanation and example code that could generate them.

function_clause
1> lists:sort([3,2,1]). 
[1,2,3]
2> lists:sort(fffffff). 
** exception error: no function clause matching lists:sort(fffffff)
        
All the guard clauses of a function failed, or none of the function clauses’ patterns matched.
case_clause
3> case "Unexpected Value" of 
3>    expected_value -> ok;
3>    other_expected_value -> 'also ok'
3> end.
** exception error: no case clause matching "Unexpected Value"
        
Looks like someone has forgotten a specific pattern in their case, sent in the wrong kind of data, or needed a catch-all clause!
if_clause
4> if 2 > 4 -> ok;
4>    0 > 1 -> ok
4> end.
** exception error: no true branch found when evaluating an if expression
        
This is pretty similar to case_clause errors: it can not find a branch that evaluates to true. Ensuring you consider all cases or add the catch-all true clause might be what you need.
badmatch
5> [X,Y] = {4,5}.
** exception error: no match of right hand side value {4,5}
        
Badmatch errors happen whenever pattern matching fails. This most likely means you’re trying to do impossible pattern matches (such as above), trying to bind a variable for the second time, or just anything that isn’t equal on both sides of the = operator (which is pretty much what makes rebinding a variable fail!). Note that this error sometimes happens because the programmer believes that a variable of the form _MyVar is the same as _. Variables with an underscore are normal variables, except the compiler won’t complain if they’re not used. It is not possible to bind them more than once.
badarg
6> erlang:binary_to_list("heh, already a list").
** exception error: bad argument
     in function  binary_to_list/1
        called as binary_to_list("heh, already a list")
        
This one is really similar to function_clause as it’s about calling functions with incorrect arguments. The main difference here is that this error is usually triggered by the programmer after validating the arguments from within the function, outside of the guard clauses. I’ll show how to throw such errors later in this chapter.
undef
7> lists:random([1,2,3]).
** exception error: undefined function lists:random/1
        
This happens when you call a function that doesn’t exist. Make sure the function is exported from the module with the right arity (if you’re calling it from outside the module) and double check that you did type the name of the function and the name of the module correctly. Another reason to get the message is when the module is not in Erlang’s search path. By default, Erlang’s search path is set to be in the current directory. You can add paths by using code:add_patha/1 or code:add_pathz/1. If this still doesn’t work, make sure you compiled the module to begin with!
badarith
8> 5 + llama.
** exception error: bad argument in an arithmetic expression
     in operator  +/2
        called as 5 + llama
        
This happens when you try to do arithmetic that doesn’t exist, like divisions by zero or between atoms and numbers.
badfun
9> hhfuns:add(one,two).
** exception error: bad function one
in function  hhfuns:add/2
        
The most frequent reason why this error occurs is when you use variables as functions, but the variable’s value is not a function. In the example above, I’m using the hhfuns function from the previous chapter and using two atoms as functions. This doesn’t work and badfun is thrown.
badarity
10> F = fun(_) -> ok end.
#Fun<erl_eval.6.13229925>
11> F(a,b).
** exception error: interpreted function with arity 1 called with two arguments
        
The badarity error is a specific case of badfun: it happens when you use higher order functions, but you pass them more (or fewer) arguments than they can handle.
system_limit
There are many reasons why a system_limit error can be thrown: too many processes (we’ll get there), atoms that are too long, too many arguments in a function, number of atoms too large, too many nodes connected, etc. To get a full list in details, read the Erlang Efficiency Guide on system limits. Note that some of these errors are serious enough to crash the whole VM.

Raising Exceptions

A stop sign

In trying to monitor the execution of code and protect against logical errors, it’s often a good idea to provoke run-time crashes so problems will be spotted early.

There are three kinds of exceptions in Erlang: errors, throws and exits. They all have different uses (kind of):

Errors

Calling erlang:error(Reason) will end the execution in the current process and include a stack trace of the last functions called with their arguments when you catch it. These are the kind of exceptions that provoke the run-time errors above.

Errors are the means for a function to stop its execution when you can’t expect the calling code to handle what just happened. If you get an if_clause error, what can you do? Change the code and recompile, that’s what you can do (other than just displaying a pretty error message). An example of when not to use errors could be our tree module from the recursion chapter. That module might not always be able to find a specific key in a tree when doing a lookup. In this case, it makes sense to expect the user to deal with unknown results: they could use a default value, check to insert a new one, delete the tree, etc. This is when it’s appropriate to return a tuple of the form {ok, Value} or an atom like undefined rather than raising errors.

Now, errors aren’t limited to the examples above. You can define your own kind of errors too:

1> erlang:error(badarith).
** exception error: bad argument in an arithmetic expression
2> erlang:error(custom_error).
** exception error: custom_error

Here, custom_error is not recognized by the Erlang shell and it has no custom translation such as «bad argument in …», but it’s usable in the same way and can be handled by the programmer in an identical manner (we’ll see how to do that soon).

Exits

There are two kinds of exits: ‘internal’ exits and ‘external’ exits. Internal exits are triggered by calling the function exit/1 and make the current process stop its execution. External exits are called with exit/2 and have to do with multiple processes in the concurrent aspect of Erlang; as such, we’ll mainly focus on internal exits and will visit the external kind later on.

Internal exits are pretty similar to errors. In fact, historically speaking, they were the same and only exit/1 existed. They’ve got roughly the same use cases. So how to choose one? Well the choice is not obvious. To understand when to use one or the other, there’s no choice but to start looking at the concepts of actors and processes from far away.

In the introduction, I’ve compared processes as people communicating by mail. There’s not a lot to add to the analogy, so I’ll go to diagrams and bubbles.

A process 'A' represented by a circle, sending a message (represented by an arrow) to a process 'B' (another circle)

Processes here can send each other messages. A process can also listen for messages, wait for them. You can also choose what messages to listen to, discard some, ignore others, give up listening after a certain time etc.

A process 'A' sending 'hello' to a process 'B', which in turns messages C with 'A says hello!'

These basic concepts let the implementors of Erlang use a special kind of message to communicate exceptions between processes. They act a bit like a process’ last breath; they’re sent right before a process dies and the code it contains stops executing. Other processes that were listening for that specific kind of message can then know about the event and do whatever they please with it. This includes logging, restarting the process that died, etc.

A dead process (a bursting bubble) sending 'I'm dead' to a process 'B'

With this concept explained, the difference in using erlang:error/1 and exit/1 is easier to understand. While both can be used in an extremely similar manner, the real difference is in the intent. You can then decide whether what you’ve got is ‘simply’ an error or a condition worthy of killing the current process. This point is made stronger by the fact that erlang:error/1 returns a stack trace and exit/1 doesn’t. If you were to have a pretty large stack trace or lots of arguments to the current function, copying the exit message to every listening process would mean copying the data. In some cases, this could become unpractical.

Throws

A throw is a class of exceptions used for cases that the programmer can be expected to handle. In comparison with exits and errors, they don’t really carry any ‘crash that process!’ intent behind them, but rather control flow. As you use throws while expecting the programmer to handle them, it’s usually a good idea to document their use within a module using them.

The syntax to throw an exception is:

1> throw(permission_denied).
** exception throw: permission_denied

Where you can replace permission_denied by anything you want (including 'everything is fine', but that is not helpful and you will lose friends).

Throws can also be used for non-local returns when in deep recursion. An example of that is the ssl module which uses throw/1 as a way to push {error, Reason} tuples back to a top-level function. This function then simply returns that tuple to the user. This lets the implementer only write for the successful cases and have one function deal with the exceptions on top of it all.

Another example could be the array module, where there is a lookup function that can return a user-supplied default value if it can’t find the element needed. When the element can’t be found, the value default is thrown as an exception, and the top-level function handles that and substitutes it with the user-supplied default value. This keeps the programmer of the module from needing to pass the default value as a parameter of every function of the lookup algorithm, again focusing only on the successful cases.

A fish that was caught

As a rule of thumb, try to limit the use of your throws for non-local returns to a single module in order to make it easier to debug your code. It will also let you change the innards of your module without requiring changes in its interface.

Dealing with Exceptions

I’ve already mentioned quite a few times that throws, errors and exits can be handled. The way to do this is by using a try ... catch expression.

A try ... catch is a way to evaluate an expression while letting you handle the successful case as well as the errors encountered. The general syntax for such an expression is:

try Expression of
    SuccessfulPattern1 [Guards] ->
        Expression1;
    SuccessfulPattern2 [Guards] ->
        Expression2
catch
    TypeOfError:ExceptionPattern1 ->
        Expression3;
    TypeOfError:ExceptionPattern2 ->
        Expression4
end.

The Expression in between try and of is said to be protected. This means that any kind of exception happening within that call will be caught. The patterns and expressions in between the try ... of and catch behave in exactly the same manner as a case ... of. Finally, the catch part: here, you can replace TypeOfError by either error, throw or exit, for each respective type we’ve seen in this chapter. If no type is provided, a throw is assumed. So let’s put this in practice.

First of all, let’s start a module named exceptions. We’re going for simple here:

-module(exceptions).
-compile(export_all).

throws(F) ->
    try F() of
        _ -> ok
    catch
        Throw -> {throw, caught, Throw}
    end.

We can compile it and try it with different kinds of exceptions:

1> c(exceptions).
{ok,exceptions}
2> exceptions:throws(fun() -> throw(thrown) end).
{throw,caught,thrown}
3> exceptions:throws(fun() -> erlang:error(pang) end).
** exception error: pang

As you can see, this try ... catch is only receiving throws. As stated earlier, this is because when no type is mentioned, a throw is assumed. Then we have functions with catch clauses of each type:

errors(F) ->
    try F() of
        _ -> ok
    catch
        error:Error -> {error, caught, Error}
    end.

exits(F) ->
    try F() of
        _ -> ok
    catch
        exit:Exit -> {exit, caught, Exit}
    end.

And to try them:

4> c(exceptions).
{ok,exceptions}
5> exceptions:errors(fun() -> erlang:error("Die!") end).
{error,caught,"Die!"}
6> exceptions:exits(fun() -> exit(goodbye) end).
{exit,caught,goodbye}

The next example on the menu shows how to combine all the types of exceptions in a single try ... catch. We’ll first declare a function to generate all the exceptions we need:

sword(1) -> throw(slice);
sword(2) -> erlang:error(cut_arm);
sword(3) -> exit(cut_leg);
sword(4) -> throw(punch);
sword(5) -> exit(cross_bridge).

black_knight(Attack) when is_function(Attack, 0) ->
    try Attack() of
        _ -> "None shall pass."
    catch
        throw:slice -> "It is but a scratch.";
        error:cut_arm -> "I've had worse.";
        exit:cut_leg -> "Come on you pansy!";
        _:_ -> "Just a flesh wound."
    end.

Here is_function/2 is a BIF which makes sure the variable Attack is a function of arity 0. Then we add this one for good measure:

talk() -> "blah blah".

And now for something completely different:

7> c(exceptions).
{ok,exceptions}
8> exceptions:talk().
"blah blah"
9> exceptions:black_knight(fun exceptions:talk/0).
"None shall pass."
10> exceptions:black_knight(fun() -> exceptions:sword(1) end).
"It is but a scratch."
11> exceptions:black_knight(fun() -> exceptions:sword(2) end).
"I've had worse."
12> exceptions:black_knight(fun() -> exceptions:sword(3) end).
"Come on you pansy!"
13> exceptions:black_knight(fun() -> exceptions:sword(4) end).
"Just a flesh wound."
14> exceptions:black_knight(fun() -> exceptions:sword(5) end).
"Just a flesh wound."

Monty Python's black knight

The expression on line 9 demonstrates normal behavior for the black knight, when function execution happens normally. Each line that follows that one demonstrates pattern matching on exceptions according to their class (throw, error, exit) and the reason associated with them (slice, cut_arm, cut_leg).

One thing shown here on expressions 13 and 14 is a catch-all clause for exceptions. The _:_ pattern is what you need to use to make sure to catch any exception of any type. In practice, you should be careful when using the catch-all patterns: try to protect your code from what you can handle, but not any more than that. Erlang has other facilities in place to take care of the rest.

There’s also an additional clause that can be added after a try ... catch that will always be executed. This is equivalent to the ‘finally’ block in many other languages:

try Expr of
    Pattern -> Expr1
catch
    Type:Exception -> Expr2
after % this always gets executed
    Expr3
end

No matter if there are errors or not, the expressions inside the after part are guaranteed to run. However, you can not get any return value out of the after construct. Therefore, after is mostly used to run code with side effects. The canonical use of this is when you want to make sure a file you were reading gets closed whether exceptions are raised or not.

We now know how to handle the 3 classes of exceptions in Erlang with catch blocks. However, I’ve hidden information from you: it’s actually possible to have more than one expression between the try and the of!

whoa() ->
    try
        talk(),
        _Knight = "None shall Pass!",
        _Doubles = [N*2 || N <- lists:seq(1,100)],
        throw(up),
        _WillReturnThis = tequila
    of
        tequila -> "hey this worked!"
    catch
        Exception:Reason -> {caught, Exception, Reason}
    end.

By calling exceptions:whoa(), we’ll get the obvious {caught, throw, up}, because of throw(up). So yeah, it’s possible to have more than one expression between try and of

What I just highlighted in exceptions:whoa/0 and that you might have not noticed is that when we use many expressions in that manner, we might not always care about what the return value is. The of part thus becomes a bit useless. Well good news, you can just give it up:

im_impressed() ->
    try
        talk(),
        _Knight = "None shall Pass!",
        _Doubles = [N*2 || N <- lists:seq(1,100)],
        throw(up),
        _WillReturnThis = tequila
    catch
        Exception:Reason -> {caught, Exception, Reason}
    end.

And now it’s a bit leaner!

Note: It is important to know that the protected part of an exception can’t be tail recursive. The VM must always keep a reference there in case there’s an exception popping up.

Because the try ... catch construct without the of part has nothing but a protected part, calling a recursive function from there might be dangerous for programs supposed to run for a long time (which is Erlang’s niche). After enough iterations, you’ll go out of memory or your program will get slower without really knowing why. By putting your recursive calls between the of and catch, you are not in a protected part and you will benefit from Last Call Optimisation.

Some people use try ... of ... catch rather than try ... catch by default to avoid unexpected errors of that kind, except for obviously non-recursive code with results that won’t be used by anything. You’re most likely able to make your own decision on what to do!

Wait, there’s more!

As if it wasn’t enough to be on par with most languages already, Erlang’s got yet another error handling structure. That structure is defined as the keyword catch and basically captures all types of exceptions on top of the good results. It’s a bit of a weird one because it displays a different representation of exceptions:

1> catch throw(whoa).
whoa
2> catch exit(die).
{'EXIT',die}
3> catch 1/0.
{'EXIT',{badarith,[{erlang,'/',[1,0]},
                   {erl_eval,do_apply,5},
                   {erl_eval,expr,5},
                   {shell,exprs,6},
                   {shell,eval_exprs,6},
                   {shell,eval_loop,3}]}}
4> catch 2+2.
4

What we can see from this is that throws remain the same, but that exits and errors are both represented as {'EXIT', Reason}. That’s due to errors being bolted to the language after exits (they kept a similar representation for backwards compatibility).

The way to read this stack trace is as follows:

5> catch doesnt:exist(a,4).              
{'EXIT',{undef,[{doesnt,exist,[a,4]},
                {erl_eval,do_apply,5},
                {erl_eval,expr,5},
                {shell,exprs,6},
                {shell,eval_exprs,6},
                {shell,eval_loop,3}]}}
  • The type of error is undef, which means the function you called is not defined (see the list at the beginning of this chapter)
  • The list right after the type of error is a stack trace
  • The tuple on top of the stack trace represents the last function to be called ({Module, Function, Arguments}). That’s your undefined function.
  • The tuples after that are the functions called before the error. This time they’re of the form {Module, Function, Arity}.
  • That’s all there is to it, really.

You can also manually get a stack trace by calling erlang:get_stacktrace/0 in the process that crashed.

You’ll often see catch written in the following manner (we’re still in exceptions.erl):

catcher(X,Y) ->
    case catch X/Y of
        {'EXIT', {badarith,_}} -> "uh oh";
        N -> N
    end.

And as expected:

6> c(exceptions).
{ok,exceptions}
7> exceptions:catcher(3,3).
1.0
8> exceptions:catcher(6,3).
2.0
9> exceptions:catcher(6,0).
"uh oh"

This sounds compact and easy to catch exceptions, but there are a few problems with catch. The first of it is operator precedence:

10> X = catch 4+2.
* 1: syntax error before: 'catch'
10> X = (catch 4+2).
6

That’s not exactly intuitive given that most expressions do not need to be wrapped in parentheses this way. Another problem with catch is that you can’t see the difference between what looks like the underlying representation of an exception and a real exception:

11> catch erlang:boat().
{'EXIT',{undef,[{erlang,boat,[]},
                {erl_eval,do_apply,5},
                {erl_eval,expr,5},
                {shell,exprs,6},
                {shell,eval_exprs,6},
                {shell,eval_loop,3}]}}
12> catch exit({undef, [{erlang,boat,[]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}). 
{'EXIT',{undef,[{erlang,boat,[]},
                {erl_eval,do_apply,5},
                {erl_eval,expr,5},
                {shell,exprs,6},
                {shell,eval_exprs,6},
                {shell,eval_loop,3}]}}

And you can’t know the difference between an error and an actual exit. You could also have used throw/1 to generate the above exception. In fact, a throw/1 in a catch might also be problematic in another scenario:

one_or_two(1) -> return;
one_or_two(2) -> throw(return).

And now the killer problem:

13> c(exceptions).
{ok,exceptions}
14> catch exceptions:one_or_two(1).
return
15> catch exceptions:one_or_two(2).
return

Because we’re behind a catch, we can never know if the function threw an exception or if it returned an actual value! This might not really happen a whole lot in practice, but it’s still a wart big enough to have warranted the addition of the try ... catch construct in the R10B release.

Try a try in a tree

To put exceptions in practice, we’ll do a little exercise requiring us to dig for our tree module. We’re going to add a function that lets us do a lookup in the tree to find out whether a value is already present in there or not. Because the tree is ordered by its keys and in this case we do not care about the keys, we’ll need to traverse the whole thing until we find the value.

The traversal of the tree will be roughly similar to what we did in tree:lookup/2, except this time we will always search down both the left branch and the right branch. To write the function, you’ll just need to remember that a tree node is either {node, {Key, Value, NodeLeft, NodeRight}} or {node, 'nil'} when empty. With this in hand, we can write a basic implementation without exceptions:

%% looks for a given value 'Val' in the tree.
has_value(_, {node, 'nil'}) ->
    false;
has_value(Val, {node, {_, Val, _, _}}) ->
    true;
has_value(Val, {node, {_, _, Left, Right}}) ->
    case has_value(Val, Left) of
        true -> true;
        false -> has_value(Val, Right)
    end.

The problem with this implementation is that every node of the tree we branch at has to test for the result of the previous branch:

A diagram of the tree with an arrow following every node checked while traversing the tree, and then when returning the result

This is a bit annoying. With the help of throws, we can make something that will require less comparisons:

has_value(Val, Tree) -> 
    try has_value1(Val, Tree) of
        false -> false
    catch
        true -> true
    end.

has_value1(_, {node, 'nil'}) ->
    false;
has_value1(Val, {node, {_, Val, _, _}}) ->
    throw(true);
has_value1(Val, {node, {_, _, Left, Right}}) ->
    has_value1(Val, Left),
    has_value1(Val, Right).

The execution of the code above is similar to the previous version, except that we never need to check for the return value: we don’t care about it at all. In this version, only a throw means the value was found. When this happens, the tree evaluation stops and it falls back to the catch on top. Otherwise, the execution keeps going until the last false is returned and that’s what the user sees:

A diagram of the tree with an arrow following every node checked while traversing the tree, and then skipping all the nodes on the way back up (thanks to a throw)

Of course, the implementation above is longer than the previous one. However, it is possible to realize gains in speed and in clarity by using non-local returns with a throw, depending on the operations you’re doing. The current example is a simple comparison and there’s not much to see, but the practice still makes sense with more complex data structures and operations.

That being said, we’re probably ready to solve real problems in sequential Erlang.

  • < Previous
  • Index
  • Next >

Содержание

  1. Современная обработка ошибок в PHP
  2. Новый интерфейс
  3. THROWABLE
  4. Константы ошибок
  5. Функция обработчика ошибок
  6. Отображение или подавление нефатальной ошибки
  7. Исключения (Exceptions)
  8. Ловля исключений
  9. Это ваш окончательный ответ?
  10. Функция обработчика исключений
  11. Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»
  12. В заключении
  13. Good Practices: handling error and exceptions in PHP
  14. About this series:
  15. Introduction
  16. How to manage errors in PHP 7
  17. Errors, errors everywhere!
  18. Creating a bespoke error handler
  19. Error Handling Functions
  20. What are exceptions
  21. Catch multiple exceptions at once
  22. The finally keyword

Современная обработка ошибок в PHP

как правильно использовать try catch

Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой

Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений — Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP.

Новый интерфейс

Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс Throwable . И Error и Exception классы реализуют Throwable интерфейс — это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:

Список доступных встроенных классов исключений начиная с PHP 7.4:

Дополнительные классы исключений можно найти в стандартной библиотеке PHP . И наиболее заметным из расширений JSON является класс JsonException.

THROWABLE

Интерфейс Throwable PHP 7:

Вот иерархия Throwable:

В предыдущих версиях PHP ошибки обрабатывались совершенно иначе, чем исключения. Если возникала ошибка, то пока она не была фатальной, она могла быть обработана пользовательской функцией.

Проблема заключалась в том, что было несколько фатальных ошибок, которые не могли быть обработаны определяемым пользователем обработчиком ошибок. Это означало, что вы не могли корректно обрабатывать фатальные ошибки в PHP. Было несколько побочных эффектов, которые были проблематичными, такие как потеря контекста времени выполнения, деструкторы не вызывались, да и вообще иметь дело с ними было неудобно. В PHP 7 фатальные ошибки теперь являются исключениями, и мы можем легко их обработать. Фатальные ошибки приводят к возникновению исключений. Вам необходимо обрабатывать нефатальные ошибки с помощью функции обработки ошибок.

Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.

Этот скрипт выведет сообщение об ошибке при попытке доступа к недопустимой переменной. Попытка вызвать функцию, которая не существует, приведет к фатальной ошибке в более ранних версиях PHP, но в PHP 7.1 вы можете ее перехватить. Вот вывод для скрипта:

Константы ошибок

В PHP много констант, которые используются в отношении ошибок. Эти константы используются при настройке PHP для скрытия или отображения ошибок определенных классов.

Вот некоторые из наиболее часто встречающихся кодов ошибок:

  • E_DEPRECATED — интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.
  • E_STRICT — аналогично E_DEPRECATED, — указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.
  • E_PARSE — ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.
  • E_NOTICE — движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.
  • E_ERROR — скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.
  • E_RECOVERABLE_ERROR — указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.

Полный список констант можно найти в руководстве по PHP.

Функция обработчика ошибок

Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.

PHP передает параметры в вашу функцию обработчика ошибок. Вы можете опционально объявить их в сигнатуре функции, если хотите использовать их в своей функции.

Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:

Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются whoops, и Symony Debug, ErrorHandler компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка Laravel .

Для подробного и расширенного использования, пожалуйста, обратитесь к руководству по PHP для обработчика ошибок.

Отображение или подавление нефатальной ошибки

Когда ваше приложение выходит в продакшн, логично, что вы хотите скрыть все системные сообщения об ошибках во время работы, и ваш код должен работать без генерации предупреждений или сообщений. Если вы собираетесь показать сообщение об ошибке, убедитесь, что оно сгенерировано и не содержит информации, которая может помочь злоумышленнику проникнуть в вашу систему.

В вашей среде разработки вы хотите, чтобы все ошибки отображались, чтобы вы могли исправить все проблемы, с которыми они связаны, но в процессе работы вы хотите подавить любые системные сообщения, отправляемые пользователю.

Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:

  • display_errors – может быть установлен в false для подавления сообщений
  • log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала
  • error_reporting – можно настроить, какие ошибки вызывают отчет

Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например Sentry или Rollbar .

Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое — просто добавьте любое выражение PHP с символом «собаки», и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.

Больше информации для всех функций, связанных с ошибками PHP, можно найти в руководстве.

Исключения (Exceptions)

Исключения являются основной частью объектно-ориентированного программирования и впервые были представлены в PHP 5.0. Исключением является состояние программы, которое требует специальной обработки, поскольку оно не выполняется ожидаемым образом. Вы можете использовать исключение, чтобы изменить поток вашей программы, например, чтобы прекратить что-либо делать, если некоторые предварительные условия не выполняются.

Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:

PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.

Надежный код может встретить ошибку и справиться с ней. Разумная обработка исключений повышает безопасность вашего приложения и облегчает ведение журнала и отладку. Управление ошибками в вашем приложении также позволит вам предложить своим пользователям лучший опыт. В этом разделе мы рассмотрим, как отлавливать и обрабатывать ошибки, возникающие в вашем коде.

Ловля исключений

Вы должны использовать try/catch структуру:

Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в Exception PHP docs.

Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).

Давайте посмотрим на другой пример:

Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.

Немного более продвинутый сценарий:

Это ваш окончательный ответ?

В PHP 5.5 и более поздних, блок finally также может быть указан после или вместо блоков catch. Код внутри блока finally всегда будет выполняться после блоков try и catch независимо от того, было ли выброшено исключение, и до возобновления нормального выполнения. Одним из распространенных применений блока finally является закрытие соединения с базой данных, но, наконец, его можно использовать везде, где вы хотите, чтобы код всегда выполнялся.

Вот хороший пример того, как работают операторы PHP catch/finally:

Функция обработчика исключений

Любое исключение, которое не было обнаружено, приводит к фатальной ошибке. Если вы хотите изящно реагировать на исключения, которые не перехватываются в блоках перехвата, вам нужно установить функцию в качестве обработчика исключений по умолчанию.

Для этого вы используете функцию set_exception_handler() , которая принимает вызываемый элемент в качестве параметра. Ваш сценарий завершится после того, как вызов будет выполнен.

Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.

Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.

Для получения дополнительной информации обратитесь к документации PHP.

Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»

Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .

Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:

В заключении

Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.

Веб-разработчик со стажем программирования более 13 лет, постоянно учусь, люблю делать новые проекты.

Источник

Good Practices: handling error and exceptions in PHP

About this series:


In this series, we are exploring what are the best practices a web developer must take care of when creating or managing PHP code.
Sanitize, validate and escape
Security and managing passwords
Handling error and exceptions

Introduction


21st October 1879 19:15:13

Menlo Park, New Jersey


A strong breeze is cooling down the evening,
​ ​
the weather has not been kind lately and the rain has been fallen continuously for the past 2 weeks and it does not seem it wants to stop anytime soon.
​​
inside a factory, several men are working like hell on something that has the potential to change the world as they know it.
​​

​​
The final test will happen in a few minutes and it will be the result of weeks of research, hard work and calculation.
​​
3
​​
2

1
​​
POP!!
​​
A flashlight up the room for a second then dark again, another day has passed, another failed one, another error.
​​
That’s the 9’999th now.
​​

“I have not failed. I’ve just found 10,000 ways that won’t work.”
Thomas Edison

​​
If you have ever heard motivational speaker like Brian Tracy, Tony Robin, Zig Ziglar or Jim Rohn you know that all of them have a problem with the way schools run around the world.
​​
In university and colleges, errors are punished rather than welcomed,
​​
In the real world though, errors are the only way you can become a better one.
​​
This is especially true in web development.

Trial and errors are definitely the best way to become great and to craft your skill.

It is also true that we need to be careful and pay attention to the error we make.

The reason for making errors is that we can manage and learn from them.

PHP make it easy to handle, manage and learn by error that can occur in your code.

How to manage errors in PHP 7

If you ask me what are the features that have improved the most between PHP 5.6 and PHP 7 or PHP 7.1 I will surely put error handling in the top 3 positions.
​​
The seventh version of our beloved programming language brought to us an interface with a defined goal:
​​
it needed to be easily implemented and be useful in lots of cases.
​​
The throwable interface,
​​
in fact, can be implemented by both types of class (Error class and Exception) and supply and a handful of methods that we can use to better understand and analyze our error.

Here you can see the class and how can you use it:

In the snippet above are showed all the methods you can use to debug a PHP error in your script (with lots of type hinting)

and the actual code you can use to try a block of code and throw an error or an exception.

Errors, errors everywhere!


Whoever has dealt with error prior to PHP 5.6 know what type of pain it was and the headache he needed to deal with during the debugging phase.

Previously errors were produced within the engine and you could have handled it as long as they weren’t fatal errors.

You also needed to consider that errors and exception were two completely different things in your code and it added another layer of difficulty to the already complicated situation.

From PHP 7 onwards, fatal errors result in error exception being thrown and you can easily manage not fatal errors with bespoke methods

I can, for instance, run some code and only throw an error in case of a fatal occurs.


If this is the first time you handle errors or you see a try-catch this may seem confusing you may feel you want to step back a bit,

Don’t worry, in Php Basics you will find all you need to know to be ready to read this article.

Bear with me while I explain what is happening:

In our pseudocode we have to element a variable and a funtion, both of them have not been defined , thus are unser or inexistent.

PHP handle an unset variable differently that a undefined function,

the first one is just an invalid variable a notice error, web developes work long hour, it can happen often,

the second error instead is an actual fuction, with maybe some important logic in it.

That is why in PHP an undefined function is a serious problem, a fatal error.

Having these two lines of code within the try block permits the catch to instanciate an instance of the Error class (the variable $e).

$e implements the Throwable interface, which means it can use all the method you saw in the previous paragraph. Hence $e->getMessage();

Creating a bespoke error handler


There may be occasions in which some errors that are not instace of the Error class occur.

If these errors are not fatal PHP allows you, as a developer, to define bespoke function and handle them the way you prefer.

To do so you need to use the set_error_handle() function.

This function accept either a string with the name of the function you want to use or an array that contains an object and the name of the method you are invoking.

The fuction can stop the script,

if you do not want it to continue or return a value and continue when the code invoked the function in the first place.

Let’s have a look at a practical yet easy example below.

set_error_handle() cannot manage fatal error, which means that, in order to test it, we need to simulate a warning (a division by zero will do),

then we’ll define the content of the function and catch the error when it occurs.


Do not forget that you want to hide errors from the production environment when an error occurs it has to be visible only on local or a staging server.

In fact, showing an error in production can give a massive hint about the vulnerability of your website to a hacker or malicious users.

To do this you can edit 3 settings within your php.ini

  • display_errors if set to false it will suppress errors;
  • log_errors store errors into a log file;
  • error_reporting configures which type of errors trigger a report;

Error Handling Functions

PHP has several functions that can make handling errors and easy tasks in this section you will find a brief description for each of them.

  • debug_•backtrace() it accepts few parameters like options’ flag and a number that limits the results and generate a summary of how your script got where it is, it does it returning an array variable;
  • debug_•print_•backtrace() it works in a similar way as the previous one but instead of creating an array of trace it prints them in an inverted chronological order;
  • error_•get_•last() it returns an associative array that contains the info of the last error occurred in the script;
  • error_•clear_•last() reset the internal error log of PHP, if used before error_get_last() the latter will return null;
  • error_•log() it requires a mandatory message as a parameter and sends it to the defined error handling routines;
  • error_•reporting() This function requires either none or error constants as a parameter (you can find the complete list here https://www.php.net/manual/en/errorfunc.constants.php ) and set the level of accuracy the PHP application must have when handling errors;
  • set_•error_•handler() and set_•exception_•handler() set bespoke functions that handle errors and exception;
  • restore_•error_•handler() and restore_•exception_•handler() it is used after set_•error_•handler() and set_•exception_•handler() it aims to revert the handler to the previous error handler it could be the built-in or a user-defined function;
  • trigger_•error() this function triggers an error by accepting a message as a mandatory parameter and an error_type’s flag as a discretionary one;
  • user_•error() alias of trigger_•error();

What are exceptions


Exceptions are relatively new features of PHP, they have been implemented only in PHP 5 but they quickly became a core part of any object-oriented programming script.

Exceptions are states of the scripts the require special treatment because the script is not running as it is supposed to.

Both Errors and Exception are just classes that implement the Throwable interface.

Like any other class in the OOP world, the can be extended,

which allows to create error hierarchies and create tailor the way you handle exceptions.

Something to pay attention to and that can lead to errors and misunderstandings is that you cannot declare your own class and then decide to throw exceptions.

The only classes that can throw error are the ones that implement the Throwable class.

Let’s play a game, look at the code and answer the following question.


Which exception is thrown?

In the example below the exception thrown is the SubException, it inherits from MainException which extends Exception.

The block is evaluated from the first (the one on top) to the last and when the exception matches the name of the class given it is triggered.

This above can be considered the best practice because we narrow down the message we want to show and eventually.

In case none of our classes matches, we use the PHP class Exception for an overall check.

Catch multiple exceptions at once


Until now we have seen several tries with several catches.

Each block of code echo a message with different errors,

Think at the case we create a dozen of different exceptions,

should we add a dozen of catch blocks? And what if we want to handle several types of error in the same way?

To manage this situation PHP provided the pipe keyword “ | ”;

Here is an example of how to use it:

The finally keyword


I am sure you have already seen a switch-case conditional structure in the past,
​​
if not I got an entire article about conditional statements in PHP.
​​
One of the elements of the switch statement is that if none of the cases is evaluated eventually a “default“ block, if present, is going to run.
​​
The try a catch allows to do something similar to your code by providing the keyword finally


A common use for this keyword is, as shown in the example above, when we need to close a file that we have previously opened inside the try block.
​​
You must remember that the code inside the finally block is always executed, even if exceptions are thrown earlier in the script.
​​
But you are free to use it whenever you believe this will help your case.
​​

If you think this article was useful and you want to learn more about good practices in PHP click the image below

Источник

Время прочтения
5 мин

Просмотры 389K

JavaScript может быть кошмаром при отладке: некоторые ошибки, которые он выдает, могут быть очень трудны для понимания с первого взгляда, и выдаваемые номера строк также не всегда полезны. Разве не было бы полезно иметь список, глядя на который, можно понять смысл ошибок и как исправить их? Вот он!

Ниже представлен список странных ошибок в JavaScript. Разные браузеры могут выдавать разные сообщения об одинаковых ошибках, поэтому приведено несколько примеров там, где возможно.

Как читать ошибки?

Перед самим списком, давайте быстро взглянем на структуру сообщения об ошибке. Понимание структуры помогает понимать ошибки, и вы получите меньше проблем, если наткнетесь на ошибки, не представленные в этом списке.

Типичная ошибка из Chrome выглядит так:

Uncaught TypeError: undefined is not a function

Структура ошибки следующая:

  1. Uncaught TypeError: эта часть сообщения обычно не особо полезна. Uncaught значит, что ошибка не была перехвачена в catch, а TypeError — это название ошибки.
  2. undefined is not a function: это та самая часть про ошибку. В случае с сообщениями об ошибках, читать их нужно прямо буквально. Например, в этом случае, она значит то, что код попытался использовать значение undefined как функцию.

Другие webkit-браузеры, такие как Safari, выдают ошибки примерно в таком же формате, как и Chrome. Ошибки из Firefox похожи, но не всегда включают в себя первую часть, и последние версии Internet Explorer также выдают более простые ошибки, но в этом случае проще — не всегда значит лучше.

Теперь к самим ошибкам.

Uncaught TypeError: undefined is not a function

Связанные ошибки: number is not a function, object is not a function, string is not a function, Unhandled Error: ‘foo’ is not a function, Function Expected

Возникает при попытке вызова значения как функции, когда значение функцией не является. Например:

var foo = undefined;
foo();

Эта ошибка обычно возникает, если вы пытаетесь вызвать функцию для объекта, но опечатались в названии.

var x = document.getElementByID('foo');

Несуществующие свойства объекта по-умолчанию имеют значение undefined, что приводит к этой ошибке.

Другие вариации, такие как “number is not a function” возникают при попытке вызвать число, как будто оно является функцией.

Как исправить ошибку: убедитесь в корректности имени функции. Для этой ошибки, номер строки обычно указывает в правильное место.

Uncaught ReferenceError: Invalid left-hand side in assignment

Связанные ошибки: Uncaught exception: ReferenceError: Cannot assign to ‘functionCall()’, Uncaught exception: ReferenceError: Cannot assign to ‘this’

Вызвано попыткой присвоить значение тому, чему невозможно присвоить значение.

Наиболее частый пример этой ошибки — это условие в if:

if(doSomething() = 'somevalue')

В этом примере программист случайно использовал один знак равенства вместо двух. Выражение “left-hand side in assignment” относится к левой части знака равенства, а, как можно видеть в данном примере, левая часть содержит что-то, чему нельзя присвоить значение, что и приводит к ошибке.

Как исправить ошибку: убедитесь, что вы не пытаетесь присвоить значение результату функции или ключевому слову this.

Uncaught TypeError: Converting circular structure to JSON

Связанные ошибки: Uncaught exception: TypeError: JSON.stringify: Not an acyclic Object, TypeError: cyclic object value, Circular reference in value argument not supported

Всегда вызвано циклической ссылкой в объекте, которая потом передается в JSON.stringify.

var a = { };
var b = { a: a };
a.b = b;
JSON.stringify(a);

Так как a и b в примере выше имеют ссылки друг на друга, результирующий объект не может быть приведен к JSON.

Как исправить ошибку: удалите циклические ссылки, как в примере выше, из всех объектов, которые вы хотите сконвертировать в JSON.

Unexpected token ;

Связанные ошибки: Expected ), missing ) after argument list

Интерпретатор JavaScript что-то ожидал, но не обнаружил там этого. Обычно вызвано пропущенными фигурными, круглыми или квадратными скобками.

Токен в данной ошибке может быть разным — может быть написано “Unexpected token ]”, “Expected {” или что-то еще.

Как исправить ошибку: иногда номер строки не указывает на правильное местоположение, что затрудняет исправление ошибки.

Ошибка с [ ] { } ( ) обычно вызвано несовпадающей парой. Проверьте, все ли ваши скобки имеют закрывающую пару. В этом случае, номер строки обычно указывает на что-то другое, а не на проблемный символ.

Unexpected / связано с регулярными выражениями. Номер строки для данного случая обычно правильный.

Unexpected; обычно вызвано символом; внутри литерала объекта или массива, или списка аргументов вызова функции. Номер строки обычно также будет верным для данного случая.

Uncaught SyntaxError: Unexpected token ILLEGAL

Связанные ошибки: Unterminated String Literal, Invalid Line Terminator

В строковом литерале пропущена закрывающая кавычка.

Как исправить ошибку: убедитесь, что все строки имеют правильные закрывающие кавычки.

Uncaught TypeError: Cannot read property ‘foo’ of null, Uncaught TypeError: Cannot read property ‘foo’ of undefined

Связанные ошибки: TypeError: someVal is null, Unable to get property ‘foo’ of undefined or null reference

Попытка прочитать null или undefined так, как будто это объект. Например:

var someVal = null;
console.log(someVal.foo);

Как исправить ошибку: обычно вызвано опечатками. Проверьте, все ли переменные, использованные рядом со строкой, указывающей на ошибку, правильно названы.

Uncaught TypeError: Cannot set property ‘foo’ of null, Uncaught TypeError: Cannot set property ‘foo’ of undefined

Связанные ошибки: TypeError: someVal is undefined, Unable to set property ‘foo’ of undefined or null reference

Попытка записать null или undefined так, как будто это объект. Например:

var someVal = null;
someVal.foo = 1;

Как исправить ошибку: это тоже обычно вызвано ошибками. Проверьте имена переменных рядом со строкой, указывающей на ошибку.

Uncaught RangeError: Maximum call stack size exceeded

Связанные ошибки: Uncaught exception: RangeError: Maximum recursion depth exceeded, too much recursion, Stack overflow

Обычно вызвано неправильно программной логикой, что приводит к бесконечному вызову рекурсивной функции.

Как исправить ошибку: проверьте рекурсивные функции на ошибки, которые могут вынудить их делать рекурсивные вызовы вечно.

Uncaught URIError: URI malformed

Связанные ошибки: URIError: malformed URI sequence

Вызвано некорректным вызовом decodeURIComponent.

Как исправить ошибку: убедитесь, что вызовы decodeURIComponent на строке ошибки получают корректные входные данные.

XMLHttpRequest cannot load some/url. No ‘Access-Control-Allow-Origin’ header is present on the requested resource

Связанные ошибки: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at some/url

Эта проблема всегда связана с использованием XMLHttpRequest.

Как исправить ошибку: убедитесь в корректности запрашиваемого URL и в том, что он удовлетворяет same-origin policy. Хороший способ найти проблемный код — посмотреть на URL в сообщении ошибки и найти его в своём коде.

InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable

Связанные ошибки: InvalidStateError, DOMException code 11

Означает то, что код вызвал функцию, которую нельзя было вызывать в текущем состоянии. Обычно связано c XMLHttpRequest при попытке вызвать на нём функции до его готовности.

var xhr = new XMLHttpRequest();
xhr.setRequestHeader('Some-Header', 'val');

В данном случае вы получите ошибку потому, что функция setRequestHeader может быть вызвана только после вызова xhr.open.

Как исправить ошибку: посмотрите на код в строке, указывающей на ошибку, и убедитесь, что он вызывается в правильный момент или добавляет нужные вызовы до этого (как с xhr.open).

Заключение

JavaScript содержит в себе одни из самых бесполезных ошибок, которые я когда-либо видел, за исключением печально известной Expected T_PAAMAYIM_NEKUDOTAYIM в PHP. Большая ознакомленность с ошибками привносит больше ясности. Современные браузеры тоже помогают, так как больше не выдают абсолютно бесполезные ошибки, как это было раньше.

Какие самые непонятные ошибки вы встречали? Делитесь своими наблюдениями в комментариях.

P.S. Этот перевод можно улучшить, отправив PR здесь.

About this series:


In this series, we are exploring what are the best practices a web developer must take care of when creating or managing PHP code.
Sanitize, validate and escape
Security and managing passwords
Handling error and exceptions

Introduction


21st October 1879 19:15:13

Menlo Park, New Jersey


A strong breeze is cooling down the evening,
​ ​
the weather has not been kind lately and the rain has been fallen continuously for the past 2 weeks and it does not seem it wants to stop anytime soon.
​​
inside a factory, several men are working like hell on something that has the potential to change the world as they know it.
​​

​​
The final test will happen in a few minutes and it will be the result of weeks of research, hard work and calculation.
​​
3
​​
2

1
​​
POP!!
​​
A flashlight up the room for a second then dark again, another day has passed, another failed one, another error.
​​
That’s the 9’999th now.
​​

“I have not failed. I’ve just found 10,000 ways that won’t work.”
Thomas Edison

​​
If you have ever heard motivational speaker like Brian Tracy, Tony Robin, Zig Ziglar or Jim Rohn you know that all of them have a problem with the way schools run around the world. 
​​
In university and colleges, errors are punished rather than welcomed, 
​​
In the real world though, errors are the only way you can become a better one.
​​
This is especially true in web development.

Trial and errors are definitely the best way to become great and to craft your skill.

It is also true that we need to be careful and pay attention to the error we make. 

The reason for making errors is that we can manage and learn from them.

PHP make it easy to handle, manage and learn by error that can occur in your code.

How to manage errors in PHP 7

If you ask me what are the features that have improved the most between PHP 5.6 and PHP 7 or PHP 7.1 I will surely put error handling in the top 3 positions.
​​
The seventh version of our beloved programming language brought to us an interface with a defined goal:
​​
it needed to be easily implemented and be useful in lots of cases.
​​
The throwable interface,
​​
in fact, can be implemented by both types of class (Error class and Exception) and supply and a handful of methods that we can use to better understand and analyze our error.

Here you can see the class and how can you use it:

Throwable {
    abstract public getMessage ( void ) : string
    abstract public getCode ( void ) : int
    abstract public getFile ( void ) : string
    abstract public getLine ( void ) : int
    abstract public getTrace ( void ) : array
    abstract public getTraceAsString ( void ) : string
    abstract public getPrevious ( void ) : Throwable
    abstract public __toString ( void ) : string
}
Try {
    // some code
} catch (Throwable $e) {
    echo "an instance of class Throwable has been caught";
}

In the snippet above are showed all the methods you can use to debug a PHP error in your script (with lots of type hinting)

and the actual code you can use to try a block of code and throw an error or an exception.

Errors, errors everywhere!


Whoever has dealt with error prior to PHP 5.6 know what type of pain it was and the headache he needed to deal with during the debugging phase.

Previously errors were produced within the engine and you could have handled it as long as they weren’t fatal errors.

You also needed to consider that errors and exception were two completely different things in your code and it added another layer of difficulty to the already complicated situation.

From PHP 7 onwards, fatal errors result in error exception being thrown and you can easily manage not fatal errors with bespoke methods

I can, for instance, run some code and only throw an error in case of a fatal occurs.

try {
    echo $thisVariableIsNotSet;
    inexistentFunctionInvoked();
} catch (Error $e) {
    echo "Error: $e->getMessage()";
}
// Error: Call to undefined function inexistentFunctionInvoked();


If this is the first time you handle errors or you see a try-catch this may seem confusing you may feel you want to step back a bit,

Don’t worry, in Php Basics you will find all you need to know to be ready to read this article.

Bear with me while I explain what is happening:

In our pseudocode we have to element a variable and a funtion, both of them have not been defined , thus are unser or inexistent.

PHP handle an unset variable differently that a undefined function,

the first one is just an invalid variable a notice error, web developes work long hour, it can happen often,

the second error instead is an actual fuction, with maybe some important logic in it.

That is why in PHP an undefined function is a serious problem, a fatal error.

Having these two lines of code within the try block permits the catch to instanciate an instance of the Error class (the variable $e).

$e implements the Throwable interface, which means it can use all the method you saw in the previous paragraph. Hence $e->getMessage();

Creating a bespoke error handler


There may be occasions in which some errors that are not instace of the Error class occur.

If these errors are not fatal PHP allows you, as a developer, to define bespoke function and handle them the way you prefer.

To do so you need to use the set_error_handle() function.

This function accept either a string with the name of the function you want to use or an array that contains an object and the name of the method you are invoking.

The fuction can stop the script,

if you do not want it to continue or return a value and continue when the code invoked the function in the first place.

Let’s have a look at a practical yet easy example below.

set_error_handle() cannot manage fatal error, which means that, in order to test it, we need to simulate a warning (a division by zero will do),

then we’ll define the content of the function and catch the error when it occurs.

function myErrorHandler($errno, $errstr, $errfile, $errline) {
    echo "An error occurred in line {$errline} of file {$errfile} with message {$errstr}";
}

set_error_handler("myErrorHandler");

try {
    5 / 0;
} catch (Throwable $e) {
    echo $e->getMessage();
}

// An error occurred in line 1 of file /index with message Division by zero


Do not forget that you want to hide errors from the production environment when an error occurs it has to be visible only on local or a staging server.

In fact, showing an error in production can give a massive hint about the vulnerability of your website to a hacker or malicious users.

To do this you can edit 3 settings within your php.ini

  • display_errors if set to false it will suppress errors;
  • log_errors store errors into a log file;
  • error_reporting configures which type of errors trigger a report;

Error Handling Functions

PHP has several functions that can make handling errors and easy tasks in this section you will find a brief description for each of them.

  • debug_•backtrace() it accepts few parameters like options’ flag and a number that limits the results and generate a summary of how your script got where it is, it does it returning an array variable;
  • debug_•print_•backtrace() it works in a similar way as the previous one but instead of creating an array of trace it prints them in an inverted chronological order;
  • error_•get_•last() it returns an associative array that contains the info of the last error occurred in the script;
  • error_•clear_•last() reset the internal error log of PHP, if used before error_get_last() the latter will return null;
  • error_•log() it requires a mandatory message as a parameter and sends it to the defined error handling routines;
  • error_•reporting() This function requires either none or error constants as a parameter (you can find the complete list here https://www.php.net/manual/en/errorfunc.constants.php ) and set the level of accuracy the PHP application must have when handling errors;
  • set_•error_•handler() and set_•exception_•handler() set bespoke functions that handle errors and exception;
  • restore_•error_•handler() and restore_•exception_•handler() it is used after set_•error_•handler() and set_•exception_•handler() it aims to revert the handler to the previous error handler it could be the built-in or a user-defined function;
  • trigger_•error() this function triggers an error by accepting a message as a mandatory parameter and an error_type’s flag as a discretionary one;
  • user_•error() alias of trigger_•error();


The PHP manual provides the entire list of predefined constant you can use as flag in some of the function above

What are exceptions


Exceptions are relatively new features of PHP, they have been implemented only in PHP 5 but they quickly became a core part of any object-oriented programming script.

Exceptions are states of the scripts the require special treatment because the script is not running as it is supposed to.

Both Errors and Exception are just classes that implement the Throwable interface.

Like any other class in the OOP world, the can be extended,

which allows to create error hierarchies and create tailor the way you handle exceptions.

Something to pay attention to and that can lead to errors and misunderstandings is that you cannot declare your own class and then decide to throw exceptions.

The only classes that can throw error are the ones that implement the Throwable class.

Let’s play a game, look at the code and answer the following question.

class MainException extends Exception {}
class SubException extends MainException {}

try {
    throw new SubException("SubException thrown");
} catch (MainException $e) {
    echo "MainException thrown" . $e->getMessage();
} catch (SubException $e) {
    echo "SubException thrown" . $e->getMessage();
} catch (Exception $e) {
    echo "Exception thrown" . $e->getMessage();
}


Which exception is thrown?

In the example below the exception thrown is the SubException, it inherits from MainException which extends Exception.

The block is evaluated from the first (the one on top) to the last and when the exception matches the name of the class given it is triggered.

This above can be considered the best practice because we narrow down the message we want to show and eventually.

In case none of our classes matches, we use the PHP class Exception for an overall check.

Catch multiple exceptions at once


Until now we have seen several tries with several catches.

Each block of code echo a message with different errors,

Think at the case we create a dozen of different exceptions,

should we add a dozen of catch blocks? And what if we want to handle several types of error in the same way?

To manage this situation PHP provided the pipe keyword “ | ”;

Here is an example of how to use it:

class MainException extends Exception {}
class SubException extends Exception {}

try {
    throw new SubException;
} catch (MainException |  SubException $e) {
    echo "Exception thrown: " . get_class($e);
}
// Exception thrown: SubException

The finally keyword


I am sure you have already seen a switch-case conditional structure in the past,
​​
if not I got an entire article about conditional statements in PHP.
​​
One of the elements of the switch statement is that if none of the cases is evaluated eventually a “default“ block, if present, is going to run.
​​
The try a catch allows to do something similar to your code by providing the keyword finally

try {
    $handle = fopen("c:\folder\resource.txt", "r");   
    // do some stuff with the file

    throw new Exception("Exception thrown");
} catch (Exception $e) {
    echo "Exception thrown" . $e->getMessage();
} finally {
    fclose ( resource $handle );
}


A common use for this keyword is, as shown in the example above, when we need to close a file that we have previously opened inside the try block.
​​
You must remember that the code inside the finally block is always executed, even if exceptions are thrown earlier in the script.
​​
But you are free to use it whenever you believe this will help your case.
​​


If you think this article was useful and you want to learn more about good practices in PHP click the image below

http___eepurl.com_dIZqjf.jpg



Conclusion

Thomas Edison came from the humble origin and it is confirmed by historical data that he wasn’t the most brilliant of the individual.
​​
Anyway, his capacity to surround himself by creative and passionate people, plus the ability to deeply analyze and learn from his error made him one of the best inventors ever lived, surely one of the most wealthy and famous of his era.
​​
With no hesitation, I can say that we as web developers can do the same.
​​
Maybe we will never invent a world’s famous application or change the life of billions of people with our website but there is no doubt that by learning from our mistake we will increase our skill way faster.
​​
Managing errors is a fundamental part of this.
​​
Learn how to properly do that and master all the possibility available in PHP and its components such as PHPUnit and Sentry will make this task easy for each of us.
​​
Useful components or website you can use to manage your error right now:
filp/whoops
nette/tracy
vimeo/psalm
sebastianbergmann/phpunit
codeception/codeception
getsentry/sentry
https://phpcodechecker.com
https://www.piliapp.com/php-syntax-check

После просмотра этой статье, Я повозился с mochiweb. Пытаясь воспроизвести то, что сделано в статье — в основном настраивая сервер mochiweb, имея два узла erlang, а затем вызывая функцию, определенную в одном узле в другом (после установки net_adm: ping () между двумя узлами, чтобы они знали каждый Другие).

Я мог проследить за всем до этой части вызова функции. В n1 @ localhost, который является сервером mochiweb, я вызываю (как это сделано в статье):

router:login(IdInt, self()).

Затем в n2 @ localhost, который является скриптом router.erl, я определил функцию входа в систему:

login(Id, Pid) when is_pid(Pid) ->
    gen_server:call(?SERVER, {login, Id, Pid}).

handle_call({login, Id, Pid}, _From, State) when is_pid(Pid) ->
          ets:insert(State#state.pid2id, {Pid, Id}),
          ets:insert(State#state.id2pid, {Id, Pid}),
          link(Pid), % tell us if they exit, so we can log them out
          io:format("~w logged in as ~wn",[Pid, Id]),
          {reply, ok, State};

Я вставил только соответствующие части кода. Однако, когда я теперь обращаюсь к веб-серверу в браузере, я получаю этот отчет об ошибке на n1 @ localhost:

=CRASH REPORT==== 11-Jun-2009::12:39:49 ===
  crasher:
    initial call: mochiweb_socket_server:acceptor_loop/1
    pid: <0.62.0>
    registered_name: []
    exception error: undefined function router:login/2
      in function  mochiconntest_web:loop/2
      in call from mochiweb_http:headers/5
    ancestors: [mochiconntest_web,mochiconntest_sup,<0.59.0>]
    messages: []
    links: [<0.61.0>,#Port<0.897>]
    dictionary: [{mochiweb_request_path,"/test/123"}]
    trap_exit: false
    status: running
    heap_size: 1597
    stack_size: 24
    reductions: 1551
  neighbours:

=ERROR REPORT==== 11-Jun-2009::12:39:49 ===
{mochiweb_socket_server,235,{child_error,undef}}

После поиска в Google я получил базовую суть того, что пытается сказать ошибка — в основном это говорит о том, что функция входа в систему, вызываемая в n1 @ localhost, не определена, но она определена в n2 @ localhost (и оба узла знают каждый другое — я сделал nodes(). проверить) !! Скажите, пожалуйста, где я ошибаюсь!

Понравилась статья? Поделить с друзьями:
  • Exception error dota 2
  • Exception error 15 modbus
  • Exception erangeerror in module interfaceeditor asi at 00014c6b samp range check error
  • Exception erangeerror in module gfxhack asi at 00007e9c range check error
  • Exception efc create error in module