]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/ErrorManager.php
More infiltration of new object-based HTML generation.
[SourceForge/phpwiki.git] / lib / ErrorManager.php
1 <?php rcs_id('$Id: ErrorManager.php,v 1.11 2002-01-21 06:55:47 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      * Get postponed errors, formatted as HTML.
71      *
72      * This also flushes the postponed error queue.
73      *
74      * @return string HTML describing any queued errors. 
75      */
76     function getPostponedErrorsAsHTML() {
77         ob_start();
78         $this->flushPostponedErrors();
79         $html = ob_get_contents();
80         ob_end_clean();
81
82         if (!$html)
83             return false;
84         
85         return HTML::div(array('class' => 'errors'),
86                          HTML::h4("PHP Warnings"),
87                          new RawXml($html));
88     }
89     
90     /**
91      * Push a custom error handler on the handler stack.
92      *
93      * Sometimes one is performing an operation where one expects
94      * certain errors or warnings. In this case, one might not want
95      * these errors reported in the normal manner. Installing a custom
96      * error handler via this method allows one to intercept such
97      * errors.
98      *
99      * An error handler installed via this method should be either a
100      * function or an object method taking one argument: a PhpError
101      * object.
102      *
103      * The error handler should return either:
104      * <dl>
105      * <dt> False <dd> If it has not handled the error. In this case,
106      *                 error processing will proceed as if the handler
107      *                 had never been called: the error will be passed
108      *                 to the next handler in the stack, or the
109      *                 default handler, if there are no more handlers
110      *                 in the stack.
111      *
112      * <dt> True <dd> If the handler has handled the error. If the
113      *                error was a non-fatal one, no further processing
114      *                will be done. If it was a fatal error, the
115      *                ErrorManager will still terminate the PHP
116      *                process (see setFatalHandler.)
117      *
118      * <dt> A PhpError object <dd> The error is not considered
119      *                             handled, and will be passed on to
120      *                             the next handler(s) in the stack
121      *                             (or the default handler). The
122      *                             returned PhpError need not be the
123      *                             same as the one passed to the
124      *                             handler. This allows the handler to
125      *                             "adjust" the error message.
126      * </dl>
127      * @access public
128      * @param $handler WikiCallback  Handler to call.
129      */
130     function pushErrorHandler($handler) {
131         array_unshift($this->_handlers, $handler);
132     }
133
134     /**
135      * Pop an error handler off the handler stack.
136      * @access public
137      */
138     function popErrorHandler() {
139         return array_shift($this->_handlers);
140     }
141
142     /**
143      * Set a termination handler.
144      *
145      * This handler will be called upon fatal errors. The handler
146      * gets passed one argument: a PhpError object describing the
147      * fatal error.
148      *
149      * @access public
150      * @param $handler WikiCallback  Callback to call on fatal errors.
151      */
152     function setFatalHandler($handler) {
153         $this->_fatal_handler = $handler;
154     }
155
156     /**
157      * Handle an error.
158      *
159      * The error is passed through any registered error handlers, and
160      * then either reported or postponed.
161      *
162      * @access public
163      * @param $error object A PhpError object.
164      */
165     function handleError($error) {
166         static $in_handler;
167
168         if (!empty($in_handler)) {
169             echo "<p>ErrorManager: "._("error while handling error:")."</p>\n";
170             echo $error->printError();
171             return;
172         }
173         $in_handler = true;
174
175         foreach ($this->_handlers as $handler) {
176             $result = $handler->call($error);
177             if (!$result) {
178                 continue;       // Handler did not handle error.
179             }
180             elseif (is_object($result)) {
181                 // handler filtered the result. Still should pass to
182                 // the rest of the chain.
183                 if ($error->isFatal()) {
184                     // Don't let handlers make fatal errors non-fatal.
185                     $result->errno = $error->errno;
186                 }
187                 $error = $result;
188             }
189             else {
190                 // Handler handled error.
191                 if (!$error->isFatal()) {
192                     $in_handler = false;
193                     return;
194                 }
195                 break;
196             }
197         }
198
199         // Error was either fatal, or was not handled by a handler.
200         // Handle it ourself.
201         if ($error->isFatal()) {
202             $this->_die($error);
203         }
204         else if (($error->errno & error_reporting()) != 0) {
205             if  (($error->errno & $this->_postpone_mask) != 0) {
206                 $this->_postponed_errors[] = $error;
207             }
208             else {
209                 $error->printError();
210             }
211         }
212         $in_handler = false;
213     }
214
215     /**
216      * @access private
217      */
218     function _die($error) {
219         $error->printError();
220         $this->_flush_errors();
221         if ($this->_fatal_handler)
222             $this->_fatal_handler->call($error);
223         exit -1;
224     }
225
226     /**
227      * @access private
228      */
229     function _flush_errors($keep_mask = 0) {
230         $errors = &$this->_postponed_errors;
231         foreach ($errors as $key => $error) {
232             if (($error->errno & $keep_mask) != 0)
233                 continue;
234             unset($errors[$key]);
235             $error->printError();
236         }
237     }
238 }
239
240 /**
241  * Global error handler for class ErrorManager.
242  *
243  * This is necessary since PHP's set_error_handler() does not allow
244  * one to set an object method as a handler.
245  * 
246  * @access private
247  */
248 function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline) 
249 {
250     global $ErrorManager;
251     $error = new PhpError($errno, $errstr, $errfile, $errline);
252     $ErrorManager->handleError($error);
253 }
254
255
256 /**
257  * A class representing a PHP error report.
258  *
259  * @see The PHP documentation for set_error_handler at
260  *      http://php.net/manual/en/function.set-error-handler.php .
261  */
262 class PhpError {
263     /**
264      * The PHP errno
265      */
266     var $errno;
267
268     /**
269      * The PHP error message.
270      */
271     var $errstr;
272
273     /**
274      * The source file where the error occurred.
275      */
276     var $errfile;
277
278     /**
279      * The line number (in $this->errfile) where the error occured.
280      */
281     var $errline;
282
283     /**
284      * Construct a new PhpError.
285      * @param $errno   int
286      * @param $errstr  string
287      * @param $errfile string
288      * @param $errline int
289      */
290     function PhpError($errno, $errstr, $errfile, $errline) {
291         $this->errno   = $errno;
292         $this->errstr  = $errstr;
293         $this->errfile = $errfile;
294         $this->errline = $errline;
295     }
296
297     /**
298      * Determine whether this is a fatal error.
299      * @return boolean True if this is a fatal error.
300      */
301     function isFatal() {
302         return ($this->errno & (EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
303     }
304
305     /**
306      * Determine whether this is a warning level error.
307      * @return boolean
308      */
309     function isWarning() {
310         return ($this->errno & EM_WARNING_ERRORS) != 0;
311     }
312
313     /**
314      * Determine whether this is a notice level error.
315      * @return boolean
316      */
317     function isNotice() {
318         return ($this->errno & EM_NOTICE_ERRORS) != 0;
319     }
320
321     /**
322      * Get a printable, HTML, message detailing this error.
323      * @return string The detailed error message.
324      */
325     function getDetail() {
326         if ($this->isNotice())
327             $what = 'Notice';
328         else if ($this->isWarning())
329             $what = 'Warning';
330         else
331             $what = 'Fatal';
332
333         $errfile = ereg_replace('^' . getcwd() . '/', '', $this->errfile);
334
335         $lines = explode("\n", $this->errstr);
336         $errstr = htmlspecialchars(array_shift($lines));
337         foreach ($lines as $key => $line)
338             $lines[$key] = "<li>" . htmlspecialchars($line) . "</li>";
339         if ($lines)
340             $errstr .= "<ul>\n" . join("\n", $lines) . "\n</ul>";
341         
342         return sprintf("<p class='error'>%s:%d: %s[%d]: %s</p>\n",
343                        htmlspecialchars($errfile),
344                        $this->errline, $what, $this->errno,
345                        $errstr);
346     }
347
348     /**
349      * Print an HTMLified version of this error.
350      * @see getDetail
351      */
352     function printError() {
353         echo $this->getDetail();
354     }
355 }
356
357 if (!isset($GLOBALS['ErrorManager'])) {
358     $GLOBALS['ErrorManager'] = new ErrorManager;
359 }
360
361 // (c-file-style: "gnu")
362 // Local Variables:
363 // mode: php
364 // tab-width: 8
365 // c-basic-offset: 4
366 // c-hanging-comment-ender-p: nil
367 // indent-tabs-mode: nil
368 // End:
369 ?>