Skip to content
Newer
Older
100644 346 lines (311 sloc) 10.4 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
68 * hanlders.
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) {
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
94 $info = compact('exception') + array(
95 'type' => get_class($exception),
96 'stack' => $self::trace($exception->getTrace())
97 );
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
98 foreach (array('message', 'file', 'line', 'trace') as $key) {
99 $method = 'get' . ucfirst($key);
100 $info[$key] = $exception->{$method}();
101 }
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
102 return $return ? $info : $self::handle($info);
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
103 };
104 }
105
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
106 /**
107 * Getter & setter of error/exception handlers.
108 *
109 * @param array $handlers If set, the passed `$handlers` array will be merged with
110 * the already defined handlers in the `ErrorHandler` static class.
111 * @return array Current set of handlers.
112 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
113 public static function handlers($handlers = array()) {
114 return (static::$_handlers = $handlers + static::$_handlers);
115 }
116
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
117 /**
118 * Configure the `ErrorHandler`.
119 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
120 * @param array $config Configuration directives.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
121 * @return Current configuration set.
122 */
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
123 public static function config($config = array()) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
124 return (static::$_config = array_merge($config, static::$_config));
125 }
126
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
127 public static function handler($name, $info) {
128 return static::$_handlers[$name]($info);
129 }
130
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
131 /**
132 * Register error and exception handlers.
133 *
134 * This method (`ErrorHandler::run()`) needs to be called as early as possible in the bootstrap
135 * cycle; immediately after `require`-ing `bootstrap/libraries.php` is your best bet.
136 *
6cbb2cd @nateabele Fixing misc. QA issues and adding docblocks.
nateabele authored
137 * @param array $config The configuration with which to start the error handler. Available
138 * options include:
139 * - `'trapErrors'` _boolean_: Defaults to `false`. If set to `true`, PHP errors
140 * will be caught by `ErrorHandler` and handled in-place. Execution will resume
141 * in the same context in which the error occurred.
142 * - `'convertErrors'` _boolean_: Defaults to `true`, and specifies that all PHP
143 * errors should be converted to `ErrorException`s and thrown from the point
144 * where the error occurred. The exception will be caught at the first point in
145 * the stack trace inside a matching `try`/`catch` block, or that has a matching
146 * error handler applied using the `apply()` method.
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
147 * @return void
148 */
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
149 public static function run(array $config = array()) {
150 $defaults = array('trapErrors' => false, 'convertErrors' => true);
151
152 if (static::$_isRunning) {
153 return;
154 }
155 static::$_isRunning = true;
156 static::$_runOptions = $config + $defaults;
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
157 $self = get_called_class();
158
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
159 $trap = function($code, $message, $file, $line = 0, $context = null) use ($self) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
160 $trace = debug_backtrace();
161 $trace = array_slice($trace, 1, count($trace));
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
162 $self::handle(compact('type', 'code', 'message', 'file', 'line', 'trace', 'context'));
163 };
164
165 $convert = function($code, $message, $file, $line = 0, $context = null) use ($self) {
166 throw new ErrorException($message, 500, $code, $file, $line);
167 };
168
169 if (static::$_runOptions['trapErrors']) {
170 set_error_handler($trap);
171 } elseif (static::$_runOptions['convertErrors']) {
172 set_error_handler($convert);
173 }
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
174 set_exception_handler(static::$_exceptionHandler);
175 }
176
177 /**
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
178 * Returns the state of the `ErrorHandler`, indicating whether or not custom error/exception
179 * handers have been regsitered.
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
180 *
181 * @return void
182 */
183 public static function isRunning() {
184 return static::$_isRunning;
185 }
186
187 /**
188 * Unooks `ErrorHandler`'s exception and error handlers, and restores PHP's defaults. May have
189 * unexpected results if it is not matched with a prior call to `run()`, or if other error
190 * handlers are set after a call to `run()`.
191 *
192 * @return void
193 */
194 public static function stop() {
195 restore_error_handler();
196 restore_exception_handler();
197 static::$_isRunning = false;
198 }
199
200 /**
201 * Wipes out all configuration and resets the error handler to its initial state when loaded.
202 * Mainly used for testing.
203 *
204 * @return void
205 */
206 public static function reset() {
207 static::$_config = array();
208 static::$_checks = array();
209 static::$_handlers = array();
210 static::$_exceptionHandler = null;
211 static::__init();
212 }
213
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
214 /**
215 * Receives the handled errors and exceptions that have been caught, and processes them
216 * in a normalized manner.
217 *
218 * @param object|array $info
219 * @param array $scope
220 * @return boolean True if successfully handled, false otherwise.
221 */
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
222 public static function handle($info, $scope = array()) {
223 $checks = static::$_checks;
224 $rules = $scope ?: static::$_config;
225 $handler = static::$_exceptionHandler;
226 $info = is_object($info) ? $handler($info, true) : $info;
227
228 $defaults = array(
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
229 'type' => null, 'code' => 0, 'message' => null, 'file' => null, 'line' => 0,
230 'trace' => array(), 'context' => null, 'exception' => null
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
231 );
232 $info = (array) $info + $defaults;
233
70da000 @nateabele Fixing bad method call in `\core\ErrorHandler`.
nateabele authored
234 $info['stack'] = static::trace($info['trace']);
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
235 $info['origin'] = static::_origin($info['trace']);
236
237 foreach ($rules as $config) {
238 foreach (array_keys($config) as $key) {
239 if ($key == 'conditions' || $key == 'scope' || $key == 'handler') {
240 continue;
241 }
242 if (!isset($info[$key]) || !isset($checks[$key])) {
243 continue 2;
244 }
245 if (($check = $checks[$key]) && !$check($config, $info)) {
246 continue 2;
247 }
248 }
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
249 if (!isset($config['handler'])) {
250 return false;
251 }
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
252 if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) {
253 return false;
254 }
255 if ((isset($config['scope'])) && static::handle($info, $config['scope']) !== false) {
256 return true;
257 }
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
258 $handler = $config['handler'];
259 return $handler($info) !== false;
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
260 }
261 return false;
262 }
263
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
264 /**
265 * Determine frame from the stack trace where the error/exception was first generated.
266 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
267 * @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
268 * @return string Class where error/exception was generated.
269 */
270 protected static function _origin(array $stack) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
271 foreach ($stack as $frame) {
272 if (isset($frame['class'])) {
273 return trim($frame['class'], '\\');
274 }
275 }
276 }
277
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
278 public static function apply($object, array $conditions, $handler) {
279 $conditions = $conditions ?: array('type' => 'Exception');
280 list($class, $method) = is_string($object) ? explode('::', $object) : $object;
281 $wrap = static::$_exceptionHandler;
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
282 $_self = get_called_class();
283
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
284 $filter = function($self, $params, $chain) use ($_self, $conditions, $handler, $wrap) {
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
285 try {
286 return $chain->next($self, $params, $chain);
287 } catch (Exception $e) {
288 if (!$_self::matches($e, $conditions)) {
289 throw $e;
290 }
f23fb9d @nateabele Changing API of `\core\ErrorHandler::apply()` to allow for more compa…
nateabele authored
291 return $handler($wrap($e, true), $params);
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
292 }
293 };
294
295 if (is_string($class)) {
e0223e1 @nateabele Using the `Filters` class to lazily apply error-trapping filters to s…
nateabele authored
296 Filters::apply($class, $method, $filter);
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
297 } else {
298 $class->applyFilter($method, $filter);
299 }
300 }
301
302 public static function matches($info, $conditions) {
303 $checks = static::$_checks;
304 $handler = static::$_exceptionHandler;
305 $info = is_object($info) ? $handler($info, true) : $info;
306
307 foreach (array_keys($conditions) as $key) {
308 if ($key == 'conditions' || $key == 'scope' || $key == 'handler') {
309 continue;
310 }
311 if (!isset($info[$key]) || !isset($checks[$key])) {
312 return false;
313 }
314 if (($check = $checks[$key]) && !$check($conditions, $info)) {
315 return false;
316 }
317 }
318 if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) {
319 return false;
320 }
321 return true;
322 }
323
b06994a @jperras Adding doc blocks for core\ErrorHandler & return early refactor for
jperras authored
324 /**
325 * Trim down a typical stack trace to class & method calls.
326 *
644ac5c @davidpersson QA: Fixing undocumented parameters.
davidpersson authored
327 * @param array $stack A `debug_backtrace()`-compatible stack trace output.
775bf9b @nateabele Implementing `\core\ErrorHandler::apply()`.
nateabele authored
328 * @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
329 */
b90f483 Made `\lithium\core\ErrorHandler::_trace()` public and removed unders…
dmco authored
330 public static function trace(array $stack) {
c78dd00 @nateabele Initial draft commit of `ErrorHandler`.
nateabele authored
331 $result = array();
332
333 foreach ($stack as $frame) {
334 if (isset($frame['function'])) {
335 if (isset($frame['class'])) {
336 $result[] = trim($frame['class'], '\\') . '::' . $frame['function'];
337 } else {
338 $result[] = $frame['function'];
339 }
340 }
341 }
342 return $result;
343 }
344 }
345
346 ?>
Something went wrong with that request. Please try again.