1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\Exception; 38 39/** 40 * Class \Hoa\Exception\Idle. 41 * 42 * `\Hoa\Exception\Idle` is the mother exception class of libraries. The only 43 * difference between `\Hoa\Exception\Idle` and its directly child 44 * `\Hoa\Exception` is that the latter fires events after beeing constructed. 45 * 46 * @copyright Copyright © 2007-2017 Hoa community 47 * @license New BSD License 48 */ 49class Idle extends \Exception 50{ 51 /** 52 * Delay processing on arguments. 53 * 54 * @var array 55 */ 56 protected $_tmpArguments = null; 57 58 /** 59 * Arguments to format message. 60 * 61 * @var array 62 */ 63 protected $_arguments = null; 64 65 /** 66 * Backtrace. 67 * 68 * @var array 69 */ 70 protected $_trace = null; 71 72 /** 73 * Previous. 74 * 75 * @var \Exception 76 */ 77 protected $_previous = null; 78 79 /** 80 * Original message. 81 * 82 * @var string 83 */ 84 protected $_rawMessage = null; 85 86 87 88 /** 89 * Create an exception. 90 * An exception is built with a formatted message, a code (an ID) and an 91 * array that contains the list of formatted strings for the message. If 92 * chaining, we can add a previous exception. 93 * 94 * @param string $message Formatted message. 95 * @param int $code Code (the ID). 96 * @param array $arguments Arguments to format message. 97 * @param \Exception $previous Previous exception in chaining. 98 */ 99 public function __construct( 100 $message, 101 $code = 0, 102 $arguments = [], 103 \Exception $previous = null 104 ) { 105 $this->_tmpArguments = $arguments; 106 parent::__construct($message, $code, $previous); 107 $this->_rawMessage = $message; 108 $this->message = @vsprintf($message, $this->getArguments()); 109 110 return; 111 } 112 113 /** 114 * Get the backtrace. 115 * Do not use \Exception::getTrace() any more. 116 * 117 * @return array 118 */ 119 public function getBacktrace() 120 { 121 if (null === $this->_trace) { 122 $this->_trace = $this->getTrace(); 123 } 124 125 return $this->_trace; 126 } 127 128 /** 129 * Get previous. 130 * Do not use \Exception::getPrevious() any more. 131 * 132 * @return \Exception 133 */ 134 public function getPreviousThrow() 135 { 136 if (null === $this->_previous) { 137 $this->_previous = $this->getPrevious(); 138 } 139 140 return $this->_previous; 141 } 142 143 /** 144 * Get arguments for the message. 145 * 146 * @return array 147 */ 148 public function getArguments() 149 { 150 if (null === $this->_arguments) { 151 $arguments = $this->_tmpArguments; 152 153 if (!is_array($arguments)) { 154 $arguments = [$arguments]; 155 } 156 157 foreach ($arguments as &$value) { 158 if (null === $value) { 159 $value = '(null)'; 160 } 161 } 162 163 $this->_arguments = $arguments; 164 unset($this->_tmpArguments); 165 } 166 167 return $this->_arguments; 168 } 169 170 /** 171 * Get the raw message. 172 * 173 * @return string 174 */ 175 public function getRawMessage() 176 { 177 return $this->_rawMessage; 178 } 179 180 /** 181 * Get the message already formatted. 182 * 183 * @return string 184 */ 185 public function getFormattedMessage() 186 { 187 return $this->getMessage(); 188 } 189 190 /** 191 * Get the source of the exception (class, method, function, main etc.). 192 * 193 * @return string 194 */ 195 public function getFrom() 196 { 197 $trace = $this->getBacktrace(); 198 $from = '{main}'; 199 200 if (!empty($trace)) { 201 $t = $trace[0]; 202 $from = ''; 203 204 if (isset($t['class'])) { 205 $from .= $t['class'] . '::'; 206 } 207 208 if (isset($t['function'])) { 209 $from .= $t['function'] . '()'; 210 } 211 } 212 213 return $from; 214 } 215 216 /** 217 * Raise an exception as a string. 218 * 219 * @param bool $previous Whether raise previous exception if exists. 220 * @return string 221 */ 222 public function raise($previous = false) 223 { 224 $message = $this->getFormattedMessage(); 225 $trace = $this->getBacktrace(); 226 $file = '/dev/null'; 227 $line = -1; 228 $pre = $this->getFrom(); 229 230 if (!empty($trace)) { 231 $file = isset($trace['file']) ? $trace['file'] : null; 232 $line = isset($trace['line']) ? $trace['line'] : null; 233 } 234 235 $pre .= ': '; 236 237 try { 238 $out = 239 $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 240 'in ' . $this->getFile() . ' at line ' . 241 $this->getLine() . '.'; 242 } catch (\Exception $e) { 243 $out = 244 $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 245 'in ' . $file . ' around line ' . $line . '.'; 246 } 247 248 if (true === $previous && 249 null !== $previous = $this->getPreviousThrow()) { 250 $out .= 251 "\n\n" . ' ⬇' . "\n\n" . 252 'Nested exception (' . get_class($previous) . '):' . "\n" . 253 ($previous instanceof self 254 ? $previous->raise(true) 255 : $previous->getMessage()); 256 } 257 258 return $out; 259 } 260 261 /** 262 * Catch uncaught exception (only \Hoa\Exception\Idle and children). 263 * 264 * @param \Throwable $exception The exception. 265 * @return void 266 * @throws \Throwable 267 */ 268 public static function uncaught($exception) 269 { 270 if (!($exception instanceof self)) { 271 throw $exception; 272 } 273 274 while (0 < ob_get_level()) { 275 ob_end_flush(); 276 } 277 278 echo 279 'Uncaught exception (' . get_class($exception) . '):' . "\n" . 280 $exception->raise(true); 281 282 return; 283 } 284 285 /** 286 * String representation of object. 287 * 288 * @return string 289 */ 290 public function __toString() 291 { 292 return $this->raise(); 293 } 294 295 /** 296 * Enable uncaught exception handler. 297 * This is restricted to Hoa's exceptions only. 298 * 299 * @param bool $enable Enable. 300 * @return mixed 301 */ 302 public static function enableUncaughtHandler($enable = true) 303 { 304 if (false === $enable) { 305 return restore_exception_handler(); 306 } 307 308 return set_exception_handler(function ($exception) { 309 return self::uncaught($exception); 310 }); 311 } 312} 313