Skip to content

Instantly share code, notes, and snippets.

@SquidDev
Created August 19, 2014 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SquidDev/a76225168b171422e5b1 to your computer and use it in GitHub Desktop.
Save SquidDev/a76225168b171422e5b1 to your computer and use it in GitHub Desktop.
Scalar Type checking

This is based of the Type hinting for scalar variables with some optimisations for for strings.

##Loose/weak values This are values that represent that type. For instance 2 is an integer as is 2.0 and '2.0'. This is similar for floats.

<?php
class InvalidArgumentTypeException extends InvalidArgumentException {
protected $ArgumentNumber;
protected $ArgumentTargetType;
protected $ArgumentReceivedType;
protected $ArgumentValue;
public function __construct($Message = "", $Number = null, $TargetType = null, $RecievedType = null, Exception $Previous = null)
{
if($Message) $Message .= ' ';
if($Number !== null || $TargetType !== null || $ReceivedType !== null) {
$Message .= 'Argument';
if($Number) $Message .= ' #'.$Number;
if($TargetType) $Message .= ' expected '.$TargetType;
if($ReceivedType) $Message .= ' received '.$ReceivedType;
}
parent::__construct($Message, 0, $Previous);
$this->ArgumentNumber = $Number;
$this->ArgumentTargetType = $TargetType;
$this->ArgumentReceivedType = $RecievedType;
}
}
class Typehint
{
const TYPEHINT_PCRE = '/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/';
private static $TypeHints = array(
'LooseInteger' => 'static::IsInteger', # Is string representation of int (or int)
'LooseFloat' => 'is_numeric', # Is string representation of float (or float)
);
public static function InitializeHandler()
{
set_error_handler('Typehint::HandleTypehint');
return true;
}
private static function GetTypehintedArgument($BackTrace, $Function, $Index, &$ArgValue)
{
foreach ($BackTrace as $Trace) {
// Match the function; Note we could do more defensive error checking.
if (isset($Trace['function']) && $Trace['function'] == $Function) {
$ArgValue = $Trace['args'][$Index - 1];
return true;
}
}
return false;
}
public static function HandleTypehint($ErrorLevel, $ErrorMessage)
{
if($ErrorLevel == E_RECOVERABLE_ERROR) {
if(preg_match(static::TYPEHINT_PCRE, $ErrorMessage, $Matches)) {
list($Match, $Index, $Class, $Function, $Hint, $Type) = $Matches;
// First check for some basic values
if(
strpos($ErrorMessage, 'must be an instance of string, string') || strpos($ErrorMessage, 'must be an instance of str, string')
|| strpos($ErrorMessage, 'must be an instance of float, double') || strpos($ErrorMessage, 'must be an instance of double, double')
|| strpos($ErrorMessage, 'must be an instance of boolean, boolean') || strpos($ErrorMessage, 'must be an instance of bool, boolean')
|| strpos($ErrorMessage, 'must be an instance of integer, integer') || strpos($ErrorMessage, 'must be an instance of int, integer')
|| strpos($ErrorMessage, 'must be an instance of resource, resource')
) {
return true;
} else if(isset(self::$TypeHints[$Hint]))
{
$ThBacktrace = debug_backtrace();
$ArgValue = null;
if (self::GetTypehintedArgument($ThBacktrace, $Function, $Index, $ArgValue)) {
if (call_user_func(self::$TypeHints[$Hint], $ArgValue)) {
return true;
}
}
}
$FunctionName = ($Class ? $Class . '::' : '') . $Function;
throw new InvalidArgumentTypeException('Invalid argument for ' . $FunctionName . '.', $Index, $Hint, $Type);
}
}
return false;
}
private static function IsInteger($Value) {
return is_numeric($Value) && ($Value == intval($Value));
}
}
Typehint::InitializeHandler();
?>
<?php
function TestString(string $Value) { var_dump($Value); }
function TestInteger(integer $Value) { var_dump($Value); }
function TestWeakInteger(LooseInteger $Value) { var_dump($Value); }
class Foo{
public static function Bar(LooseFloat $Value){
var_dump($Value);
}
}
TestString('Hello'); // Works
TestString(23); // Fails
TestInteger(23); // Works
TestInteger(23.0); // Fails
TestInteger('23'); // Fails
TestWeakInteger(23); // Works
TestWeakInteger(23.0); // Works
TestWeakInteger('23'); // Works
TestWeakInteger('23.0'); // Works
TestWeakInteger('Foo'); // Fails
Foo::Bar('12.3'); // Classes work too!
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment