Skip to content

HTTPS clone URL

Subversion checkout URL

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