📜 ⬆️ ⬇️

Correct PHP error handling

What I mean by proper processing:




Universal solution


To do this, we will make the class exceptionHandlerClass. In exceptionHandlerClass settings and static methods will be stored - error, exception, and assertion handlers. We also need the setupHandlers and restoreHandlers methods. The first method will set up error trapping. Error and assertion handlers will throw an ErrorException. The Exception handler will handle the raw Exception and output the appropriate response depending on the settings. restoreHandlers will return all handlers to their original state - this will help when embedding a class in code with an existing error handling mechanism. Connection looks like this:
  1. require 'exceptionHandler / exceptionHandlerClass.php' ;
  2. exceptionHandlerClass :: setupHandlers ( ) ;

enable debug mode (disabled by default):
  1. exceptionHandlerClass :: $ debug = true ;

Output formats


It’s easier to explain with an example: to output trace on a web page, I wrap it in pre tags and use htmlspecialchars (), on the other hand, the same trace would not be convenient to read to the console, it would be easier if it were plainText. If you want to display an error as a SoapServer response, then it must be a well-formed XML document (SoapFault). If the script displays binary data, such as an image, then it is more convenient to display errors via WildFire. In all these situations, you just need to apply different output formats.
For different formats we will create different classes. To begin with, I implement two output formats, exceptionHandlerOutputWeb (for the web) and exceptionHandlerOutputCli (for the command line). We also need a factory class (exceptionHandlerOutputFactory), in which logic will be encapsulated, when and what output format to apply.
  1. public function getExceptionHandlerOutput ( ) {
  2. if ( php_sapi_name ( ) == 'cli' ) {
  3. return new exceptionHandlerOutputCli ( ) ;
  4. }
  5. return new exceptionHandlerOutputWeb ( ) ;
  6. }

When you call setupHandlers, you can set the output format by passing an instance of the class exceptionHandlerOutput * or exceptionHandlerOutputFactory *.
  1. exceptionHandlerClass :: setupHandlers ( new exceptionHandlerOutputAjax ( ) ) ;

Thanks to this architecture, you can easily expand formats. To create a new format, it is enough to create a class that will inherit from the abstract class exceptionHandlerOutput and implement one method (output).
  1. class exceptionHandlerOutputAjax extends exceptionHandlerOutput {
  2. public function output ( $ exception , $ debug ) {
  3. header ( 'HTTP / 1.0 500 Internal Server Error' , true , 500 ) ;
  4. header ( 'Status: 500 Internal Server Error' , true , 500 ) ;
  5. $ response = array (
  6. 'error' => true
  7. 'message' => '' ,
  8. ) ;
  9. if ( $ debug ) {
  10. $ response [ 'message' ] = $ exception -> getMessage ( ) ;
  11. } else {
  12. $ response [ 'message' ] = self :: $ productionMessage ;
  13. }
  14. exit ( json_encode ( $ response ) ) ;
  15. }
  16. }

If you need more complex logic to automatically select the output format, you need to create a class that inherits from exceptionHandlerOutputFactory and implement the getExceptionHandlerOutput method.
  1. class exceptionHandlerOutputAjaxFactory extends exceptionHandlerOutputDefaultFactory {
  2. public function getExceptionHandlerOutput ( ) {
  3. if ( self :: detect ( ) ) {
  4. return new exceptionHandlerOutputAjax ( ) ;
  5. }
  6. parent :: getExceptionHandlerOutput ( ) ;
  7. }
  8. public static function detect ( ) {
  9. return ( ! empty ( $ _SERVER [ 'HTTP_X_REQUESTED_WITH' ] )
  10. && strtolower ( $ _SERVER [ 'HTTP_X_REQUESTED_WITH' ] ) == 'xmlhttprequest' ) ;
  11. }
  12. }
  13. exceptionHandlerClass :: setupHandlers ( new exceptionHandlerOutputAjaxFactory ( ) ) ;

Logging


As I said above, logging can be enabled as desired. To do this, the exceptionLand method is created in exceptionHandlerClass
  1. public static function exceptionLog ( $ exception , $ logPriority = null ) {
  2. if ( ! is_null ( self :: $ exceptionHandlerLog ) ) {
  3. self :: $ exceptionHandlerLog -> log ( $ exception , $ logPriority ) ;
  4. }
  5. }

if you need to enable logging, it is enough to do the following:
  1. exceptionHandlerClass :: $ exceptionHandlerLog = new exceptionHandlerSimpleLog ( ) ;

The class for logging must be inherited from the abstract exceptionHandlerLog and implement the log method
  1. class exceptionHandlerSimpleLog extends exceptionHandlerLog {
  2. public function log ( $ exception , $ logType ) {
  3. switch ( $ logType ) {
  4. case self :: uncaughtException :
  5. error_log ( $ exception -> getMessage ( ) ) ;
  6. break ;
  7. }
  8. }
  9. }

logType is one of the constants declared by exceptionHandlerLog
  1. const uncaughtException = 0 ; // unhandled exceptions
  2. const caughtException = 1 ; // call the logging method outside of error handlers
  3. const ignoredError = 2 ; // masked @ errors are logged if the scream option is disabled
  4. const lowPriorityError = 3 ; // errors that do not become exception
  5. const assertion = 4 ; // assertion

With logType and exception, the developer can decide for himself what exceptions and how to log. For example, uncaughtException can be sent by mail, ignoredError with severity E_ERROR log to a file and so forth.

Trace


When I output trace, I want to see the type of the exception, the message, and the trace itself. In the trace for each call should be displayed, which function was called, a list of "short" parameters, the file and the line where the call occurred. I will explain what the “short” parameters are with examples: if the function was called with a string of 1000 characters in length, the presence of this string in the trace will not do anything to solve the problem, but will only make it more difficult to read the trace, the same goes for arrays with great nesting. Conclusion trace on the screen just has to tell where to look. To figure out what exactly is happening you need to debug using xdebug or primitive var_dump () and die (), which one you like best.
Example trace:
	 [ErrorException]: E_WARNING - mysql_connect (): Can't connect to MySQL server on 'localhost' (10061)
	 # 0: mysql_connect ()
	     D: \ projects1 \ d \ index.php: 19
	 # 1: testClass :: test1 ("long string ... eeeeeery long string" (56))
	     D: \ projects1 \ d \ index.php: 22
	 # 2: testClass-> test2 (testClass (), -∞, i: iTest, c: testClass, fa: testClass :: test2)
	     D: \ projects1 \ d \ index.php: 27
	 # 3: testAll (r: stream, fs: testClass :: test1)
	     D: \ projects1 \ d \ index.php: 30
Legend

And the most useful ... links to open files in the IDE right from the trace.

Clicking on the link in the IDE will open the corresponding file on the appropriate line.
For console mode (NetBeans console):
NetBeans Console
  1. exceptionHandlerOutputCli :: setFileLinkFormat ( ': in% f on line% l' ) ;
For web mode (TextMate):
  1. exceptionHandlerOutputWeb :: setFileLinkFormat ( 'txmt: // open /? file: //% f & line =% l' ) ;

It can be implemented for NetbBeans (or another IDE). To do this: register the protocol; make a handler for this protocol (the simplest is a bat file). In the handler, call NetBeans with the appropriate file and string via the command line. But this is the topic for the next article.
The code was written in two days so that minor defects are possible. Download (did not have time to put in the repository).

UPD: moved to PHP blog
UPD2: in the continuation of the topic work with exceptions in PHP

')

Source: https://habr.com/ru/post/99431/


All Articles