PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/application/libraries/PhpConsole.php

https://bitbucket.org/amila17/unituition
PHP | 316 lines | 241 code | 44 blank | 31 comment | 49 complexity | fe391bcddd2d5def1719fb87718c7f9a MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * @see http://code.google.com/p/php-console
  5. * @author Barbushin Sergey http://linkedin.com/in/barbushin
  6. * @version 1.1
  7. *
  8. * @desc Sending messages to Google Chrome console
  9. *
  10. * You need to install Google Chrome extension:
  11. * https://chrome.google.com/extensions/detail/nfhmhhlpfleoednkpnnnkolmclajemef
  12. *
  13. * All class properties and methods are static because it's required to let
  14. * them work on script shutdown when FATAL error occurs.
  15. *
  16. */
  17. class PhpConsole {
  18. public static $ignoreRepeatedEvents = false;
  19. public static $callOldErrorHandler = true;
  20. public static $callOldExceptionsHandler = true;
  21. /**
  22. * @var PhpConsole
  23. */
  24. protected static $instance;
  25. protected $handledMessagesHashes = array();
  26. protected $sourceBasePath;
  27. protected function __construct($handleErrors, $handleExceptions, $sourceBasePath) {
  28. if($handleErrors) {
  29. $this->initErrorsHandler();
  30. }
  31. if($handleExceptions) {
  32. $this->initExceptionsHandler();
  33. }
  34. if($sourceBasePath) {
  35. $this->sourceBasePath = realpath($sourceBasePath);
  36. }
  37. $this->initClient();
  38. }
  39. public static function start($handleErrors = true, $handleExceptions = true, $sourceBasePath = null) {
  40. if(!self::$instance) {
  41. self::$instance = new PhpConsole($handleErrors, $handleExceptions, $sourceBasePath);
  42. }
  43. }
  44. protected function handle(PhpConsoleEvent $event) {
  45. if(self::$ignoreRepeatedEvents) {
  46. $eventHash = md5($event->message . $event->file . $event->line);
  47. if(in_array($eventHash, $this->handledMessagesHashes)) {
  48. return;
  49. }
  50. else {
  51. $this->handledMessagesHashes[] = $eventHash;
  52. }
  53. }
  54. $this->sendEventToClient($event);
  55. }
  56. public function __destruct() {
  57. self::flushMessagesBuffer();
  58. }
  59. /***************************************************************
  60. CLIENT
  61. **************************************************************/
  62. const clientProtocolCookie = 'phpcslc';
  63. const serverProtocolCookie = 'phpcsls';
  64. const serverProtocol = 4;
  65. const messagesCookiePrefix = 'phpcsl_';
  66. const cookiesLimit = 50;
  67. const cookieSizeLimit = 4000;
  68. const messageLengthLimit = 2500;
  69. protected static $isEnabledOnClient;
  70. protected static $isDisabled;
  71. protected static $messagesBuffer = array();
  72. protected static $bufferLength = 0;
  73. protected static $messagesSent = 0;
  74. protected static $cookiesSent = 0;
  75. protected static $index = 0;
  76. protected function initClient() {
  77. if(self::$isEnabledOnClient === null) {
  78. self::setEnabledOnServer();
  79. self::$isEnabledOnClient = self::isEnabledOnClient();
  80. if(self::$isEnabledOnClient) {
  81. ob_start();
  82. }
  83. }
  84. }
  85. protected static function isEnabledOnClient() {
  86. return isset($_COOKIE[self::clientProtocolCookie]) && $_COOKIE[self::clientProtocolCookie] == self::serverProtocol;
  87. }
  88. protected static function setEnabledOnServer() {
  89. if(!isset($_COOKIE[self::serverProtocolCookie]) || $_COOKIE[self::serverProtocolCookie] != self::serverProtocol) {
  90. self::setCookie(self::serverProtocolCookie, self::serverProtocol);
  91. }
  92. }
  93. protected function sendEventToClient(PhpConsoleEvent $event) {
  94. if(!self::$isEnabledOnClient || self::$isDisabled) {
  95. return;
  96. }
  97. $message = array();
  98. $message['type'] = strpos($event->tags, 'error,') === 0 ? 'error' : 'debug';
  99. $message['subject'] = $event->type;
  100. $message['text'] = substr($event->message, 0, self::messageLengthLimit);
  101. if($event->file) {
  102. $message['source'] = ($this->sourceBasePath ? preg_replace('!^' . preg_quote($this->sourceBasePath, '!') . '!', '', $event->file) : $event->file) . ($event->line ? ':' . $event->line : '');
  103. }
  104. if($event->trace) {
  105. $traceArray = $this->convertTraceToArray($event->trace, $event->file, $event->line);
  106. if($traceArray) {
  107. $message['trace'] = $traceArray;
  108. }
  109. }
  110. self::pushMessageToBuffer($message);
  111. if(strpos($event->tags, ',fatal')) {
  112. self::flushMessagesBuffer();
  113. }
  114. }
  115. protected function convertTraceToArray($traceData, $eventFile = null, $eventLine = null) {
  116. $trace = array();
  117. foreach($traceData as $call) {
  118. if((isset($call['class']) && $call['class'] == __CLASS__) || (!$trace && isset($call['file']) && $call['file'] == $eventFile && $call['line'] == $eventLine)) {
  119. $trace = array();
  120. continue;
  121. }
  122. $args = array();
  123. if(isset($call['args'])) {
  124. foreach($call['args'] as $arg) {
  125. if(is_object($arg)) {
  126. $args[] = get_class($arg);
  127. }
  128. elseif(is_array($arg)) {
  129. $args[] = 'Array';
  130. }
  131. else {
  132. $arg = var_export($arg, 1);
  133. $args[] = strlen($arg) > 12 ? substr($arg, 0, 8) . '...\'' : $arg;
  134. }
  135. }
  136. }
  137. if(isset($call['file']) && $this->sourceBasePath) {
  138. $call['file'] = preg_replace('!^' . preg_quote($this->sourceBasePath, '!') . '!', '', $call['file']);
  139. }
  140. $trace[] = (isset($call['file']) ? ($call['file'] . ':' . $call['line']) : '[internal call]') . ' - ' . (isset($call['class']) ? $call['class'] . $call['type'] : '') . $call['function'] . '(' . implode(', ', $args) . ')';
  141. }
  142. $trace = array_reverse($trace);
  143. foreach($trace as $i => &$call) {
  144. $call = '#' . ($i + 1) . ' ' . $call;
  145. }
  146. return $trace;
  147. }
  148. protected static function pushMessageToBuffer($message) {
  149. $encodedMessageLength = strlen(rawurlencode(json_encode($message)));
  150. if(self::$bufferLength + $encodedMessageLength > self::cookieSizeLimit) {
  151. self::flushMessagesBuffer();
  152. }
  153. self::$messagesBuffer[] = $message;
  154. self::$bufferLength += $encodedMessageLength;
  155. }
  156. protected static function getNextIndex() {
  157. return substr(number_format(microtime(1), 3, '', ''), -6) + self::$index++;
  158. }
  159. public static function flushMessagesBuffer() {
  160. if(self::$messagesBuffer) {
  161. self::sendMessages(self::$messagesBuffer);
  162. self::$bufferLength = 0;
  163. self::$messagesSent += count(self::$messagesBuffer);
  164. self::$messagesBuffer = array();
  165. self::$cookiesSent++;
  166. if(self::$cookiesSent == self::cookiesLimit) {
  167. self::$isDisabled = true;
  168. $message = array('type' => 'error', 'subject' => 'PHP CONSOLE', 'text' => 'MESSAGES LIMIT EXCEEDED BECAUSE OF COOKIES STORAGE LIMIT. TOTAL MESSAGES SENT: ' . self::$messagesSent, 'source' => __FILE__, 'notify' => 3);
  169. self::sendMessages(array($message));
  170. }
  171. }
  172. }
  173. protected static function setCookie($name, $value) {
  174. if(headers_sent($file, $line)) {
  175. throw new Exception('setcookie() failed because haders are sent (' . $file . ':' . $line . '). Try to use ob_start()');
  176. }
  177. setcookie($name, $value, null, '/');
  178. }
  179. protected static function sendMessages($messages) {
  180. self::setCookie(self::messagesCookiePrefix . self::getNextIndex(), json_encode($messages));
  181. }
  182. /***************************************************************
  183. ERRORS
  184. **************************************************************/
  185. protected $codesTags = array(E_ERROR => 'fatal', E_WARNING => 'warning', E_PARSE => 'fatal', E_NOTICE => 'notice', E_CORE_ERROR => 'fatal', E_CORE_WARNING => 'warning', E_COMPILE_ERROR => 'fatal', E_COMPILE_WARNING => 'warning', E_USER_ERROR => 'fatal', E_USER_WARNING => 'warning', E_USER_NOTICE => 'notice', E_STRICT => 'warning');
  186. protected $codesNames = array(E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', E_PARSE => 'E_PARSE', E_NOTICE => 'E_NOTICE', E_CORE_ERROR => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING', E_COMPILE_ERROR => 'E_COMPILE_ERROR', E_COMPILE_WARNING => 'E_COMPILE_WARNING', E_USER_ERROR => 'E_USER_ERROR', E_USER_WARNING => 'E_USER_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_STRICT => 'E_STRICT');
  187. protected $notCompitableCodes = array('E_RECOVERABLE_ERROR' => 'warning', 'E_DEPRECATED' => 'warning');
  188. protected $oldErrorHandler;
  189. protected function initErrorsHandler() {
  190. ini_set('display_errors', false);
  191. ini_set('html_errors', false);
  192. ini_set('ignore_repeated_errors', self::$ignoreRepeatedEvents);
  193. ini_set('ignore_repeated_source', self::$ignoreRepeatedEvents);
  194. foreach($this->notCompitableCodes as $code => $tag) {
  195. if(defined($code)) {
  196. $this->codesTags[constant($code)] = $tag;
  197. $this->codesNames[constant($code)] = $code;
  198. }
  199. }
  200. $this->oldErrorHandler = set_error_handler(array($this, 'handleError'));
  201. register_shutdown_function(array($this, 'checkFatalError'));
  202. }
  203. public function checkFatalError() {
  204. $error = error_get_last();
  205. if($error) {
  206. $this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
  207. }
  208. }
  209. public function handleError($code = null, $message = null, $file = null, $line = null) {
  210. if(error_reporting() == 0) { // if error has been supressed with an @
  211. return;
  212. }
  213. if(!$code) {
  214. $code = E_USER_ERROR;
  215. }
  216. $event = new PhpConsoleEvent();
  217. $event->tags = 'error,' . (isset($this->codesTags[$code]) ? $this->codesTags[$code] : 'warning');
  218. $event->message = $message;
  219. $event->type = isset($this->codesNames[$code]) ? $this->codesNames[$code] : $code;
  220. $event->file = $file;
  221. $event->line = $line;
  222. $event->trace = debug_backtrace();
  223. $this->handle($event);
  224. if(self::$callOldErrorHandler && $this->oldErrorHandler) {
  225. call_user_func_array($this->oldErrorHandler, array($code, $message, $file, $line));
  226. }
  227. }
  228. /***************************************************************
  229. EXCEPTIONS
  230. **************************************************************/
  231. protected $oldExceptionsHandler;
  232. protected function initExceptionsHandler() {
  233. $this->oldExceptionsHandler = set_exception_handler(array($this, 'handleException'));
  234. }
  235. public function handleException(Exception $exception) {
  236. $event = new PhpConsoleEvent();
  237. $event->message = $exception->getMessage();
  238. $event->tags = 'error,fatal,exception,' . get_class($exception);
  239. $event->type = get_class($exception);
  240. $event->file = $exception->getFile();
  241. $event->line = $exception->getLine();
  242. $event->trace = $exception->getTrace();
  243. $this->handle($event);
  244. // TODO: check if need to throw
  245. if(self::$callOldExceptionsHandler && $this->oldExceptionsHandler) {
  246. call_user_func($this->oldExceptionsHandler, $exception);
  247. }
  248. }
  249. /***************************************************************
  250. DEBUG
  251. **************************************************************/
  252. public static function debug($message, $tags = 'debug') {
  253. if(self::$instance) {
  254. $event = new PhpConsoleEvent();
  255. $event->message = $message;
  256. $event->tags = $tags;
  257. $event->type = $tags;
  258. self::$instance->handle($event);
  259. }
  260. }
  261. }
  262. class PhpConsoleEvent {
  263. public $message;
  264. public $type;
  265. public $tags;
  266. public $trace;
  267. public $file;
  268. public $line;
  269. }
  270. function debug($message, $tags = 'debug') {
  271. PhpConsole::debug($message, $tags);
  272. }