MDL-23722 importing Exception from PEAR base 1.9.1
[moodle.git] / lib / pear / PEAR / Exception.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
3 /**
4  * PEAR_Exception
5  *
6  * PHP versions 4 and 5
7  *
8  * @category   pear
9  * @package    PEAR
10  * @author     Tomas V. V. Cox <cox@idecnet.com>
11  * @author     Hans Lellelid <hans@velum.net>
12  * @author     Bertrand Mansion <bmansion@mamasam.com>
13  * @author     Greg Beaver <cellog@php.net>
14  * @copyright  1997-2009 The Authors
15  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
16  * @version    CVS: $Id$
17  * @link       http://pear.php.net/package/PEAR
18  * @since      File available since Release 1.3.3
19  */
22 /**
23  * Base PEAR_Exception Class
24  *
25  * 1) Features:
26  *
27  * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
28  * - Definable triggers, shot when exceptions occur
29  * - Pretty and informative error messages
30  * - Added more context info available (like class, method or cause)
31  * - cause can be a PEAR_Exception or an array of mixed
32  *   PEAR_Exceptions/PEAR_ErrorStack warnings
33  * - callbacks for specific exception classes and their children
34  *
35  * 2) Ideas:
36  *
37  * - Maybe a way to define a 'template' for the output
38  *
39  * 3) Inherited properties from PHP Exception Class:
40  *
41  * protected $message
42  * protected $code
43  * protected $line
44  * protected $file
45  * private   $trace
46  *
47  * 4) Inherited methods from PHP Exception Class:
48  *
49  * __clone
50  * __construct
51  * getMessage
52  * getCode
53  * getFile
54  * getLine
55  * getTraceSafe
56  * getTraceSafeAsString
57  * __toString
58  *
59  * 5) Usage example
60  *
61  * <code>
62  *  require_once 'PEAR/Exception.php';
63  *
64  *  class Test {
65  *     function foo() {
66  *         throw new PEAR_Exception('Error Message', ERROR_CODE);
67  *     }
68  *  }
69  *
70  *  function myLogger($pear_exception) {
71  *     echo $pear_exception->getMessage();
72  *  }
73  *  // each time a exception is thrown the 'myLogger' will be called
74  *  // (its use is completely optional)
75  *  PEAR_Exception::addObserver('myLogger');
76  *  $test = new Test;
77  *  try {
78  *     $test->foo();
79  *  } catch (PEAR_Exception $e) {
80  *     print $e;
81  *  }
82  * </code>
83  *
84  * @category   pear
85  * @package    PEAR
86  * @author     Tomas V.V.Cox <cox@idecnet.com>
87  * @author     Hans Lellelid <hans@velum.net>
88  * @author     Bertrand Mansion <bmansion@mamasam.com>
89  * @author     Greg Beaver <cellog@php.net>
90  * @copyright  1997-2009 The Authors
91  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
92  * @version    Release: 1.9.1
93  * @link       http://pear.php.net/package/PEAR
94  * @since      Class available since Release 1.3.3
95  *
96  */
97 class PEAR_Exception extends Exception
98 {
99     const OBSERVER_PRINT = -2;
100     const OBSERVER_TRIGGER = -4;
101     const OBSERVER_DIE = -8;
102     protected $cause;
103     private static $_observers = array();
104     private static $_uniqueid = 0;
105     private $_trace;
107     /**
108      * Supported signatures:
109      *  - PEAR_Exception(string $message);
110      *  - PEAR_Exception(string $message, int $code);
111      *  - PEAR_Exception(string $message, Exception $cause);
112      *  - PEAR_Exception(string $message, Exception $cause, int $code);
113      *  - PEAR_Exception(string $message, PEAR_Error $cause);
114      *  - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
115      *  - PEAR_Exception(string $message, array $causes);
116      *  - PEAR_Exception(string $message, array $causes, int $code);
117      * @param string exception message
118      * @param int|Exception|PEAR_Error|array|null exception cause
119      * @param int|null exception code or null
120      */
121     public function __construct($message, $p2 = null, $p3 = null)
122     {
123         if (is_int($p2)) {
124             $code = $p2;
125             $this->cause = null;
126         } elseif (is_object($p2) || is_array($p2)) {
127             // using is_object allows both Exception and PEAR_Error
128             if (is_object($p2) && !($p2 instanceof Exception)) {
129                 if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
130                     throw new PEAR_Exception('exception cause must be Exception, ' .
131                         'array, or PEAR_Error');
132                 }
133             }
134             $code = $p3;
135             if (is_array($p2) && isset($p2['message'])) {
136                 // fix potential problem of passing in a single warning
137                 $p2 = array($p2);
138             }
139             $this->cause = $p2;
140         } else {
141             $code = null;
142             $this->cause = null;
143         }
144         parent::__construct($message, $code);
145         $this->signal();
146     }
148     /**
149      * @param mixed $callback  - A valid php callback, see php func is_callable()
150      *                         - A PEAR_Exception::OBSERVER_* constant
151      *                         - An array(const PEAR_Exception::OBSERVER_*,
152      *                           mixed $options)
153      * @param string $label    The name of the observer. Use this if you want
154      *                         to remove it later with removeObserver()
155      */
156     public static function addObserver($callback, $label = 'default')
157     {
158         self::$_observers[$label] = $callback;
159     }
161     public static function removeObserver($label = 'default')
162     {
163         unset(self::$_observers[$label]);
164     }
166     /**
167      * @return int unique identifier for an observer
168      */
169     public static function getUniqueId()
170     {
171         return self::$_uniqueid++;
172     }
174     private function signal()
175     {
176         foreach (self::$_observers as $func) {
177             if (is_callable($func)) {
178                 call_user_func($func, $this);
179                 continue;
180             }
181             settype($func, 'array');
182             switch ($func[0]) {
183                 case self::OBSERVER_PRINT :
184                     $f = (isset($func[1])) ? $func[1] : '%s';
185                     printf($f, $this->getMessage());
186                     break;
187                 case self::OBSERVER_TRIGGER :
188                     $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
189                     trigger_error($this->getMessage(), $f);
190                     break;
191                 case self::OBSERVER_DIE :
192                     $f = (isset($func[1])) ? $func[1] : '%s';
193                     die(printf($f, $this->getMessage()));
194                     break;
195                 default:
196                     trigger_error('invalid observer type', E_USER_WARNING);
197             }
198         }
199     }
201     /**
202      * Return specific error information that can be used for more detailed
203      * error messages or translation.
204      *
205      * This method may be overridden in child exception classes in order
206      * to add functionality not present in PEAR_Exception and is a placeholder
207      * to define API
208      *
209      * The returned array must be an associative array of parameter => value like so:
210      * <pre>
211      * array('name' => $name, 'context' => array(...))
212      * </pre>
213      * @return array
214      */
215     public function getErrorData()
216     {
217         return array();
218     }
220     /**
221      * Returns the exception that caused this exception to be thrown
222      * @access public
223      * @return Exception|array The context of the exception
224      */
225     public function getCause()
226     {
227         return $this->cause;
228     }
230     /**
231      * Function must be public to call on caused exceptions
232      * @param array
233      */
234     public function getCauseMessage(&$causes)
235     {
236         $trace = $this->getTraceSafe();
237         $cause = array('class'   => get_class($this),
238                        'message' => $this->message,
239                        'file' => 'unknown',
240                        'line' => 'unknown');
241         if (isset($trace[0])) {
242             if (isset($trace[0]['file'])) {
243                 $cause['file'] = $trace[0]['file'];
244                 $cause['line'] = $trace[0]['line'];
245             }
246         }
247         $causes[] = $cause;
248         if ($this->cause instanceof PEAR_Exception) {
249             $this->cause->getCauseMessage($causes);
250         } elseif ($this->cause instanceof Exception) {
251             $causes[] = array('class'   => get_class($this->cause),
252                               'message' => $this->cause->getMessage(),
253                               'file' => $this->cause->getFile(),
254                               'line' => $this->cause->getLine());
255         } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
256             $causes[] = array('class' => get_class($this->cause),
257                               'message' => $this->cause->getMessage(),
258                               'file' => 'unknown',
259                               'line' => 'unknown');
260         } elseif (is_array($this->cause)) {
261             foreach ($this->cause as $cause) {
262                 if ($cause instanceof PEAR_Exception) {
263                     $cause->getCauseMessage($causes);
264                 } elseif ($cause instanceof Exception) {
265                     $causes[] = array('class'   => get_class($cause),
266                                    'message' => $cause->getMessage(),
267                                    'file' => $cause->getFile(),
268                                    'line' => $cause->getLine());
269                 } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
270                     $causes[] = array('class' => get_class($cause),
271                                       'message' => $cause->getMessage(),
272                                       'file' => 'unknown',
273                                       'line' => 'unknown');
274                 } elseif (is_array($cause) && isset($cause['message'])) {
275                     // PEAR_ErrorStack warning
276                     $causes[] = array(
277                         'class' => $cause['package'],
278                         'message' => $cause['message'],
279                         'file' => isset($cause['context']['file']) ?
280                                             $cause['context']['file'] :
281                                             'unknown',
282                         'line' => isset($cause['context']['line']) ?
283                                             $cause['context']['line'] :
284                                             'unknown',
285                     );
286                 }
287             }
288         }
289     }
291     public function getTraceSafe()
292     {
293         if (!isset($this->_trace)) {
294             $this->_trace = $this->getTrace();
295             if (empty($this->_trace)) {
296                 $backtrace = debug_backtrace();
297                 $this->_trace = array($backtrace[count($backtrace)-1]);
298             }
299         }
300         return $this->_trace;
301     }
303     public function getErrorClass()
304     {
305         $trace = $this->getTraceSafe();
306         return $trace[0]['class'];
307     }
309     public function getErrorMethod()
310     {
311         $trace = $this->getTraceSafe();
312         return $trace[0]['function'];
313     }
315     public function __toString()
316     {
317         if (isset($_SERVER['REQUEST_URI'])) {
318             return $this->toHtml();
319         }
320         return $this->toText();
321     }
323     public function toHtml()
324     {
325         $trace = $this->getTraceSafe();
326         $causes = array();
327         $this->getCauseMessage($causes);
328         $html =  '<table style="border: 1px" cellspacing="0">' . "\n";
329         foreach ($causes as $i => $cause) {
330             $html .= '<tr><td colspan="3" style="background: #ff9999">'
331                . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
332                . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
333                . 'on line <b>' . $cause['line'] . '</b>'
334                . "</td></tr>\n";
335         }
336         $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
337                . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
338                . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
339                . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
341         foreach ($trace as $k => $v) {
342             $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
343                    . '<td>';
344             if (!empty($v['class'])) {
345                 $html .= $v['class'] . $v['type'];
346             }
347             $html .= $v['function'];
348             $args = array();
349             if (!empty($v['args'])) {
350                 foreach ($v['args'] as $arg) {
351                     if (is_null($arg)) $args[] = 'null';
352                     elseif (is_array($arg)) $args[] = 'Array';
353                     elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
354                     elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
355                     elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
356                     else {
357                         $arg = (string)$arg;
358                         $str = htmlspecialchars(substr($arg, 0, 16));
359                         if (strlen($arg) > 16) $str .= '&hellip;';
360                         $args[] = "'" . $str . "'";
361                     }
362                 }
363             }
364             $html .= '(' . implode(', ',$args) . ')'
365                    . '</td>'
366                    . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
367                    . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
368                    . '</td></tr>' . "\n";
369         }
370         $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
371                . '<td>{main}</td>'
372                . '<td>&nbsp;</td></tr>' . "\n"
373                . '</table>';
374         return $html;
375     }
377     public function toText()
378     {
379         $causes = array();
380         $this->getCauseMessage($causes);
381         $causeMsg = '';
382         foreach ($causes as $i => $cause) {
383             $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
384                    . $cause['message'] . ' in ' . $cause['file']
385                    . ' on line ' . $cause['line'] . "\n";
386         }
387         return $causeMsg . $this->getTraceAsString();
388     }