<?php
/**
* @module Q
*/
class Q_Exception extends Exception
{
/**
* Represents a complex exception thrown in Q. It may contain more details.
* @class Q_Exception
* @constructor
* @extends Exception
* @param {array} [$params=array()] Array of parameters for the exception.
* Used in the exception message, etc.
* To access them later, call $e->params()
* You can also provide a string here, which will
* then be the exception message.
* @param {array} [$inputFields=array()] Array of names of input fields to which the exception applies.
* @param {array} [$code=null] Optionally pass the error code here to override the default
*/
function __construct(
$params = array(),
$inputFields = array(),
$code = null,
$file = null,
$line = null,
$trace = null,
$traceAsString = null)
{
if (is_string($inputFields)) {
$inputFields = array($inputFields);
}
$this->inputFields = $inputFields;
if (isset($file)) {
$this->file = $file;
}
if (isset($line)) {
$this->line = $line;
}
if (isset($trace)) {
$this->trace = $trace;
}
if (isset($traceAsString)) {
$this->traceAsString = $traceAsString;
}
if (is_string($params)) {
parent::__construct($params, is_numeric($code) ? $code : -1);
if (isset($code)) {
$this->code = $code; // Q_Exception allows non-numeric codes
}
return;
}
$this->params = is_array($params) ? $params : array();
$className = get_class($this);
$message = isset(self::$messages[$className])
? Q::interpolate(self::$messages[$className], $this->params)
: $className;
$code = isset($code) ? $code :
(isset(self::$codes[$className]) ? self::$codes[$className] : 1);
parent::__construct($message, $code);
}
/**
* Construct a Q_Exception object from an Exception.
* @method $exception
* @param $exception
* @return {Q_Exception}
*/
static function fromException($exception)
{
$result = new Q_Exception();
$fields = get_object_vars($exception);
foreach ($fields as $k => $v) {
$result->$k = $v;
}
return $result;
}
/**
* @method __get
* @param {string} $param
* @return {mixed}
*/
function __get($param)
{
return isset($this->params[$param])
? $this->params[$param]
: null;
}
/**
* @method __set
* @param {string} $param
* @param {mixed} $value
*/
function __set($param, $value) {
$this->params[$param] = $value;
}
/**
* Returns the array of parameters the exception was created with.
* @method params
* @return {array}
*/
function params()
{
return $this->params;
}
/**
* Returns the array of names of input fields the exception applies to.
* @method inputFields
* @return {array}
*/
function inputFields()
{
return $this->inputFields;
}
/**
* Registers a new exception class that extends Q_Exception
* @method add
* @static
* @param {string} $className The name of the exception class.
* @param {string} $message The description of the error. Will be eval()-ed before rendering,
* so it can include references to parameters, such as $my_param.
* @param {array} [$rethrowDestClasses=array()] The name of the class that should handle this exception,
* @param {string} [$baseClassName=null] Here you can pass the name of different base class than Q_Exception
* should it be thrown. Almost all catch() blocks in your code should use
* `Q_Exception::rethrow($e, __CLASS__)` as the first statement,
* if the exception might have to be re-thrown further down the stack.
*/
static function add(
$className,
$message,
$rethrowDestClasses = array(),
$baseClassName = null)
{
if (is_string($rethrowDestClasses)) {
$baseClassName = $rethrowDestClasses;
$rethrowDestClasses = array();
}
static $exception_code = 10000;
++$exception_code; // TODO: improve this somehow
self::$codes[$className] = $exception_code;
self::$messages[$className] = $message;
self::$rethrowDestClasses[$className] = $rethrowDestClasses;
if (is_array($baseClassName)) {
$rethrowDestClasses = $baseClassName;
$baseClassName = null;
}
if (isset($baseClassName)) {
self::$baseClasses[$className] = isset($baseClass) ? $baseClass : 'Q_Exception';
}
}
/**
* Use in your catch() blocks if you think the exception
* might have to be thrown further down the stack.
* @method rethrow
* @static
* @param {Exception} $exception The exception that was thrown. It is analyzed for
* whether it should be re-thrown.
* @param {string} $currentClass If the $rethrowDestClasses was specified in Q_Exception::add
* when creating this exception's class, and it does not contain
* $currentClass, this function throws the exception again.
*/
static function rethrow(
$exception,
$currentClass)
{
if (!is_callable(array($exception, 'rethrowDestClasses'))) {
return false;
}
$rdc = $exception->rethrowDestClasses();
if ($rdc and !in_array($currentClass, $rdc)) {
throw $exception;
}
}
/**
* Returns an array of classes to rethrow to, if any.
* @method rethrowDestClasses
* @return {array}
*/
function rethrowDestClasses()
{
$className = get_class($this);
if (isset(self::$rethrowDestClasses[$className])) {
return self::$rethrowDestClasses[$className];
}
return array();
}
/**
* Returns the trace array, can be overridden. Use this in your exception reporting.
* This is the default implementation.
* @method getTraceEx
* @return {array}
*/
function getTraceEx()
{
if (isset($this->trace)) {
return $this->trace;
}
return parent::getTrace();
}
/**
* Returns trace as string, can be overridden. Use this in your exception reporting.
* This is the default implementation.
* @method getTraceAsStringEx
* @return {string}
*/
function getTraceAsStringEx()
{
if (isset($this->traceAsString)) {
return $this->traceAsString;
}
return parent::getTraceAsString();
}
/**
* Converts an exception or array of exceptions to an array
* @method toArray
* @static
* @param {Exception|array} $exceptions The exception object or array of exceptions to convert
* @return {array}
*/
static function toArray($exceptions)
{
if (empty($exceptions)) {
return array();
}
$array_was_passed = true;
if (!is_array($exceptions)) {
$exceptions = array($exceptions);
$array_was_passed = false;
}
$results = array();
$show_fal = Q_Config::get('Q', 'exception', 'showFileAndLine', true);
$show_trace = Q_Config::get('Q', 'exception', 'showTrace', true);
foreach ($exceptions as $e) {
if (!($e instanceof Exception)) {
continue;
}
$message = $e->getMessage();
$code = $e->getCode();
if ($show_fal) {
$line = $e->getLine();
$file = $e->getFile();
}
if ($show_trace) {
if (is_callable(array($e, 'getTraceEx'))) {
$trace = $e->getTraceEx();
} else {
$trace = $e->getTrace();
}
}
$fields = null;
if (is_callable(array($e, 'inputFields'))) {
$fields = $e->inputFields();
}
$classname = get_class($e);
$results[] = compact('message', 'code', 'line', 'file', 'trace', 'fields', 'classname');
}
if ($array_was_passed) {
return $results;
} else {
$ret = reset($results);
return $ret ? $ret : array();
}
}
/**
* Return colored text that you can output in logs or text mode
* @return {string}
*/
function colored()
{
return self::coloredString($this);
}
/**
* Return colored text that you can output in logs or text mode
* Pass an exception or
* @param {string|Exception} $exception The exception or an exception message. If the later, you must pass three more arguments.
* @param {string} [$file]
* @param {string} [$line]
* @param {string} [$trace]
* @return {string}
*/
static function coloredString($message, $file=null, $line=null, $trace=null)
{
if ($message instanceof Exception) {
$e = $message;
$traceString = is_callable(array($e, 'getTraceAsStringEx'))
? $e->getTraceAsStringEx()
: $e->getTraceAsString();
return self::coloredString(
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$traceString
);
}
$colors = Q_Config::get('Q', 'exception', 'colors', array());
Q::autoload('Q_Utils');
$fields = array(
'message' => $message,
'fileAndLine' => "in $file ($line)",
'trace' => $trace
);
foreach ($fields as $f => $v) {
$c0 = isset($colors[$f][0]) ? $colors[$f][0] : null;
$c1 = isset($colors[$f][1]) ? $colors[$f][1] : null;
$fields[$f] = Q_Utils::colored($v, $c0, $c1);
}
$reset = Q_Utils::colored("", "", "");
return "$fields[message]\n\n$fields[fileAndLine]\n$fields[trace]\n";
}
/**
* @property $params
* @protected
* @type array
*/
public $params = array();
/**
* @property $inputFields
* @protected
* @type array
*/
public $inputFields = array();
/**
* @property $codes
* @protected
* @type array
*/
protected static $codes = array();
/**
* @property $messages
* @protected
* @type array
*/
protected static $messages = array();
/**
* @property $rethrowDestClasses
* @protected
* @type array
*/
protected static $rethrowDestClasses = array();
/**
* @property $baseClasses
* @protected
* @type array
*/
protected static $baseClasses = array();
/**
* @property $trace
* @protected
* @type array
*/
protected $trace = null;
/**
* @property $traceAsString
* @protected
* @type string
*/
protected $traceAsString = null;
/**
* @property $code
* @protected
* @type string
*/
protected $code = null;
}