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