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