]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
Jeff's hacks II.
[SourceForge/phpwiki.git] / lib / ErrorManager.php
1 <?php rcs_id('$Id: ErrorManager.php,v 1.1 2001-09-18 19:16:23 dairiki Exp $');
2
3
4 define ('EM_FATAL_ERRORS',
5         E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR);
6 define ('EM_WARNING_ERRORS',
7         E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING);
8 define ('EM_NOTICE_ERRORS', E_NOTICE | E_USER_NOTICE);
9
10
11 /**
12  * A class which allows custom handling of PHP errors.
13  *
14  * This is a singleton class.  There should only be one instance
15  * of it --- you can access the one instance via $GLOBALS['ErrorManager'].
16  *
17  * FIXME: more docs.
18  */ 
19 class ErrorManager 
20 {
21     /**
22      * Constructor.
23      *
24      * As this is a singleton class, you should never call this.
25      * @access private
26      */
27     function ErrorManager() {
28         $this->_handlers = array();
29         $this->_fatal_handler = false;
30         $this->_postpone_mask = 0;
31         $this->_postponed_errors = array();
32         
33         set_error_handler('ErrorManager_errorHandler');
34     }
35
36     /**
37      * Get mask indicating which errors are currently being postponed.
38      * @access public
39      * @return int The current postponed error mask.
40      */
41     function getPostponedErrorMask() {
42         return $this->_postpone_mask;
43     }
44
45     /**
46      * Set mask indicating which errors to postpone.
47      *
48      * The default value of the postpone mask is zero (no errors postponed.)
49      *
50      * When you set this mask, any queue errors which do not match tne new
51      * mask are reported.
52      *
53      * @access public
54      * @param $newmask int The new value for the mask.
55      */
56     function setPostponedErrorMask($newmask) {
57         $this->_postpone_mask = $newmask;
58         $this->_flush_errors($newmask);
59     }
60
61     /**
62      * Report any queued error messages.
63      * @access public
64      */
65     function flushPostponedErrors() {
66         $this->_flush_errors();
67     }
68
69     /**
70      * Push a custom error handler on the handler stack.
71      *
72      * Sometimes one is performing an operation where one expects certain errors
73      * or warnings.  In this case, one might not want these errors reported
74      * in the normal manner.  Installing a custom error handler via this method
75      * allows one to intercept such errors.
76      *
77      * An error handler installed via this method should be either a
78      * function or an object method taking one argument: a PhpError object.
79      *
80      * The error handler should return either:
81      * <dl>
82      * <dt> False <dd> If it has not handled the error.  In this case, error
83      *       processing will proceed as if the handler had never been called:
84      *       the error will be passed to the next handler in the stack,
85      *       or the default handler, if there are no more handlers in the stack.
86      * <dt> True  <dd> If the handler has handled the error.  If the error was
87      *        a non-fatal one, no further processing will be done.
88      *        If it was a fatal error, the ErrorManager will still
89      *        terminate the PHP process (see setFatalHandler.)
90      * <dt> A PhpError object
91      *   <dd> The error is not considered
92      *        handled, and will be passed on to the next handler(s) in the stack
93      *        (or the default handler).
94      *        The returned PhpError need not be the same as the one passed to the
95      *        handler.  This allows the handler to "adjust" the error message.
96      *
97      * @access public
98      * @param $handler string or array
99      * To register a global function as a handler, just pass the functions name
100      * (as a string).  To register an object method as a handler, pass a array:
101      * the first element is the object, the second is the name of the method.
102      */
103     function pushErrorHandler($handler) {
104         array_unshift($this->_handlers, $handler);
105     }
106
107     /**
108      * Pop an error handler off the handler stack.
109      * @access public
110      */
111     function popErrorHandler() {
112         return array_shift($this->_handlers);
113     }
114
115     /**
116      * Set a termination handler.
117      *
118      * This handler will be called upon fatal errors.  The handler gets passed
119      * one argument: a PhpError object describing the fatal error.
120      *
121      * @access public
122      * @param $handler string or array
123      * To register a global function as a handler, just pass the functions name
124      * (as a string).  To register an object method as a handler, pass a array:
125      * the first element is the object, the second is the name of the method.
126      */
127     function setFatalHandler($handler) {
128         $this->_fatal_handler = $handler;
129     }
130
131     function _callHandler($handler, $error) {
132         if (is_string($handler)) {
133             return call_user_func($handler, $error);
134         }
135         else if (is_array($handler)) {
136             list($object, $method) = $handler;
137             if (method_exists($object, $method))
138                 return call_user_method($method, $object, $error);
139         }
140         echo "<div>ErrorManager::_callHandler: BAD HANDLER<br></div>\n";
141         return false;
142     }
143
144     /**
145      * Handle an error.
146      *
147      * The error is passed through any registered error handlers,
148      * and then either reported or postponed.
149      *
150      * @access public
151      * @param $error object A PhpError object.
152      */
153     function handleError($error) {
154         static $in_handler;
155         
156         if (!empty($in_handler)) {
157             echo "<p>ErrorManager: error while handling error:</p>\n";
158             echo $error->printError();
159             return;
160         }
161         $in_handler = true;
162         
163         foreach ($this->_handlers as $handler) {
164             $result = $this->_callHandler($handler, $error);
165             if (!$result) {
166                 continue;       // Handler did not handle error.
167             }
168             elseif (is_object($result)) {
169                 // handler filtered the result.  Still should pass to the
170                 // rest of the chain.
171                 if ($error->isFatal()) {
172                     // Don't let handlers make fatal errors non-fatal.
173                     $result->errno = $error->errno;
174                 }
175                 $error = $result;
176             }
177             else {
178                 // Handler handled error.
179                 if (!$error->isFatal()) {
180                     $in_handler = false;
181                     return;
182                 }
183                 break;
184             }
185         }
186
187         // Error was either fatal, or was not handled by a handler.
188         // Handle it ourself.
189         if ($error->isFatal()) {
190             $this->_die($error);
191         }
192         else if (($error->errno & error_reporting()) != 0) {
193             if (($error->errno & $this->_postpone_mask) != 0) {
194                 $this->_postponed_errors[] = $error;
195             }
196             else {
197                 $error->printError();
198             }
199         }
200         $in_handler = false;
201     }
202
203     /**
204      * @access private
205      */
206     function _die($error) {
207         $error->printError();
208         $this->_flush_errors();
209         if ($this->_fatal_handler)
210             $this->_callHandler($this->_fatal_handler, $error);
211         exit -1;
212     }
213         
214     /**
215      * @access private
216      */
217     function _flush_errors($keep_mask = 0) {
218         $errors = &$this->_postponed_errors;
219         foreach ($errors as $key => $error) {
220             if (($error->errno & $keep_mask) != 0)
221                 continue;
222             unset($errors[$key]);
223             $error->printError();
224         }
225     }
226 }
227
228 /**
229  * Global error handler for class ErrorManager.
230  *
231  * This is necessary since PHP's set_error_handler() does not allow one to
232  * set an object method as a handler.
233  * 
234  * @access private
235  */
236 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline) 
237 {
238     global $ErrorManager;
239     $error = new PhpError($errno, $errstr, $errfile, $errline);
240     $ErrorManager->handleError($error);
241 }
242
243
244 /**
245  * A class representing a PHP error report.
246  *
247  * @see The PHP documentation for set_error_handler at
248  *      http://php.net/manual/en/function.set-error-handler.php .
249  */
250 class PhpError {
251     /**
252      * The PHP errno
253      */
254     var $errno;
255
256     /**
257      * The PHP error message.
258      */
259     var $errstr;
260
261     /**
262      * The source file where the error occurred.
263      */
264     var $errfile;
265
266     /**
267      * The line number (in $this->errfile) where the error occured.
268      */
269     var $errline;
270
271     /**
272      * Construct a new PhpError.
273      * @param $errno int
274      * @param $errstr string
275      * @param $errfile string
276      * @param $errline int
277      */
278     function PhpError($errno, $errstr, $errfile, $errline) {
279         $this->errno = $errno;
280         $this->errstr = $errstr;
281         $this->errfile = $errfile;
282         $this->errline = $errline;
283     }
284
285     /**
286      * Determine whether this is a fatal error.
287      * @return boolean True if this is a fatal error.
288      */
289     function isFatal() {
290         return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
291     }
292
293     /**
294      * Determine whether this is a warning level error.
295      * @return boolean
296      */
297     function isWarning() {
298         return ($this->errno & EM_WARNING_ERRORS) != 0;
299     }
300
301     /**
302      * Determine whether this is a notice level error.
303      * @return boolean
304      */
305     function isNotice() {
306         return ($this->errno & EM_NOTICE_ERRORS) != 0;
307     }
308
309     /**
310      * Get a printable, HTML, message detailing this error.
311      * @return string The detailed error message.
312      */
313     function getDetail() {
314         if ($this->isNotice())
315             $what = 'Notice';
316         else if ($this->isWarning())
317             $what = 'Warning';
318         else
319             $what = 'Fatal';
320
321         $errfile = ereg_replace('^' . getcwd() . '/', '', $this->errfile);
322
323         $lines = explode("\n", $this->errstr);
324         $errstr = htmlspecialchars(array_shift($lines));
325         foreach ($lines as $key => $line)
326             $lines[$key] = "<li>" . htmlspecialchars($line) . "<\li>";
327         if ($lines)
328             $errstr .= "<ul>\n" . join("\n", $lines) . "\n</ul>";
329         
330         return sprintf("<p class='error'>%s:%d: %s[%d]: %s</p>\n",
331                        htmlspecialchars($errfile),
332                        $this->errline, $what, $this->errno,
333                        $errstr);
334     }
335
336     /**
337      * Print an HTMLified version of this error.
338      * @see getDetail
339      */
340     function printError() {
341         echo $this->getDetail();
342     }
343 }
344
345 if (!isset($GLOBALS['ErrorManager'])) {
346     $GLOBALS['ErrorManager'] = new ErrorManager;
347 }
348
349 // (c-file-style: "gnu")
350 // Local Variables:
351 // mode: php
352 // tab-width: 8
353 // c-basic-offset: 4
354 // c-hanging-comment-ender-p: nil
355 // indent-tabs-mode: nil
356 // End:   
357 ?>