Readers facing this article
This article is for programmers who wish to understand the PHP5 exception handling mechanism. Readers need to have certain object-oriented programming and PHP foundations.
Introduction
This article focuses on various errors, where you will be able to see a variety of error handling methods in PHP4, PHP5. PHP5 introduces an "exception mechanism" - a new mechanism for incorrect processing in an object system. Just as you are about to see, "Abnormal" provides a lot of advanced features than traditional error handling mechanisms.
PHP5 error before
Program error handling before PHP5 uses three ways:
1. Use the Trigger_Error () or DIE () function to generate a warning (Warning) or fatal error (FATAL ERROR);
2. Returns an error tag (such as false) in a class method or function, or it is also possible to set an attribute or global variable (such as $ error) after one, and then verify the value of whether or not to continue executing the program (eg if IF ($ Error == 1) {});
3. Use a PEAR to handle errors;
(1) Use Die () or Trigger_ERROR ()
You can use the DIE () function to end the program run. The following is a simple class that attempts to load a class file from a directory.
Code list index.php?>
php // php 4 Require_once ('cmd_php4 / command.php'); Class CommandManager {var $ cmddir = "cmd_php4"; function getcommandObject ($ cmd) {$ PATH = "{$ this-> cmddir} / {$ CMD} .php "; if (! File_exists ($ PATH)) {Die (" Cannot Find $ PATH / N ");} Require_once $ PATH; if (! Class_exists ($ cmd)) {Die (" Class $ CMD Does NOT EXIST ");} $ RET = New $ cmd (); if (! is_A ($ RET, 'Command')) {DIE (" $ cmd is not a command ");} RETURN $ RET;}}?>
This is a simple example of "Command Pattern Design Mode" with PHP (see "Java and Mode"). Programmers using this class (Customer CLIENT CODER) can place a class into the directory (CMD_PHP4 directory). Once the file and the class included in the class, and this class is a subclass of the Command class, our class method will generate an available Command object. A Execute () method is defined in the Command class to perform the list of commands, which is the object returned by the getcommandObject () method will execute Execute ().
Let's take a look at the parent Class Command class, we exist it in the cmd_php4 / command.php file.
Code list cmd_php4 / command.php?>
php // php 4 class command {function execute () {die ("Command :: execute () is an abstract method");}}?>
You can see that Command is the implementation of abstract classes in PHP4, we can't directly alive, but must be derived from neutronia and then instantiate. When we use PHP5, we can use better way - use the Abstract keyword to declare the classes and methods as "abstraction": Code list?>
php // php 5 Abstract class command {Abstract function execute ();}?>
Below is an implementation of the abstract class above, overridden the execute () method, where you join the real-executed content. This class is named RealCommand, which can be found in the cmd_php4 / realcommand.php file.
Code list cmd_php4 / realcommand.php?>
php // PHP 4 Require_once 'Command.php'; Class Realcommand Extends Command {Function Execute () {Print "Realcommand :: EXECUTE () Executing as Ordered Sah! / n";}}?>
Using such a structure can make the code flexible. You can add new Command classes at any time without changing the framework of the periphery. But you have to pay attention to some factors for potential suspension scripts. We need to make sure that the class file exists and exists in the file, and the class is a subclass of Command (just like Realcommand).
In the example, if we try to find the operation of the class fails, the script execution will abort, which reflects the security of the code. But this code is not flexible, there is not enough flexibility. Extreme reflection is that the class method can only perform a positive and positive operation, which is only responsible for finding and instantifying a Command object. It cannot handle errors performed in a larger-wide script (of course, it should not be responsible for handling errors. If we give a class method plus too much association with the surrounding code, then this type of reuse will become difficult, not easy Extension).
Although use DIE () avoids the risk of embedded script logic in the getcommandObject () method, it is too intense for the response to the error - immediately abort the program. In fact, we don't want to stop executing programs immediately when you can't find the you want, maybe we have a default command to let the program continue.
We may be able to generate a user warning with Trigger_Error () to make the program more flexible.
Code list index2.php?>
php // php 4 Require_once ('cmd_php4 / command.php'); Class CommandManager {var $ cmddir = "cmd_php4"; function getcommandObject ($ cmd) {$ PATH = "{$ this-> cmddir} / {$ CMD} .php "; if (! File_exists ($ PATH)) {Trigger_ERROR (" Cannot Find $ PATH ", E_USER_ERROR);} Require_once $ path; if (! Class_exists ($ cmd)) {Trigger_ERROR (" Class $ cmd does NOT EXIST ", E_USER_ERROR);} $ RET = New $ cmd (); if (! is_A ($ RET, 'Command')) {Trigger_ERROR (" $ cmd is not a command ", e_user_error);} Return $ Ret; }}?> If you use the Trigger_Error () function to replace Die (), your code will be more advantageous in handling errors, which is easier to handle errors for customer programmers. Trigger_ERROR () accepts an error message and a constant as a parameter. Constants are:
constant
meaning
E_USER_ERROR
A Fatal Error
E_USER_WARNING
A non-fat error
E_USER_NOTICE
A report this may not represent an error
You can design an error handler, then define a processor selection function set_error_handler () to use this error handler.
Code list index2.php second half?>
PHP // PHP 4 Function Cmderrorhandler ($ Errnum, $ Errmsg, $ File, $ LINENO) {IF ($ Errnum == E_USER_ERROR) {Print "Error: $ Errmsg / N"; Print "File: $ file / n Print "Line: $ lineno / n"; exit ();}} $ handler = set_error_handler ('cmderrorhandler'); $ mgr = new commandmanager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); $ cmd-> eXecute ();?>
SET_ERROR_HANDLER () accepts a function name as a parameter. If an error is triggered, this function in the parameter is called to handle errors. Functions need to pass four parameters: error flag, error message, error file, number of rows at the error. You can also pass a set of arrays to set_error_handler (). The first element in the array must be the object that the error processor will call, the second element is the name of the error handling function.
It can be seen that our error processor is quite simple and improved. However, although you can add some functions to the error processor, such as logging error information, output Debug data, etc., which is still an overworked error handling path. Your choice is limited to an error that has been considered. For example, capture an E_USER_ERROR error, if you want, you may not stop the execution of the script (not using exit () and die ()), but if you do this, you may cause some very subtle bug, which should have been suspended Execute it. (2) Return to the wrong mark
The error handling of the script hierarchy is relatively rough but it is useful. Despite this, we sometimes need greater flexibility. We can use the way to return to the wrong identifier to tell the customer code "Error happened!". This will be determined by handing the program to continue, how to continue to hand over the customer code.
Here we improve the previous example to return a script to perform an error (False is a commonly used nice choice).
Code list index3.php?>
php // php 4 Require_once ('cmd_php4 / command.php'); Class CommandManager {var $ cmddir = "cmd_php4"; function getcommandObject ($ cmd) {$ PATH = "{$ this-> cmddir} / {$ CMD} .php "; if (! file_exists ($ path)) {RETURN FALSE;} Require_once $ path; if (! Class_exists ($ cmd)) {Return False;} $ RET = New $ cmd (); if (! IS_A ($ RET, 'Command')) {RETURN FALSE;} RETURN $ RET;}}?>
This means that you can handle multiple errors according to the environment, and will stop the execution of the program immediately when the first error occurs.
Code list?>
php // PHP 4 $ mgr = new commandManager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); if (is_bool ($ cmd)) {Die ("Error getting command / n");} Else {$ cmd-> EXECUTE ();}?>
Or just record errors:
Code list?>
php // PHP 4 $ mgr = new commandManager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); if (is_bool ($ cmd)) {Error_Log ("Error getting command / n", 0) } Else {$ cmd-> execute ();}?>
The advantage of using the error flag like "false" is intuitive, but the amount of information given is not enough, we can't know which link is on display, resulting in returning false. You can set an error in the error, which outputs an error message after generating an error.
Code list index4.php?>
php // PHP 4 Require_once ('cmd_php4 / command.php'); class commandmanager {var $ cmddir = "cmd_php4"; var $ error_str = ""; Function SETERROR ($ Method, $ msg) {$ this-> Error_Str = get_class ($ this). ": $ msg";} function error () {Return $ this-> error_str;} Function getcommandObject ($ cmd) {$ Path = "{$ THIS -> cmddir} / {$ cmd} .php "; if (! file_exists ($ path)) {$ this-> setError (__ function__," cannot Find $ PATH / N "); Return False;} Require_Once $ PATH; IF (! Class_exists ($ cmd)) {$ this-> setError (__ function__, "class $ cmd does not exist"); return false;} $ RET = New $ cmd (); if (! is_a ($ RET, 'Command ')) {$ This-> setError (__ function__, "$ cmd is not a command"); return false;} RETURN $ RET;}}?> This simple mechanism allows setError () records the error message. Other code can get information about script errors by error (). You should draw this feature and put it in a basic class, and other cates are inherited from this class. This can be uniformly handled errors, otherwise it may appear confusion. I have seen some programs in different classes using getErRorstr (), getError (), and error () and other functions.
However, in actual development, all classes in the program are difficult to inherit from the same classes, unless otherwise using the interface (Interface), some sub-class itself cannot be implemented, but that is already PHP5 content. Just as we will mention, PHP5 provides a better solution.
(3) Using PEAR to handle errors
You can also use PEAR to handle errors. When an error occurs, a pear_error object will be returned. The following code uses a static method PEAR :: ISERROR () to verify this object. If the error does happen, the returned Pear_Error object will provide all the information you need:
Here we have modified the getcommandObject () method to return a pear_error object.
Code list index_pear.php?>
php // PHP 4 Require_once ("pear.php"); Require_once ('cmd_php4 / command.php'); Class CommandManager {var $ cmddir = "cmd_php4"; function getcommandObject ($ cmd) {$ PATH = { $ this-> cmddir} / {$ cmd} .php "; if (! file_exists ($ PATH)) {Return Pear :: RaiseError (" Cannot Find $ PATH ");} Require_once $ PATH; if (! Class_exists ($ CMD)) {RETURN PEAR :: RaiseError ("Class $ CMD Does Not Exist";} $ RET = New $ CMD (); if (! is_A ($ RET, 'Command')) {Return Pear :: raiseerror "$ cmd is not a command");} RETURN $ RET;}}?> pear_error is both an error mark and contains an error related specific information, which is very useful for customer code.
Code list?>
php // PHP 4 $ mgr = new commandManager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); if (Pear :: ISERROR ($ cmd)) {Print $ cmd-> getMessage (). "/ n"; exit;} $ cmd-> execute ();?>
Although returning an object value can make you flexibly reflect the problem in the program, it also has side effects of "pollution interface". Because the exit of the getcommandObject () method is now two, and it is an object, sometimes confusion.
PHP does not allow you to specify a type method or function to return the type of value, although this is more convenient. The getcommandObject () method is the Command object or a pear_error object. If you want to get a certain type of return value, you must verify the type of the value each time. A cautious code will be full of complex inspection conditions, if each type is tested.
The following is a PEAR :: DB Customer Code that does not consider the wrong inspection
Code list?>
php // php 4 require_once ("db.php"); $ db = "errors.db"; unlink ($ db); $ dsn = "sqlite: // / ($ db"; $ db = db: : Connect ($ DSN); $ CREATE_RESULT = $ DB-> Query ("Create Table Records") ")") ")") ")") ")"); $ INSERT_RESULT = $ DB-> Query ("INSERT INTO RECORDS VALUES (" 'OK Computer') "); $ query_result = $ db-> query (" Select * from records "); $ row = $ query_result-> fetchrow (db_fetchmode_assoc); Print $ ROW ['Name']." / n "; $ Drop_Result = $ DB-> Query ("Drop Table Records"); $ db-> disconnect ();?> The readability is very well, the operation is clear - we choose a database, create a new data table and insert a line of records, then take Back data, then discard the data sheet.
Let's take a look at the following use error judgment:
Code list?>
php // php 4 require_once ("db.php"); $ db = "errors.db"; unlink ($ db); $ dsn = "sqlite: // / ($ db"; $ db = db: : Connect ($ DSN); IF (DB :: ISERROR ($ dB)) {Die ($ db-> getMessage ());} $ crete_result = $ db-> query ("Create Table Records (Name Varchar (255) ); if (db :: iesrror ($ create_result)) {Die ($ CREATE_RESULT-> getMessage ());} $ INSERT_RESULT = $ dB-> Query ("INSERT INTO RECORDS VALUES") ; if (DB :: ISERROR ($ INSERT_RESULT)) {Die ($ INSERT_RESULT-> getMessage ());} $ query_result = $ db-> query ("Select * from records"); if (db :: iesrror ($ Query_Result)) {Die ($ query_result-> getMessage ()); $ row = $ query_result-> fetchrow (db_fetchmode_assoc); Print $ ROW ['Name']. "/ n"; $ Drop_Result = $ DB-> Query ("DROP TABLE RECORDS"); if (DB :: ISERROR ($ DROP_RESULT)) {Die ($ DROP_RESULT-> getMessage ());} $ db-> disconnect ();?>
Obviously, after the error inspection, the code appears to be extremely complicated. In fact, the above code is simpler than the code in the actual project, but it is enough to explain the complexity of the error inspection. Some discussions, we need a such error handling mechanism:
Allow methods give an error label to the customer code
Detailed information for providing program errors
Let you judge multiple error conditions and separate your error reports and program processing processes.
The return value must be an independent type and will not be confused with the type of normal returns.
The exception mechanism of PHP happens to meet the above requirements.
PHP5 exception mechanism
According to the above discussion, PHP built-in exceptions need to have the following members:
It can be seen that the structure of the Exception class is similar to Pear_Error. When you encounter an error in your script, you can build your exception object:
$ EX = New Exception ("COULD NOT OPEN $ THIS-> File");
The constructor of the Exception class will accept an error message and an error code.
Use throw keywords
After establishing an Exception object, you can return objects, but should not be used like this, better ways to replace it with the THROW keyword. Throw is used to throw an exception:
Throw New Exception ("My Message", 44);
Throw will abort the script and make the relevant Exception objects available to the customer code.
The following is an improved getcommandObject () method:
(See the next page code list). In the code, we used the reflection API to determine if the given class belongs to the Command type. Executing this script under the wrong path will report this error:
Fatal error: Uncaught exception 'Exception' with message 'Can not find command / xrealcommand.php' in /home/xyz/BasicException.php:10 Stack trace: # 0 /home/xyz/BasicException.php(26): CommandManager-> GetcommandObject ('XRealcommand') # 1 {main} thrown in /Home/xyz/basicexception.php on line 10
By default, the exception is thrown to cause a Fatal Error. This means that there is a security mechanism with an abnormal class. And only one error mark cannot have such a function. Processing error tag failed to continue using the error value of your script.
TRY-CATCH statement
Code list index_php5.php?>
php // php 5 Require_once ('cmd_php5 / command.php'); class commandManager {private $ cmddir = "cmd_php5"; function getcommandObject ($ cmd) {$ PATH = "{$ this-> cmddir} / {$ CMD} .php "; if (! file_exists ($ PATH)) {throw new exception;} Require_once $ path; if (! Class_exists ($ cmd)) {throw new Exception (" Class $ CMD Does Not Exist ");} $ CMD); if (! $ class-> issubclassof (New Reflection")) {throw new exception ("$ cmd is not a commnd") } RETURN NEW $ cmd ();}}?> For further processing exceptions, we need to use the try-catch statement - including the TRY statement and at least one CATCH statement. Any code that calls that may throw an exception should use the TRY statement. The Catch statement is used to handle an exception that may be thrown. The following shows how we handle the exceptions thrown by getcommandObject ():
Code list index_php5.php second half?>
php // php 5 try {$ mgr = new commandmanager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); $ cmd-> execute ();} catch (Exception $ E) {Print $ E -> GetMessage (); exit ();}?>
It can be seen that we can avoid the value returned by the error mark by combining the use of the THROW keyword and the TRY-CATCH statement. Because "exception" itself is a type of PHP built with any other object, it will not be confused.
If you throw an exception, the script in the TRY sentence will stop execution, then turn to the script in the execution of the CATCH statement.
If the exception throws, it will generate a Fatal Error if it is not captured.
Handling multiple errors
There is no difference between the error identification or objects returned by abnormal processing and our traditional practice - the value of the error logo or object is not checked. Let us handle the CommandManager and check if the Command directory exists in the constructor.
Code list index_php5_2.php?>
Note: Although the first half of the code has two (more), the first half of the code may be wrong, although compared to index_php5.php, this code is exactly the same.
Code list index_php5_2.php second half?>
php // php 5 try {$ mgr = new commandmanager (); // potential error $ cmd = mgr-> getcommandObject ('realcommand'); // another potential error $ cmd-> execute ();} catch (Exception $ E) {// Handle Either Error Here Print $ E-> getMessage (); exit ();}?>
There is also a place we have not mentioned. How do we distinguish between different types of errors? For example, we may want to handle errors that cannot be found in a way, and use another way to handle illegal Command classes. The Exception class can accept an optional integer error identifier, which is a method that distinguishes different errors in the CATCH statement.
Code list index_php5_3.php?>
}?>
By passing the parameters of cmdman_illegalclass_error and cmdman_general_error, we can make customer code distinguish between different types of errors and define different processing strategies.
Code list index_php5_3.php?>
php // php 5 try {$ mgr = new commandManager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); $ cmd-> execute ();} catch (exception $ e) {= E-> getcode () == comMMandManager :: cmdman_general_ERROR) {// no way of recovering die ($ E-> getMessage ());} else if ($ e-> getcode () == commandManager :: cmdman_illegalclass_ERROR) { Error_Log ($ E-> getMessage ()); Print "Attempting Recovery / N"; // Perhaps Attempt To Invoke A Default Command?}?> We can also use another way to achieve this effect - from the most fundamental The Exception class has a subclass representing different types of abnormalities, then throws and captures.
Subclass of the Exception class
There are two reasons to let us want to derive neutron classes from the Exception class:
1. Let subclasses provide custom features;
2. Different types of abnormalities;
Look at the second example. When using the CommandManager class, we may generate two errors: one is a general error. If the directory is not found, the other is not found or unable to generate a Command object. This way we need to define two exception subtypes for these two errors.
Code list index_php5_4.php?>
The COMMANDMANAGER class now has the ability to handle these various error, we can add new Catch statements to match different error types.
Code list index_php5_4.php second half?>
php // php 5 try {$ mgr = new commandmanager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); $ cmd-> execute ();} catch (commandmanagerexception $ e) {die ($ E-> getMessage ();} catch (IllegalCommandexception $ E) {Error_Log ($ E-> getMessage ()); Print "Attempting Recovery / N"; // Perhaps Attempt to Invoke A Default Command?} catch (Exception $ e) {Print "Unexpected Exception / N"; DIE ($ E-> getMessage ());}?> If the CommandManager object throws a CommandManageRexception exception, the corresponding CATCH statement will be executed. The parameters of each Catch statement are like a matching test, and the first matching catch statement will execute without performing other catch statements. So, you should write a catch statement for a specific exception, and write it back in the same CATCH statement for the general.
If you write a catch statement:
Code list?>
php // PHP 5 try {$ mgr = new commandmanager (); $ cmd = $ mgr-> getcommandObject ('realcommand'); $ cmd-> execute ();} catch (Exception $ e) {print "unExpected Exception / N "; DIE ($ E-> getMessage ());} catch ($ e-> getMessage ());} catch (IllegalCommandexception $ E) {Error_Log ($ E-> GetMessage ()); Print "Attempting Recovery / N"; // Perhaps Attempt to Invoke A Default Command?}?>
Then when an exception is thrown, no matter what the first CATCH statement catch (Exception $ E) {} will always be executed. This is because any exception is from an Exception type, so it always matches. This does not meet the purpose of different processing we have to do with specific exceptions.
If you capture a specific type of exception, the exception of capturing the Exception type in the last catch statement is a good idea. The last Catch statement represents catch-all, capturing all exceptions. Of course, you may not want to handle an exception immediately, but want to pass it, then process it properly. This is another place in PHP's abnormal mechanism that needs to be discussed.
Unusual transmission, re-throwing abnormalities
If we have triggered an exception that can't be handled immediately, there is a good solution - check the responsibility of the abnormality back to the code to call the current method, that is, throw an exception again in the catch statement (turn throw abnormal). This will transfer an exception to the call chain of the method.
Code list index_php5_5.php?>
At the end we will pass all anomaly objects outside IllegalCommandException to a higher level class to delay processing. We throw an exception again in the last CATCH statement.
} catch (Exception $ E) {throw $ E;}
If we caught a LlegalCommandexception exception, we first try to call a default Command. By setting the $ cmdstr property to a $ defaultcmd equivalent and repeatedly calls the RunCommand method. If the $ cmdstr and $ defaultcmd strings have equal, we don't have anything to do, then throw anomalies. In fact, in the Zend Engine II will automatically turn all unmatched exceptions, we can omit the last Catch statement. This is the last line of CommandManager :: getcommandObject ():
Return $ Class-> newinstance ();
I have to pay attention to two problems here:
First, we assume that the constructor of the CommandManager class does not require parameters. In this article, we do not discuss the case of the need for parameters.
Second, we assume that the Command class (herein refers to our custom RealCommand) can be instantiated. If the constructor is declared as private, this statement will throw a ReflectionException object. If we don't have an exception in the RequestHelper, this exception will be passed to the code called Requesthelper. If an exception is implicitly thrown, you'd better describe it in the document, or manually throw this exception - such other programmers use your code to use your code to handle possible abnormal conditions.
Get an abnormally related information
The following is the code used to format the output exception information:
Code list index_php5_6.php?>
php // php 5 Class Front {static function main () {Try {$ helper = new requesthelper (array (cmd => 'realcommand ")); $ helper-> runcemmand ();} catch (Exception $ E) {Print "
If your Realcommand class cannot be instantiated (for example, you declare its constructor as private) and run the above code, you can get this output:
REFLECTIONEXCEPTION
Access to non-public constructor of class realcommand (0)
File: c: /myweb/apache/htdocs/php5exception/index_php5_4.phpline: 31 # 0: /myweb/apache/htdocs/php5Exception/index_php5_5.php (25): commandManager-> getcommandObject ()
# 1: /myweb/apache/htdocs/php5exception/index_php5_6.php (10): Requesthelper-> Runcommand ('realcommand') # 2: /myweb/apache/htdocs/php5Exception/index_php5_6.php (23): Front :: Main ()
# 3 {main}
You can see getFile () and getLine () return to an exception file and the number of files. The getStackasstring () method returns the details of each layer that causes an exceptional method. From # 0 until # 4, we can clearly see the path of the abnormality.
You can also use the GetTrace () method to get this information, getTrace () returns a multi-dimensional array. The first element contains an abnormal position, and the second element contains the details of the external method call until the highest layer call. Each element of this array itself is also an array that contains the following key names:
Key
meaning
File
Produce an abnormal file
Line
A number of ways to generate an abnormal class
FUNCTION
Production of abnormal functions / methods
Class
Calling method
Type
Call Type: '::' Indicates Call Static Class Members '->' Indication Instantiated Call (First Instantiate Generate Object Repair)
argg
Parameters accepted by class methods
to sum up
The abnormal mechanism provides several very critical benefits:
(1) By focusing on the error handling in the catch statement, you can independently handle the error process from the application process. This also enhances the readability of the code, it looks pleasant. I usually take a very strict strategy to capture all exceptions and abort the script. This allows the desired additional elastic while achieving security and useful anomaly management.
(2) Rehabilitation abnormality, pass the abnormal data stream from the low layer to the high layer, that is, the abnormality is transmitted back to the most suitable place to handle exceptions. This looks a bit strange, but in the actual situation, we are often unable to decide how to handle it at an abnormality.
(3) Throw / catch provided by the abnormal mechanism avoids the direct return error identifier, the return value of the method can be determined by your class. When other programmers use your code, you can specify a form of returning to his hopes without tired.
About author
Matt Zandstra is a writer and is also working in server-side programming and training technical consultants. He and his partner Max Guglielmino operates Corrrosive - a technology company that provides open source, development standard training, planning, and development.
Matt is also the author of Sams "TEACH YourSelf PHP in 24 Hours". He is now writing a book about object-oriented programming in PHP.
Haohappy
Email: haohappy@msn.com
BLOG: blog.9cbs.net/haohappy2004
http://www.openphp.cn/index.php?module=article&id=2