You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Exception.php 15KB

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