Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 349 lines (314 sloc) 10.665 kb
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
1 <?php
2 /**
3 * Lithium: the most rad php framework
4 *
5 * @copyright Copyright 2009, Union of RAD (http://union-of-rad.org)
6 * @license http://opensource.org/licenses/bsd-license.php The BSD License
7 */
8
9 namespace lithium\core;
10
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
11 use Exception;
e0223e1 @nateabele Using the `Filters` class to lazily apply error-trapping filters to s…
nateabele authored
12 use ErrorException;
13 use lithium\util\collection\Filters;
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
14
15 /**
16 * The `ErrorHandler` class allows PHP errors and exceptions to be handled in a uniform way. Using
17 * the `ErrorHandler`'s configuration, it is possible to have very broad but very tight control
18 * over error handling in your application.
19 *
20 * {{{ embed:lithium\tests\cases\core\ErrorHandlerTest::testExceptionCatching(2-7) }}}
21 *
22 * Using a series of cascading rules and handlers, it is possible to capture and handle very
23 * specific errors and exceptions.
24 */
25 class ErrorHandler extends \lithium\core\StaticObject {
26
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
27 /**
28 * Configuration parameters.
29 *
30 * @var array Config params
31 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
32 protected static $_config = array();
33
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
34 /**
35 * Error/exception handlers.
36 *
37 * @var array An array of closures that represent all invokable error/exception handlers.
38 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
39 protected static $_handlers = array();
40
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
41 /**
42 * Types of checks available for sorting & parsing errors/exceptions.
43 * Default checks are for `code`, `stack` and `message`.
44 *
45 * @var array Array of checks represented as closures, indexed by name.
46 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
47 protected static $_checks = array();
48
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
49 /**
50 * Currently registered exception handler.
51 *
52 * @var closure Closure representing exception handler.
53 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
54 protected static $_exceptionHandler = null;
55
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
56 /**
57 * State of error/exception handling.
58 *
59 * @var boolean True if custom error/exception handlers have been registered, false
60 * otherwise.
61 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
62 protected static $_isRunning = false;
63
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
64 protected static $_runOptions = array();
65
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
66 /**
67 * Setup basic error handling checks/types, as well as register the error and exception
a0930f4 @phishy fixed spelling errors
phishy authored
68 * handlers.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
69 *
70 * Called on static class initialization (i.e. when first loaded).
71 *
72 * @return void
73 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
74 public static function __init() {
75 static::$_checks = array(
76 'type' => function($config, $info) {
8ed8e65 @nateabele Enabling `ErrorHandler` exception-type checking to catch sub-classes …
nateabele authored
77 return (boolean) array_filter((array) $config['type'], function($type) use ($info) {
78 return $type == $info['type'] || is_subclass_of($info['type'], $type);
79 });
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
80 },
81 'code' => function($config, $info) {
82 return ($config['code'] & $info['code']);
83 },
84 'stack' => function($config, $info) {
8ed8e65 @nateabele Enabling `ErrorHandler` exception-type checking to catch sub-classes …
nateabele authored
85 return (boolean) array_intersect((array) $config['stack'], $info['stack']);
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
86 },
87 'message' => function($config, $info) {
88 return preg_match($config['message'], $info['message']);
89 }
90 );
91 $self = get_called_class();
92
93 static::$_exceptionHandler = function($exception, $return = false) use ($self) {
cc56501 @mackstar Test and fix for issue #66
mackstar authored
94 if (ob_get_length()) {
95 ob_end_clean();
96 }
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
97 $info = compact('exception') + array(
98 'type' => get_class($exception),
99 'stack' => $self::trace($exception->getTrace())
100 );
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
101 foreach (array('message', 'file', 'line', 'trace') as $key) {
102 $method = 'get' . ucfirst($key);
103 $info[$key] = $exception->{$method}();
104 }
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
105 return $return ? $info : $self::handle($info);
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
106 };
107 }
108
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
109 /**
110 * Getter & setter of error/exception handlers.
111 *
112 * @param array $handlers If set, the passed `$handlers` array will be merged with
113 * the already defined handlers in the `ErrorHandler` static class.
114 * @return array Current set of handlers.
115 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
116 public static function handlers($handlers = array()) {
117 return (static::$_handlers = $handlers + static::$_handlers);
118 }
119
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
120 /**
121 * Configure the `ErrorHandler`.
122 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
123 * @param array $config Configuration directives.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
124 * @return Current configuration set.
125 */
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
126 public static function config($config = array()) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
127 return (static::$_config = array_merge($config, static::$_config));
128 }
129
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
130 public static function handler($name, $info) {
131 return static::$_handlers[$name]($info);
132 }
133
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
134 /**
135 * Register error and exception handlers.
136 *
137 * This method (`ErrorHandler::run()`) needs to be called as early as possible in the bootstrap
138 * cycle; immediately after `require`-ing `bootstrap/libraries.php` is your best bet.
139 *
6cbb2cd @nateabele Fixing misc. QA issues and adding docblocks.
nateabele authored
140 * @param array $config The configuration with which to start the error handler. Available
141 * options include:
142 * - `'trapErrors'` _boolean_: Defaults to `false`. If set to `true`, PHP errors
143 * will be caught by `ErrorHandler` and handled in-place. Execution will resume
144 * in the same context in which the error occurred.
145 * - `'convertErrors'` _boolean_: Defaults to `true`, and specifies that all PHP
146 * errors should be converted to `ErrorException`s and thrown from the point
147 * where the error occurred. The exception will be caught at the first point in
148 * the stack trace inside a matching `try`/`catch` block, or that has a matching
149 * error handler applied using the `apply()` method.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
150 * @return void
151 */
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
152 public static function run(array $config = array()) {
153 $defaults = array('trapErrors' => false, 'convertErrors' => true);
154
155 if (static::$_isRunning) {
156 return;
157 }
158 static::$_isRunning = true;
159 static::$_runOptions = $config + $defaults;
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
160 $self = get_called_class();
161
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
162 $trap = function($code, $message, $file, $line = 0, $context = null) use ($self) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
163 $trace = debug_backtrace();
164 $trace = array_slice($trace, 1, count($trace));
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
165 $self::handle(compact('type', 'code', 'message', 'file', 'line', 'trace', 'context'));
166 };
167
168 $convert = function($code, $message, $file, $line = 0, $context = null) use ($self) {
169 throw new ErrorException($message, 500, $code, $file, $line);
170 };
171
172 if (static::$_runOptions['trapErrors']) {
173 set_error_handler($trap);
174 } elseif (static::$_runOptions['convertErrors']) {
175 set_error_handler($convert);
176 }
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
177 set_exception_handler(static::$_exceptionHandler);
178 }
179
180 /**
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
181 * Returns the state of the `ErrorHandler`, indicating whether or not custom error/exception
182 * handers have been regsitered.
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
183 *
184 * @return void
185 */
186 public static function isRunning() {
187 return static::$_isRunning;
188 }
189
190 /**
191 * Unooks `ErrorHandler`'s exception and error handlers, and restores PHP's defaults. May have
192 * unexpected results if it is not matched with a prior call to `run()`, or if other error
193 * handlers are set after a call to `run()`.
194 *
195 * @return void
196 */
197 public static function stop() {
198 restore_error_handler();
199 restore_exception_handler();
200 static::$_isRunning = false;
201 }
202
203 /**
204 * Wipes out all configuration and resets the error handler to its initial state when loaded.
205 * Mainly used for testing.
206 *
207 * @return void
208 */
209 public static function reset() {
210 static::$_config = array();
211 static::$_checks = array();
212 static::$_handlers = array();
213 static::$_exceptionHandler = null;
214 static::__init();
215 }
216
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
217 /**
218 * Receives the handled errors and exceptions that have been caught, and processes them
219 * in a normalized manner.
220 *
221 * @param object|array $info
222 * @param array $scope
223 * @return boolean True if successfully handled, false otherwise.
224 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
225 public static function handle($info, $scope = array()) {
226 $checks = static::$_checks;
227 $rules = $scope ?: static::$_config;
228 $handler = static::$_exceptionHandler;
229 $info = is_object($info) ? $handler($info, true) : $info;
230
231 $defaults = array(
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
232 'type' => null, 'code' => 0, 'message' => null, 'file' => null, 'line' => 0,
233 'trace' => array(), 'context' => null, 'exception' => null
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
234 );
235 $info = (array) $info + $defaults;
236
70da000 @nateabele Fixing bad method call in `\core\ErrorHandler`.
nateabele authored
237 $info['stack'] = static::trace($info['trace']);
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
238 $info['origin'] = static::_origin($info['trace']);
239
240 foreach ($rules as $config) {
241 foreach (array_keys($config) as $key) {
242 if ($key == 'conditions' || $key == 'scope' || $key == 'handler') {
243 continue;
244 }
245 if (!isset($info[$key]) || !isset($checks[$key])) {
246 continue 2;
247 }
248 if (($check = $checks[$key]) && !$check($config, $info)) {
249 continue 2;
250 }
251 }
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
252 if (!isset($config['handler'])) {
253 return false;
254 }
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
255 if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) {
256 return false;
257 }
258 if ((isset($config['scope'])) && static::handle($info, $config['scope']) !== false) {
259 return true;
260 }
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
261 $handler = $config['handler'];
262 return $handler($info) !== false;
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
263 }
264 return false;
265 }
266
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
267 /**
268 * Determine frame from the stack trace where the error/exception was first generated.
269 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
270 * @param array $stack Stack trace from error/exception that was produced.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
271 * @return string Class where error/exception was generated.
272 */
273 protected static function _origin(array $stack) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
274 foreach ($stack as $frame) {
275 if (isset($frame['class'])) {
276 return trim($frame['class'], '\\');
277 }
278 }
279 }
280
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
281 public static function apply($object, array $conditions, $handler) {
282 $conditions = $conditions ?: array('type' => 'Exception');
283 list($class, $method) = is_string($object) ? explode('::', $object) : $object;
284 $wrap = static::$_exceptionHandler;
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
285 $_self = get_called_class();
286
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
287 $filter = function($self, $params, $chain) use ($_self, $conditions, $handler, $wrap) {
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
288 try {
289 return $chain->next($self, $params, $chain);
290 } catch (Exception $e) {
291 if (!$_self::matches($e, $conditions)) {
292 throw $e;
293 }
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
294 return $handler($wrap($e, true), $params);
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
295 }
296 };
297
298 if (is_string($class)) {
e0223e1 @nateabele Using the `Filters` class to lazily apply error-trapping filters to s…
nateabele authored
299 Filters::apply($class, $method, $filter);
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
300 } else {
301 $class->applyFilter($method, $filter);
302 }
303 }
304
305 public static function matches($info, $conditions) {
306 $checks = static::$_checks;
307 $handler = static::$_exceptionHandler;
308 $info = is_object($info) ? $handler($info, true) : $info;
309
310 foreach (array_keys($conditions) as $key) {
311 if ($key == 'conditions' || $key == 'scope' || $key == 'handler') {
312 continue;
313 }
314 if (!isset($info[$key]) || !isset($checks[$key])) {
315 return false;
316 }
317 if (($check = $checks[$key]) && !$check($conditions, $info)) {
318 return false;
319 }
320 }
321 if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) {
322 return false;
323 }
324 return true;
325 }
326
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
327 /**
328 * Trim down a typical stack trace to class & method calls.
329 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
330 * @param array $stack A `debug_backtrace()`-compatible stack trace output.
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
331 * @return array Returns a flat stack array containing class and method references.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
332 */
b90f483 Made `\lithium\core\ErrorHandler::_trace()` public and removed unders…
dmco authored
333 public static function trace(array $stack) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
334 $result = array();
335
336 foreach ($stack as $frame) {
337 if (isset($frame['function'])) {
338 if (isset($frame['class'])) {
339 $result[] = trim($frame['class'], '\\') . '::' . $frame['function'];
340 } else {
341 $result[] = $frame['function'];
342 }
343 }
344 }
345 return $result;
346 }
347 }
348
349 ?>
Something went wrong with that request. Please try again.