PHP 8.0: New get_debug_type function

Version8.0
TypeNew Feature

get_debug_type() function that comes with PHP 8.0 is an attempt to improve gettype() function which returns inconsistent values and does not expand to reveal the class names of objects.

One of the use cases of this function is to use it as an easy way to explain an unexpected variable in error logs or exception messages because the return type of gettype() is often not verbose enough to be meaningful.

get_debug_type() vs gettype()

The following sections will explain the differences between the new get_debug_type() function and current gettype() function return values.

Scalar types

Type Example value gettype() get_debug_type()
String "Foo" string string
Arrays [1, 2] array array
Null null NULL null
Integers 123 integer int
Float 3.141 double float
Boolean true boolean bool
Boolean false boolean bool

Notice how the new get_debug_type() function returns the exact types that you use in scalar typing.

Class objects and anonymous functions

Type Example value gettype() get_debug_type()
Class object new stdClass() object stdClass
Class object new DateTime() object DateTime
Class object new Foo\Bar() object Foo\Bar
Closure function() {} object Closure
Anonymous class new class {} object class@anonymous
Anonymous subclass new class extends Foo{} object Foo@anonymous

As you can see, get_debug_type() is starting to get more helpful here, because it helpfully reports the name of the class, or further details about anonymous classes.

If a closure is passed (which is internally an object), get_debug_type() returns it as Closure.

When an anonymous class object is passed, it returns class@anonymous for classes without a parent/interface, or parent/interface name, followed by @anonymous.

This is where the get_debug_type() function gets quite helpful because instead of calling get_class() and get_parent_class(), get_debug_type() directly returns a useful name for the given object. This can help minimize the boilerplate code and code bloat in when exceptions are thrown and errors are logged.

Resources

Type Example value gettype() get_debug_type()
Streams tmpfile() resource resource (stream)
Curl handler curl_init() resource resource (curl)
Closed Curl handler curl_close($ch) resource (closed) resource (closed)
XML document xml_parser_create() resource resource (xml)
... ... resource resource (TYPE)

For all resource types in PHP, get_debug_type() function will return the type of that resource too.


Use cases

When you want to output a verbose error message on an unexpected type, instead of doing an ugly type lookup, which often overlooks other variable types, you can now easily get the type of a passed variable:

Previously:

if (!($foo instanceof Foo)) { 
    throw new TypeError(
        sprintf(
            'Parameter 1 is expected to be of type "%s", got "%s" instead.',
            Foo::class,
            (is_object($foo) ? get_class($foo) : gettype($foo))
        )
    );
}

Now:

if (!($foo instanceof Foo)) { 
    throw new TypeError(
        sprintf(
            'Parameter 1 is expected to be of type "%s", got "%s" instead.',
            Foo::class,
            get_debug_type($foo)
        )
    );
}

Polyfill

if (!function_exists('get_debug_type')) {
    function get_debug_type($value): string {
        switch (true) {
            case null === $value: return 'null';
            case \is_bool($value): return 'bool';
            case \is_string($value): return 'string';
            case \is_array($value): return 'array';
            case \is_int($value): return 'int';
            case \is_float($value): return 'float';
            case \is_object($value): break;
            case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
            default:
                if (null === $type = @get_resource_type($value)) {
                    return 'unknown';
                }

                if ('Unknown' === $type) {
                    $type = 'closed';
                }

                return "resource ($type)";
        }

        $class = \get_class($value);

        if (false === strpos($class, '@')) {
            return $class;
        }

        return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
    }
}

Above is a polyfill that you can use in your code if you want to bring the get_debug_type() function to your own code that cannot require PHP 8.0 to run. This snippet is from Symfony's PHP 8.0 polyfill.

Backwards compatibility impact

get_debug_type() is a new function. Unless you have declared a function with an identical name in the global namespace, this should not bring any BC issues.


RFC Externals.io discussion Implementation