Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
862 lines (790 sloc) 23.1 KB
<?php
/**
* @link https://github.com/aercolino/ando-php
* @copyright Copyright (c) 2015 Andrea Ercolino
* @license https://github.com/aercolino/ando-php/blob/master/LICENSE
*/
//@Formatter:off
/**
* A simple reference copied from the PHP documentation.
*
* 1 E_ERROR Fatal run-time errors. Errors that cannot be recovered from. Execution of the script
* is halted.
* 2 E_WARNING Run-time warnings (non-fatal errors). Execution of the script is not halted.
* 4 E_PARSE Compile-time parse errors. Parse errors should only be generated by the parser.
* 8 E_NOTICE Run-time notices. The script found something that might be an error, but could also
* happen when running a script normally.
* 16 E_CORE_ERROR Fatal errors at PHP startup. This is like E_ERROR, except it is generated by the
* core of PHP.
* 32 E_CORE_WARNING Non-fatal errors at PHP startup. This is like E_WARNING, except it is generated by
* the core of PHP.
* 64 E_COMPILE_ERROR Fatal compile-time errors. This is like E_ERROR, except it is generated by by the
* Zend Scripting Engine.
* 128 E_COMPILE_WARNING Non-fatal compile-time errors. This is like E_WARNING, except it is generated by by
* the Zend Scripting Engine.
* 256 E_USER_ERROR Fatal user-generated error. This is like E_ERROR, except it is generated in PHP code
* by using the PHP function trigger_error().
* 512 E_USER_WARNING Non-fatal user-generated warning. This is like E_WARNING, except it is generated in
* PHP code by using the PHP function trigger_error().
* 1024 E_USER_NOTICE User-generated notice. This is like E_NOTICE, except it is generated in PHP code by
* using the PHP function trigger_error().
* 2048 E_STRICT Enable to have PHP suggest changes to your code which will ensure the best
* interoperability and forward compatibility of your code. (Since PHP 5 but not
* included in E_ALL until PHP 5.4)
* 4096 E_RECOVERABLE_ERROR Catchable fatal error. Indicates that a probably dangerous error occurred, but did
* not leave the Engine in an unstable state. If the error is not caught by a user
* defined handle, the application aborts as it was an E_ERROR. (Since PHP 5.2)
* 8192 E_DEPRECATED Run-time notices. Enable this to receive warnings about code that will not work in
* future versions. (Since PHP 5.3)
* 16384 E_USER_DEPRECATED User-generated warning message. This is like E_DEPRECATED, except it is generated in
* PHP code by using the PHP function trigger_error(). (Since PHP 5.3)
*/
//@Formatter:on
/**
* Class Ando_ErrorFactory
*/
class Ando_ErrorFactory
{
// -----------------------------------------------------------------------------------------------INTERNAL SINGLETON
/**
* Map error names to error values.
*
* @var int[]
*/
protected $map_to_int = array(
'E_ERROR' => E_ERROR,
'E_WARNING' => E_WARNING,
'E_PARSE' => E_PARSE,
'E_NOTICE' => E_NOTICE,
'E_CORE_ERROR' => E_CORE_ERROR,
'E_CORE_WARNING' => E_CORE_WARNING,
'E_COMPILE_ERROR' => E_COMPILE_ERROR,
'E_COMPILE_WARNING' => E_COMPILE_WARNING,
'E_USER_ERROR' => E_USER_ERROR,
'E_USER_WARNING' => E_USER_WARNING,
'E_USER_NOTICE' => E_USER_NOTICE,
'E_STRICT' => E_STRICT,
'E_RECOVERABLE_ERROR' => E_RECOVERABLE_ERROR,
'E_DEPRECATED' => E_DEPRECATED,
'E_USER_DEPRECATED' => E_USER_DEPRECATED,
);
/**
* Map error values to error names.
*
* @var string[]
*/
protected $map_to_str = array();
protected
function __construct()
{
$this->map_to_str = array_flip($this->map_to_int);
}
/**
* Internal singleton.
*
* @var Ando_ErrorFactory
*/
protected static $instance;
/**
* Get the singleton.
*
* @return Ando_ErrorFactory
*/
protected static
function instance()
{
if ( is_null(self::$instance) ) {
self::$instance = new self();
}
return self::$instance;
}
// ---------------------------------------------------------------------------------------------CATEGORIES OF ERRORS
/**
* All errors, as an array of integers.
*
* @return int[]
*/
public static
function all_errors()
{
$result = array_values(self::instance()->map_to_int);
return $result;
}
/**
* All errors, as an array of strings.
*
* @return string[]
*/
public static
function all_errors_to_str()
{
$result = array_keys(self::instance()->map_to_int);
return $result;
}
/**
* Non catchable errors cannot be handled from an error handler.
*
* @link https://github.com/php/php-src/blob/fc33f52d8c25997dd0711de3e07d0dc260a18c11/Zend/zend.c#L1078
*
* @return int[]
*/
public static
function non_catchable_errors()
{
return array(
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
);
}
/**
* Catchable errors can be handled from an error handler.
*
* @return int[]
*/
public static
function catchable_errors()
{
return array_diff(self::all_errors(), self::non_catchable_errors());
}
/**
* Non catchable shutdown errors cannot be handled from an error handler and jump to script shutdown.
*
* @return int[]
*/
public static
function non_catchable_shutdown_errors()
{
return array(
E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR
);
}
/**
* Catchable shutdown errors jump to script shutdown unless they are handled from an error handler.
*
* @return int[]
*/
public static
function catchable_shutdown_errors()
{
return array(
E_RECOVERABLE_ERROR, E_USER_ERROR
);
}
/**
* Shutdown errors jump to script shutdown.
*
* @return int[]
*/
public static
function shutdown_errors()
{
return array_merge(self::non_catchable_shutdown_errors(), self::catchable_shutdown_errors());
}
/**
* Non shutdown errors do not jump to script shutdown.
*
* @return int[]
*/
public static
function non_shutdown_errors()
{
return array_diff(self::all_errors(), self::shutdown_errors());
}
/**
* Catchable non shutdown errors can be handled from an error handler and do not jump to script shutdown.
*
* @return int[]
*/
public static
function catchable_non_shutdown_errors()
{
return array_intersect(self::catchable_errors(), self::non_shutdown_errors());
}
/**
* Non catchable non shutdown errors cannot be handled from an error handler but do not jump to script shutdown.
*
* @return int[]
*/
public static
function non_catchable_non_shutdown_errors()
{
return array_intersect(self::non_catchable_errors(), self::non_shutdown_errors());
}
/**
* The type of the error according to PHP. It's kind of legacy...
*
* @link https://github.com/php/php-src/blob/fc33f52d8c25997dd0711de3e07d0dc260a18c11/main/main.c#L1083
*
* @param $error
*
* @return string
*/
public static
function php_error_type( $error )
{
switch ($error) {
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
$result = "Fatal error";
break;
case E_RECOVERABLE_ERROR:
$result = "Catchable fatal error";
break;
case E_WARNING:
case E_CORE_WARNING:
case E_COMPILE_WARNING:
case E_USER_WARNING:
$result = "Warning";
break;
case E_PARSE:
$result = "Parse error";
break;
case E_NOTICE:
case E_USER_NOTICE:
$result = "Notice";
break;
case E_STRICT:
$result = "Strict Standards";
break;
case E_DEPRECATED:
case E_USER_DEPRECATED:
$result = "Deprecated";
break;
default:
$result = "Unknown error";
break;
}
return $result;
}
// ---------------------------------------------------------------------------------------------CONVERSION OF ERRORS
/**
* Not an error.
*/
const NO_ERROR = 0;
const NO_ERROR_STR = 'NO_ERROR';
/**
* Unknown error.
*/
const UNKNOWN_ERROR = -1;
const UNKNOWN_ERROR_STR = 'UNKNOWN_ERROR';
/**
* Convert errors from string (or int) to int (or string), using unknown for unmapped values.
*
* @param int|int[]|string|string[] $errors
* @param string[]|int[] $map
* @param string|int $unknown
*
* @return string[]|int[]
*/
protected static
function convert( $errors, $map, $unknown )
{
$was_array = true;
if ( ! is_array($errors) ) {
$was_array = false;
$errors = array($errors);
}
$result = array();
foreach ($errors as $error) {
$result[] = isset($map[$error])
? $map[$error]
: $unknown;
}
if ( ! $was_array ) {
list($result) = $result;
}
return $result;
}
/**
* Convert errors from int to string.
*
* @param int|int[] $errors
*
* @return string|string[]
*/
public static
function to_str( $errors )
{
$result = self::convert($errors, self::instance()->map_to_str, self::UNKNOWN_ERROR_STR);
return $result;
}
/**
* Convert errors from string to int.
*
* @param string|string[] $errors
*
* @return int[]
*/
public static
function to_int( $errors )
{
$result = self::convert($errors, self::instance()->map_to_int, self::UNKNOWN_ERROR);
return $result;
}
/**
* Convert a set of errors to a mask of bits.
*
* @param int|int[] $errors
*
* @return int
*/
public static
function to_mask( $errors )
{
if ( ! is_array($errors) ) {
return (int) $errors;
}
$result = 0;
foreach ($errors as $error) {
$result = $result | (int) $error;
}
return $result;
}
/**
* Convert a mask of bits to a set of errors.
*
* @param int $mask
*
* @return int[]
*/
public static
function to_array( $mask )
{
$mask = (int) $mask;
$result = array();
foreach (self::all_errors() as $error) {
if ( $error & $mask ) {
$result[] = $error;
}
}
return $result;
}
// ----------------------------------------------------------------------------QUICK AND DIRTY VISUALIZERS OF ERRORS
/**
* Show info about the error.
*
* @param $handler
* @param $level
* @param $error
*/
protected static
function see_error( $handler, $level, $error )
{
$name = $level == self::NO_ERROR
? '(none)'
: self::to_str($level);
$type = self::php_error_type($level);
echo "\n$handler: $name ($type)\n";
if ( '(none)' != $name ) {
echo "\nerror = ", print_r($error, true), "\n";
}
}
/**
* Visualizer for non catchable errors. To be appended to the shutdown handlers queue.
*/
public static
function non_catchable_error_visualizer()
{
static $count = 0;
if ( ++$count > 1 ) {
return;
}
$error = error_get_last();
$level = is_array($error) && isset($error['type']) && is_numeric($error['type'])
? (int) $error['type']
: 0;
self::see_error('Non catchable error', $level, $error);
// no backtrace available here...
}
protected $catchable_error_handled = true; // True means no default error processing is needed.
/**
* Visualizer for catchable errors. To be pushed onto the error handlers stack (conceptually).
*
* @param int $type
* @param string $message
* @param string $file
* @param int $line
* @param array $context
*
* @return bool
*/
public static
function catchable_error_visualizer( $type, $message, $file, $line, $context )
{
$error = compact('type', 'message', 'file', 'line', 'context');
self::see_error('Catchable error', $type, $error);
debug_print_backtrace();
return self::instance()->catchable_error_handled;
}
/**
* Append the non catchable error visualizer to the shutdown handlers queue.
*/
public static
function register_non_catchable_error_visualizer()
{
/**
* Note that
* - (1) register_shutdown_function appends the given function to a shutdown callbacks queue, so it must play
* nicely with unknown code executed before and after, during the same shutdown;
* - (2) it does not check whether the given function is already in the queue or not;
* - (3) it's not possible to un-register a registered shutdown function.
* Therefore, a registered shutdown function, not only must play nicely with other unknown code, but also with
* possible additional executions of its own code. Tip: Use something like:
* <code>static $count = 0; if (++$count > 1) return; ...</code>
*/
register_shutdown_function(array('Ando_ErrorFactory', 'non_catchable_error_visualizer'));
}
/**
* Push the catchable error visualizer onto the error handlers stack (conceptually).
*
* @param bool $handled
*
* @return mixed Previous handler.
*/
public static
function register_catchable_error_visualizer( $handled = true )
{
self::instance()->catchable_error_handled = (bool) $handled;
$result = set_error_handler(array(
'Ando_ErrorFactory', 'catchable_error_visualizer'
));
return $result;
}
// -----------------------------------------------------------------------------------------------TRIGGERS OF ERRORS
/**
* Trigger E_ERROR.
*/
public static
function E_ERROR()
{
eval(self::E_ERROR_code());
}
/**
* Trigger E_WARNING.
*/
public static
function E_WARNING()
{
eval(self::E_WARNING_code());
}
/**
* Trigger E_PARSE.
*/
public static
function E_PARSE()
{
eval(self::E_PARSE_code());
}
/**
* Trigger E_NOTICE.
*/
public static
function E_NOTICE()
{
eval(self::E_NOTICE_code());
}
/**
* Trigger E_CORE_ERROR.
*/
public static
function E_CORE_ERROR()
{
eval(self::E_CORE_ERROR_code());
}
/**
* By looking at the PHP source files, I discovered that, unlike E_CORE_ERROR, E_CORE_WARNING is triggered only
* during low level operations, like loading modules during startup, or handling deactivation of signal handlers
* during shutdown (5.4+). Given that there is no way to interfere with startup from userland, and given that,
* as much as I can currently understand from the sources, there is neither a way to do it during shutdown because
* userland shutdown handlers get executed before zend_signal_deactivate, I'm giving up on this, for now.
*
* @link http://lxr.php.net/s?refs=zend_signal_deactivate&project=PHP_5_4
*
* For the above reasons this method is not public.
*/
protected static
function E_CORE_WARNING()
{
eval(self::no_way_to_trigger_E_CORE_WARNING_from_userland_yet());
}
/**
* Trigger E_COMPILE_ERROR.
*/
public static
function E_COMPILE_ERROR()
{
eval(self::E_COMPILE_ERROR_code());
}
/**
* Trigger E_COMPILE_WARNING.
*/
public static
function E_COMPILE_WARNING()
{
eval(self::E_COMPILE_WARNING_code());
}
/**
* Trigger E_USER_ERROR.
*/
public static
function E_USER_ERROR()
{
eval(self::E_USER_ERROR_code());
}
/**
* Trigger E_USER_WARNING.
*/
public static
function E_USER_WARNING()
{
eval(self::E_USER_WARNING_code());
}
/**
* Trigger E_USER_NOTICE.
*/
public static
function E_USER_NOTICE()
{
eval(self::E_USER_NOTICE_code());
}
/**
* Trigger E_STRICT.
*/
public static
function E_STRICT()
{
eval(self::E_STRICT_code());
}
/**
* Trigger E_RECOVERABLE_ERROR.
*/
public static
function E_RECOVERABLE_ERROR()
{
eval(self::E_RECOVERABLE_ERROR_code());
}
/**
* Trigger E_DEPRECATED.
*/
public static
function E_DEPRECATED()
{
eval(self::E_DEPRECATED_code());
}
/**
* Trigger E_USER_DEPRECATED.
*/
public static
function E_USER_DEPRECATED()
{
eval(self::E_USER_DEPRECATED_code());
}
//--------------------------------------------------------------------------------------------------CODES FOR ERRORS
/**
* Code for E_ERROR error by calling an undefined function.
*
* @return string
*/
public static
function E_ERROR_code()
{
$name = uniqid('oops_');
$result = "$name();";
return $result;
}
/**
* Code for E_WARNING error by calling a function with less arguments.
*
* @return string
*/
public static
function E_WARNING_code()
{
$name = uniqid('oops_');
$result = "
function $name(\$required){}
$name();
";
return $result;
}
/**
* Code for E_NOTICE error by using an undefined constant.
*
* @return string
*/
public static
function E_NOTICE_code()
{
$name = uniqid('oops_');
$result = "$name;";
return $result;
}
/**
* Code for E_PARSE error by not using a semicolon.
*
* @return string
*/
public static
function E_PARSE_code()
{
$name = uniqid('oops_');
$result = "echo '$name'";
return $result;
}
/**
* Code for E_CORE_ERROR error by declaring a class directly implementing Traversable.
*
* @return string
*/
public static
function E_CORE_ERROR_code()
{
$name = uniqid('oops_');
$result = "class $name implements Traversable {}";
return $result;
}
/**
* Missing E_CORE_WARNING code.
*
* @return string
*/
protected static
function no_way_to_trigger_E_CORE_WARNING_from_userland_yet()
{
// TODO Find a way to trigger E_CORE_WARNING from userland.
// You can play with this code if you like, but it doesn't really work.
// In fact, it only works inside the CLI created by exec "php...".
$name = uniqid('oops_');
$filename = sys_get_temp_dir() . "/$name.ini";
file_put_contents($filename, "extension=$name");
$result = "php -c $filename oops.php";
exec($result, $output, $return);
unlink($filename);
$result = '';
return $result;
}
/**
* Code for E_COMPILE_ERROR error by declaring a class called self.
*
* @return string
*/
public static
function E_COMPILE_ERROR_code()
{
$result = 'class self {}';
return $result;
}
/**
* Code for E_COMPILE_WARNING error by not closing a multiline comment.
*
* @return string
*/
public static
function E_COMPILE_WARNING_code()
{
$name = uniqid('oops_');
$result = "/* $name";
return $result;
}
/**
* Code for E_USER_ERROR error by calling trigger_error with type E_USER_ERROR.
*
* @return string
*/
public static
function E_USER_ERROR_code()
{
$name = uniqid('oops_');
$result = "trigger_error('$name', E_USER_ERROR);";
return $result;
}
/**
* Code for E_USER_WARNING error by calling trigger_error with type E_USER_WARNING.
*
* @return string
*/
public static
function E_USER_WARNING_code()
{
$name = uniqid('oops_');
$result = "trigger_error('$name', E_USER_WARNING);";
return $result;
}
/**
* Code for E_USER_NOTICE error by calling trigger_error with type E_USER_NOTICE.
*
* @return string
*/
public static
function E_USER_NOTICE_code()
{
$name = uniqid('oops_');
$result = "trigger_error('$name', E_USER_NOTICE);";
return $result;
}
/**
* Code for E_STRICT error by accessing static property as non static.
*
* @return string
*/
public static
function E_STRICT_code()
{
$name = uniqid('oops_');
$result = "
class Parent_$name {
function oops(\$arg = array()) {}
}
class Child_$name extends Parent_$name {
function oops() {}
}
";
return $result;
}
/**
* Code for E_RECOVERABLE_ERROR error by converting to string an object of a class without __toString.
*
* @return string
*/
public static
function E_RECOVERABLE_ERROR_code()
{
$name = uniqid('oops_');
$result = "
class $name{}
echo new $name();
";
return $result;
}
/**
* Code for E_DEPRECATED error by calling call_user_method.
*
* @return string
*/
public static
function E_DEPRECATED_code()
{
$name = uniqid('oops_');
$result = "
class $name {
function oops(){}
}
\$oops = new $name();
call_user_method('oops', \$oops);
";
return $result;
}
/**
* Code for E_USER_DEPRECATED error by calling trigger_error with type E_USER_DEPRECATED.
*
* @return string
*/
public static
function E_USER_DEPRECATED_code()
{
$name = uniqid('oops_');
$result = "trigger_error('$name', E_USER_DEPRECATED);";
return $result;
}
}