Location: PHPKode > scripts > Type Hint Class > type-hint-class/TypeHintHandler.php
<?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];
    }
}
?>
Return current item: Type Hint Class