Is there a way to test if a $var
satisfies a type defined in a string other than this:
function test_type(mixed $value, string $type): bool
{
try {
eval('(fn('.$type.' $a)=>true)($value);');
return true;
}
catch (Throwable $e) {
return false;
}
}
test_type(null, "int") ; // returns false.
test_type(null, "?int") ; // return true.
test_type("hello, world", "bool") ; // returns true. I want it to return false
test_type(new DateTime, "DateTimeInterface") ; // return true.
test_type(new DateTime, "null|int|DateTime|DateTimeImmutable") ; // return true
There are two major problems with this approach that do not need further explanation:
test_type("hello, world", "bool")
=> true
)Is there a way to avoid these problems? I've Googled a lot but haven't found anything up to now.
@Jaquarh convinced me it was way easier than I thought it would be by just testing all the types and unions and intersections. His version was the basis for this solution. Thanks, Jaquarh!
function test_type(mixed $value, string $type): bool
{ if ( $type == 'mixed' ) return true;
$foundType = strtolower(gettype($value));
$foundType = ['boolean'=>'bool', 'integer'=>'int', 'double'=>'float'][$foundType] ?? $foundType;
$type = str_replace('?', 'null|', $type);
$typesUnion = explode('|', $type);
$typesIntersection = explode('&', $type);
if ( count($typesIntersection)>1) {
if ( ! is_object($value) ) return false;
foreach($typesIntersection as $subtype) {
if ( ! ($value instanceof $subtype)) return false;
}
return true;
}
foreach($typesUnion as $subtype) {
if(match(strtolower($subtype)) {
$foundType => true,
'false' => $value === false,
'true' => $value === true,
'callable' => is_callable($value),
default => is_object($value) && $value instanceof $subtype
}) return true;
}
return false;
}
enum hello { case world; case earth; case mars; };
echo (int) test_type(false, "int|bool"); // 1
echo (int) test_type(null, "?int"); // 1
echo (int) test_type(null, "int|string"); // 0
echo (int) test_type("hello, world", "?bool"); // 0
echo (int) test_type(hello::world, "hello"); // 1
echo (int) test_type(false, "DateTimeInterface|false"); // 1
echo (int) test_type(true, "DateTimeInterface|false"); // 0
echo (int) test_type(new DateTime, "DateTimeInterface|false"); // 1
echo (int) test_type(new DateTime, "null|int|DateTime|DateTimeImmutable"); // 1
echo (int) test_type(new DateTime, "DateTime&DateTimeInterface"); // 1
echo (int) test_type([1,2,3,4], "Array"); // 1
echo (int) test_type(fn($x) => $x+1, "callable"); // 1
echo (int) test_type($x = fopen('php://output', 'w'), "object|resource"); // 1