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.
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! | |
?> |