Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which is a faster approach to typechecking in PHP? gettype() or multiple is_*()

In PHP, which is dynamically typed, we can create functions that may accept multiple data types as parameters. We can then operate on the data depending on the type of the variable. There are two ways to do this:

Approach One:

function doSomething1($param) {
    $type = gettype($param);
    if ($type === 'string') {
        // do something
    }
    else if ($type === 'integer') {
        // do something
    }
    else if ($type === 'array') {
        // do something
    }
}

Approach Two:

function doSomething2($param) {
    if (is_string($param)) {
        // do something
    }
    else if (is_int($param)) {
        // do something
    }
    else if (is_array($param)) {
        // do something
    }
}
  1. As far as I know, these two approaches are functionally equivalent from a testing perspective, but since PHP has so many gotchas, I gotta ask if there is anything I could miss if I favour one approach over the other?

  2. From a performance perspective, is it right to say approach one is faster than two because PHP function calls are expensive? Or is gettype() a much more expensive operation than the individual is_*() functions?

  3. Is there any coding idioms / style guides regarding this?

Update From my benchmark using PHP 7.0.4, a million iterations of doSomething2() took 159ms, slightly less than half the time of doSomething1() at 315ms. This was regardless of whether a string (first check) or array (last check) was passed in. This seems to suggest that gettype() is indeed an expensive operation, and more expensive than multiple function calls using is_*().

Anyone with more insight into why this might be, your help is appreciated.

like image 587
light Avatar asked Oct 18 '22 12:10

light


1 Answers

Let's compare C-code of gettype and is_string functions.

gettype:

PHP_FUNCTION(gettype)
{
    zval *arg;
    zend_string *type;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ZVAL(arg)
    ZEND_PARSE_PARAMETERS_END();

    type = zend_zval_get_type(arg);
    if (EXPECTED(type)) {
        RETURN_INTERNED_STR(type);
    } else {
        RETURN_STRING("unknown type");
    }
}

So, it creates string type and fill it by result of calling function zend_zval_get_type, which is:

ZEND_API zend_string *zend_zval_get_type(const zval *arg) /* {{{ */
{
    switch (Z_TYPE_P(arg)) {
        case IS_NULL:
            return ZSTR_KNOWN(ZEND_STR_NULL);
        case IS_FALSE:
        case IS_TRUE:
            return ZSTR_KNOWN(ZEND_STR_BOOLEAN);
        case IS_LONG:
            return ZSTR_KNOWN(ZEND_STR_INTEGER);
        case IS_DOUBLE:
            return ZSTR_KNOWN(ZEND_STR_DOUBLE);
        case IS_STRING:
            return ZSTR_KNOWN(ZEND_STR_STRING);
        case IS_ARRAY:
            return ZSTR_KNOWN(ZEND_STR_ARRAY);
        case IS_OBJECT:
            return ZSTR_KNOWN(ZEND_STR_OBJECT);
        case IS_RESOURCE:
            if (zend_rsrc_list_get_rsrc_type(Z_RES_P(arg))) {
                return ZSTR_KNOWN(ZEND_STR_RESOURCE);
            } else {
                return ZSTR_KNOWN(ZEND_STR_CLOSED_RESOURCE);
            }
        default:
            return NULL;
    }
}

Let's compare with is_string, for example:

PHP_FUNCTION(is_string)
{
    php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_STRING);
}

Go to php_is_type:

static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
{
    zval *arg;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ZVAL(arg)
    ZEND_PARSE_PARAMETERS_END();

    if (Z_TYPE_P(arg) == type) {
        if (type == IS_RESOURCE) {
            const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
            if (!type_name) {
                RETURN_FALSE;
            }
        }
        RETURN_TRUE;
    } else {
        RETURN_FALSE;
    }
}

So, the core logic of these methods is absolutely the same – PHP uses Z_TYPE_P to detect the type of the variable.

But in case of gettype it also creates additional string for result and fill it with the constant string instead of just returning boolean TRUE or FALSE in case of is_* functions. So, definitely is_* functions are faster :)

like image 97
Ivan Pomortsev Avatar answered Oct 29 '22 18:10

Ivan Pomortsev