Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
<?php
namespace PhpConsole;
/**
* Overrides PHP errors and exceptions handlers, so all errors, exceptions and debug messages will be sent to PHP Console client
* By default all handled errors and exceptions will be passed to previously defined handlers
*
* You will need to install Google Chrome extension "PHP Console"
* https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
*
* @package PhpConsole
* @version 3.1
* @link http://consle.com
* @author Sergey Barbushin http://linkedin.com/in/barbushin
* @copyright © Sergey Barbushin, 2011-2013. All rights reserved.
* @license http://www.opensource.org/licenses/BSD-3-Clause "The BSD 3-Clause License"
*/
class Handler {
const ERRORS_RECURSION_LIMIT = 3;
/** @var Handler */
protected static $instance;
/** @var Connector */
protected $connector;
protected $isStarted = false;
protected $isHandling = false;
protected $errorsHandlerLevel;
protected $handleErrors = true;
protected $handleExceptions = true;
protected $callOldHandlers = true;
protected $oldErrorsHandler;
protected $oldExceptionsHandler;
protected $recursiveHandlingLevel = 0;
/**
* @return static
*/
public static function getInstance() {
if(!self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
protected function __construct() {
$this->connector = Connector::getInstance();
}
/**
* @codeCoverageIgnore
*/
private final function __clone() {
}
/**
* Start errors & exceptions handlers
* @throws \Exception
*/
public function start() {
if($this->isStarted) {
throw new \Exception(get_called_class() . ' is already started, use ' . get_called_class() . '::getInstance()->isStarted() to check it.');
}
$this->isStarted = true;
if($this->handleErrors) {
$this->initErrorsHandler();
}
if($this->handleExceptions) {
$this->initExceptionsHandler();
}
}
/**
* Validate that method is called before start. Required for handlers configuration methods.
* @throws \Exception
*/
protected function checkIsCalledBeforeStart() {
if($this->isStarted) {
throw new \Exception('This method can be called only before ' . get_class($this) . '::start()');
}
}
/**
* Enable or disable errors handler
* @param bool $isEnabled
*/
public function setHandleErrors($isEnabled) {
$this->checkIsCalledBeforeStart();
$this->handleErrors = $isEnabled; // @codeCoverageIgnore
}
/**
* Enable or disable exceptions handler
* @param bool $isEnabled
*/
public function setHandleExceptions($isEnabled) {
$this->checkIsCalledBeforeStart();
$this->handleExceptions = $isEnabled; // @codeCoverageIgnore
}
/**
* Enable or disable calling overridden errors & exceptions
* @param bool $isEnabled
*/
public function setCallOldHandlers($isEnabled) {
$this->callOldHandlers = $isEnabled;
}
/**
* @return Connector
*/
public function getConnector() {
return $this->connector;
}
/**
* Check if PHP Console handler is started
* @return bool
*/
public function isStarted() {
return $this->isStarted;
}
/**
* Override PHP exceptions handler to PHP Console handler
*/
protected function initExceptionsHandler() {
$this->oldExceptionsHandler = set_exception_handler(array($this, 'handleException'));
}
/**
* Set custom errors handler level like E_ALL ^ E_STRICT
* But, anyway, it's strongly recommended to configure ignore some errors type in PHP Console extension options
* IMPORTANT: previously old error handler will be called only with new errors level
* @param int $level see http://us1.php.net/manual/ru/function.error-reporting.php
*/
public function setErrorsHandlerLevel($level) {
$this->checkIsCalledBeforeStart();
$this->errorsHandlerLevel = $level;
}
/**
* Override PHP errors handler to PHP Console handler
*/
protected function initErrorsHandler() {
ini_set('display_errors', false);
error_reporting($this->errorsHandlerLevel ? : E_ALL | E_STRICT);
$this->oldErrorsHandler = set_error_handler(array($this, 'handleError'));
register_shutdown_function(array($this, 'checkFatalErrorOnShutDown'));
$this->connector->registerFlushOnShutDown();
}
/**
* Method is called by register_shutdown_function(), it's required to handle fatal PHP errors. Never call it manually.
* @codeCoverageIgnore
*/
public function checkFatalErrorOnShutDown() {
$error = error_get_last();
if($error) {
ini_set('memory_limit', memory_get_usage(true) + 1000000); // if memory limit exceeded
$this->callOldHandlers = false;
$this->handleError($error['type'], $error['message'], $error['file'], $error['line'], null, 1);
}
}
/**
* Handle error data
* @param int|null $code
* @param string|null $text
* @param string|null $file
* @param int|null $line
* @param null $context
* @param int|array $ignoreTraceCalls Ignore tracing classes by name prefix `array('PhpConsole')` or fixed number of calls to ignore
*/
public function handleError($code = null, $text = null, $file = null, $line = null, $context = null, $ignoreTraceCalls = 0) {
if(!$this->isStarted || error_reporting() === 0 || $this->isHandlingDisabled() || ($this->errorsHandlerLevel && !($code & $this->errorsHandlerLevel))) {
return;
}
$this->onHandlingStart();
$this->connector->getErrorsDispatcher()->dispatchError($code, $text, $file, $line, is_numeric($ignoreTraceCalls) ? $ignoreTraceCalls + 1 : $ignoreTraceCalls);
if($this->oldErrorsHandler && $this->callOldHandlers) {
call_user_func_array($this->oldErrorsHandler, array($code, $text, $file, $line, $context));
}
$this->onHandlingComplete();
}
/**
* Method is called before handling any error or exception
*/
protected function onHandlingStart() {
$this->recursiveHandlingLevel++;
}
/**
* Method is called after handling any error or exception
*/
protected function onHandlingComplete() {
$this->recursiveHandlingLevel--;
}
/**
* Check if errors/exception handling is disabled
* @return bool
*/
protected function isHandlingDisabled() {
return $this->recursiveHandlingLevel >= static::ERRORS_RECURSION_LIMIT;
}
/**
* Handle exception object
* @param \Exception|\Throwable $exception
*/
public function handleException($exception) {
if(!$this->isStarted || $this->isHandlingDisabled()) {
return;
}
try {
$this->onHandlingStart();
$this->connector->getErrorsDispatcher()->dispatchException($exception);
if($this->oldExceptionsHandler && $this->callOldHandlers) {
call_user_func($this->oldExceptionsHandler, $exception);
}
}
catch(\Throwable $internalException) {
$this->handleException($internalException);
}
catch(\Exception $internalException) {
$this->handleException($internalException);
}
$this->onHandlingComplete();
}
/**
* Handle debug data
* @param mixed $data
* @param string|null $tags Tags separated by dot, e.g. "low.db.billing"
* @param int|array $ignoreTraceCalls Ignore tracing classes by name prefix `array('PhpConsole')` or fixed number of calls to ignore
*/
public function debug($data, $tags = null, $ignoreTraceCalls = 0) {
if($this->connector->isActiveClient()) {
$this->connector->getDebugDispatcher()->dispatchDebug($data, $tags, is_numeric($ignoreTraceCalls) ? $ignoreTraceCalls + 1 : $ignoreTraceCalls);
}
}
}