<?php
/**
* @package typehintclass
*/
/**
* Class that handles type hinting errors and checks type hint
* classes. Singleton.
*/
final class TypeHintHandler {
/**
* Error handler that was replaced by the one from this object
* @var function|null
*/
private $oldErrorHandler;
/**
* Cached info parsed from error message.
* @var array
*/
private $parsedErrors;
/**
* Cached results of checking which classes are type hints.
* @var array
*/
private $typeHintClasses;
/**
* Constructor. Activates the error handling.
*/
private function __construct() {
$this->oldErrorHandler = set_error_handler(array($this, 'errorHandler'));
$this->parsedErrors = array();
$this->typeHintClasses = array();
}
/**
* Single instance of TypeHintHandler.
* @var TypeHintHandler
*/
private static $instance;
/**
* Sets up the single instance of this handler.
*/
public static function setUp() {
if (self::$instance === null) {
self::$instance = new TypeHintHandler();
}
}
/**
* Returns the single instance of this handler.
* @return TypeHintHandler The singleton instance.
*/
public static function getInstance() {
self::setUp();
return self::$instance;
}
/**
* Parses an error string to find out if it is a type hint failure
* and to gather the info about this error in particular.
* @param string $errstr Error message.
* @return array|null Info on the parsed error.
*/
private function parseErrorString($errstr) {
if (isset($this->parsedErrors[$errstr])) {
return $this->parsedErrors[$errstr];
}
$parsedError = null;
if (preg_match('/^Argument ([0-9]+) passed to ([a-zA-Z0-9_:]+)\(\) must be an instance of ([a-zA-Z0-9_:]+),/', $errstr, $match)) {
$parsedError = array(
'argnum' => $match[1],
'class' => null,
'function' => $match[2],
'typehint' => $match[3],
);
if (preg_match('/^([a-zA-Z0-9_]+)::([a-zA-Z0-9_]+)$/', $match[2], $match)) {
$parsedError['class'] = $match[1];
$parsedError['function'] = $match[2];
}
}
$this->parsedErrors[$errstr] = $parsedError;
return $parsedError;
}
/**
* Error handling routine.
* @private
* @param int $errno Error code.
* @param string $errstr Error message.
* @param string $errfile File where the error occurred.
* @param int $errline The line number where the error occurred.
* @param array $errcontext The context where the error occurred.
*/
public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
if ($errno == E_RECOVERABLE_ERROR) {
if ($parsedError = $this->parseErrorString($errstr)) {
$backtrace = debug_backtrace();
$value = @$backtrace[1]['args'][$parsedError['argnum']-1];
if ($this->solveTypeHintFailure($parsedError['typehint'], $value)) {
return;
}
}
}
if ($this->oldErrorHandler) {
return call_user_func($this->oldErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext);
} else {
return false;
}
}
/**
* Solves a type hint failure, deciding to continue or not.
* @param string $typeHint The type hint used.
* @param mixed $value The value that failed the type hint.
* @return bool Whether to continue or not.
*/
private function solveTypeHintFailure($typeHint, $value) {
if ($this->isTypeHintClass($typeHint)) {
return call_user_func(array($typeHint, 'isTypeHintFor'), $value);
}
return false;
}
/**
* Indicates if a class is a type hint class.
*
* @param string $className The class.
* @return bool True if the class is a type hint class.
*/
public function isTypeHintClass($className) {
if (isset($this->typeHintClasses[$className])) {
return $this->typeHintClasses[$className];
}
$this->typeHintClasses[$className] = false;
if (class_exists($className)) {
if (in_array('TypeHint', class_implements($className))) {
$this->typeHintClasses[$className] = true;
}
}
return $this->typeHintClasses[$className];
}
}
?>
|