Exception.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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. * @link http://pear.php.net/package/PEAR
  17. * @since File available since Release 1.3.3
  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
  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
  91. * @since Class available since Release 1.3.3
  92. *
  93. */
  94. class PEAR_Exception extends Exception
  95. {
  96. const OBSERVER_PRINT = -2;
  97. const OBSERVER_TRIGGER = -4;
  98. const OBSERVER_DIE = -8;
  99. protected $cause;
  100. private static $_observers = array();
  101. private static $_uniqueid = 0;
  102. private $_trace;
  103. /**
  104. * Supported signatures:
  105. * - PEAR_Exception(string $message);
  106. * - PEAR_Exception(string $message, int $code);
  107. * - PEAR_Exception(string $message, Exception $cause);
  108. * - PEAR_Exception(string $message, Exception $cause, int $code);
  109. * - PEAR_Exception(string $message, PEAR_Error $cause);
  110. * - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
  111. * - PEAR_Exception(string $message, array $causes);
  112. * - PEAR_Exception(string $message, array $causes, int $code);
  113. * @param string exception message
  114. * @param int|Exception|PEAR_Error|array|null exception cause
  115. * @param int|null 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('exception cause must be Exception, ' .
  127. 'array, or PEAR_Error');
  128. }
  129. }
  130. $code = $p3;
  131. if (is_array($p2) && isset($p2['message'])) {
  132. // fix potential problem of passing in a single warning
  133. $p2 = array($p2);
  134. }
  135. $this->cause = $p2;
  136. } else {
  137. $code = null;
  138. $this->cause = null;
  139. }
  140. parent::__construct($message, $code);
  141. $this->signal();
  142. }
  143. /**
  144. * @param mixed $callback - A valid php callback, see php func is_callable()
  145. * - A PEAR_Exception::OBSERVER_* constant
  146. * - An array(const PEAR_Exception::OBSERVER_*,
  147. * mixed $options)
  148. * @param string $label The name of the observer. Use this if you want
  149. * to remove it later with removeObserver()
  150. */
  151. public static function addObserver($callback, $label = 'default')
  152. {
  153. self::$_observers[$label] = $callback;
  154. }
  155. public static function removeObserver($label = 'default')
  156. {
  157. unset(self::$_observers[$label]);
  158. }
  159. /**
  160. * @return int unique identifier for an observer
  161. */
  162. public static function getUniqueId()
  163. {
  164. return self::$_uniqueid++;
  165. }
  166. private function signal()
  167. {
  168. foreach (self::$_observers as $func) {
  169. if (is_callable($func)) {
  170. call_user_func($func, $this);
  171. continue;
  172. }
  173. settype($func, 'array');
  174. switch ($func[0]) {
  175. case self::OBSERVER_PRINT :
  176. $f = (isset($func[1])) ? $func[1] : '%s';
  177. printf($f, $this->getMessage());
  178. break;
  179. case self::OBSERVER_TRIGGER :
  180. $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
  181. trigger_error($this->getMessage(), $f);
  182. break;
  183. case self::OBSERVER_DIE :
  184. $f = (isset($func[1])) ? $func[1] : '%s';
  185. die(printf($f, $this->getMessage()));
  186. break;
  187. default:
  188. trigger_error('invalid observer type', E_USER_WARNING);
  189. }
  190. }
  191. }
  192. /**
  193. * Return specific error information that can be used for more detailed
  194. * error messages or translation.
  195. *
  196. * This method may be overridden in child exception classes in order
  197. * to add functionality not present in PEAR_Exception and is a placeholder
  198. * to define API
  199. *
  200. * The returned array must be an associative array of parameter => value like so:
  201. * <pre>
  202. * array('name' => $name, 'context' => array(...))
  203. * </pre>
  204. * @return array
  205. */
  206. public function getErrorData()
  207. {
  208. return array();
  209. }
  210. /**
  211. * Returns the exception that caused this exception to be thrown
  212. * @access public
  213. * @return Exception|array The context of the exception
  214. */
  215. public function getCause()
  216. {
  217. return $this->cause;
  218. }
  219. /**
  220. * Function must be public to call on caused exceptions
  221. * @param array
  222. */
  223. public function getCauseMessage(&$causes)
  224. {
  225. $trace = $this->getTraceSafe();
  226. $cause = array('class' => get_class($this),
  227. 'message' => $this->message,
  228. 'file' => 'unknown',
  229. 'line' => 'unknown');
  230. if (isset($trace[0])) {
  231. if (isset($trace[0]['file'])) {
  232. $cause['file'] = $trace[0]['file'];
  233. $cause['line'] = $trace[0]['line'];
  234. }
  235. }
  236. $causes[] = $cause;
  237. if ($this->cause instanceof PEAR_Exception) {
  238. $this->cause->getCauseMessage($causes);
  239. } elseif ($this->cause instanceof Exception) {
  240. $causes[] = array('class' => get_class($this->cause),
  241. 'message' => $this->cause->getMessage(),
  242. 'file' => $this->cause->getFile(),
  243. 'line' => $this->cause->getLine());
  244. } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
  245. $causes[] = array('class' => get_class($this->cause),
  246. 'message' => $this->cause->getMessage(),
  247. 'file' => 'unknown',
  248. 'line' => 'unknown');
  249. } elseif (is_array($this->cause)) {
  250. foreach ($this->cause as $cause) {
  251. if ($cause instanceof PEAR_Exception) {
  252. $cause->getCauseMessage($causes);
  253. } elseif ($cause instanceof Exception) {
  254. $causes[] = array('class' => get_class($cause),
  255. 'message' => $cause->getMessage(),
  256. 'file' => $cause->getFile(),
  257. 'line' => $cause->getLine());
  258. } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
  259. $causes[] = array('class' => get_class($cause),
  260. 'message' => $cause->getMessage(),
  261. 'file' => 'unknown',
  262. 'line' => 'unknown');
  263. } elseif (is_array($cause) && isset($cause['message'])) {
  264. // PEAR_ErrorStack warning
  265. $causes[] = array(
  266. 'class' => $cause['package'],
  267. 'message' => $cause['message'],
  268. 'file' => isset($cause['context']['file']) ?
  269. $cause['context']['file'] :
  270. 'unknown',
  271. 'line' => isset($cause['context']['line']) ?
  272. $cause['context']['line'] :
  273. 'unknown',
  274. );
  275. }
  276. }
  277. }
  278. }
  279. public function getTraceSafe()
  280. {
  281. if (!isset($this->_trace)) {
  282. $this->_trace = $this->getTrace();
  283. if (empty($this->_trace)) {
  284. $backtrace = debug_backtrace();
  285. $this->_trace = array($backtrace[count($backtrace)-1]);
  286. }
  287. }
  288. return $this->_trace;
  289. }
  290. public function getErrorClass()
  291. {
  292. $trace = $this->getTraceSafe();
  293. return $trace[0]['class'];
  294. }
  295. public function getErrorMethod()
  296. {
  297. $trace = $this->getTraceSafe();
  298. return $trace[0]['function'];
  299. }
  300. public function __toString()
  301. {
  302. if (isset($_SERVER['REQUEST_URI'])) {
  303. return $this->toHtml();
  304. }
  305. return $this->toText();
  306. }
  307. public function toHtml()
  308. {
  309. $trace = $this->getTraceSafe();
  310. $causes = array();
  311. $this->getCauseMessage($causes);
  312. $html = '<table style="border: 1px" cellspacing="0">' . "\n";
  313. foreach ($causes as $i => $cause) {
  314. $html .= '<tr><td colspan="3" style="background: #ff9999">'
  315. . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
  316. . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
  317. . 'on line <b>' . $cause['line'] . '</b>'
  318. . "</td></tr>\n";
  319. }
  320. $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
  321. . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
  322. . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
  323. . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
  324. foreach ($trace as $k => $v) {
  325. $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
  326. . '<td>';
  327. if (!empty($v['class'])) {
  328. $html .= $v['class'] . $v['type'];
  329. }
  330. $html .= $v['function'];
  331. $args = array();
  332. if (!empty($v['args'])) {
  333. foreach ($v['args'] as $arg) {
  334. if (is_null($arg)) $args[] = 'null';
  335. elseif (is_array($arg)) $args[] = 'Array';
  336. elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
  337. elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
  338. elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
  339. else {
  340. $arg = (string)$arg;
  341. $str = htmlspecialchars(substr($arg, 0, 16));
  342. if (strlen($arg) > 16) $str .= '&hellip;';
  343. $args[] = "'" . $str . "'";
  344. }
  345. }
  346. }
  347. $html .= '(' . implode(', ',$args) . ')'
  348. . '</td>'
  349. . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
  350. . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
  351. . '</td></tr>' . "\n";
  352. }
  353. $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
  354. . '<td>{main}</td>'
  355. . '<td>&nbsp;</td></tr>' . "\n"
  356. . '</table>';
  357. return $html;
  358. }
  359. public function toText()
  360. {
  361. $causes = array();
  362. $this->getCauseMessage($causes);
  363. $causeMsg = '';
  364. foreach ($causes as $i => $cause) {
  365. $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
  366. . $cause['message'] . ' in ' . $cause['file']
  367. . ' on line ' . $cause['line'] . "\n";
  368. }
  369. return $causeMsg . $this->getTraceAsString();
  370. }
  371. }