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