static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
                          char *kw, int kw_len, int min_argc, int has_timeout,
                          char **cmd, int *cmd_len, short *slot)
{
    zval *z_args, *z_ele, *ztimeout = NULL;
    HashTable *ht_arr;
    char *key;
    int key_free, i, tail;
    size_t key_len;
    int single_array = 0, argc = ZEND_NUM_ARGS();
    smart_string cmdstr = {0};
    short kslot = -1;
    zend_string *zstr;

    if (argc < min_argc) {
        zend_wrong_param_count();
        return FAILURE;
    }

    // Allocate args
    z_args = emalloc(argc * sizeof(zval));
    if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
        efree(z_args);
        return FAILURE;
    }

    // Handle our "single array" case
    if (has_timeout == 0) {
        single_array = argc==1 && Z_TYPE(z_args[0]) == IS_ARRAY;
    } else {
        single_array = argc==2 && Z_TYPE(z_args[0]) == IS_ARRAY &&
            (Z_TYPE(z_args[1]) == IS_LONG || Z_TYPE(z_args[1]) == IS_DOUBLE);
        if (single_array)
            ztimeout = &z_args[1];
    }

    // If we're running a single array, rework args
    if (single_array) {
        ht_arr = Z_ARRVAL(z_args[0]);
        argc = zend_hash_num_elements(ht_arr);
        if (has_timeout) argc++;
        efree(z_args);
        z_args = NULL;

        /* If the array is empty, we can simply abort */
        if (argc == 0) return FAILURE;
    }

    // Begin construction of our command
    redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);

    if (single_array) {
        ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
            zstr = zval_get_string(z_ele);
            key = ZSTR_VAL(zstr);
            key_len = ZSTR_LEN(zstr);
            key_free = redis_key_prefix(redis_sock, &key, &key_len);

            // Protect against CROSSLOT errors
            if (slot) {
                if (kslot == -1) {
                    kslot = cluster_hash_key(key, key_len);
                } else if (cluster_hash_key(key,key_len)!=kslot) {
                    zend_string_release(zstr);
                    if (key_free) efree(key);
                    php_error_docref(NULL, E_WARNING,
                        "Not all keys hash to the same slot!");
                    return FAILURE;
                }
            }

            // Append this key, free it if we prefixed
            redis_cmd_append_sstr(&cmdstr, key, key_len);
            zend_string_release(zstr);
            if (key_free) efree(key);
        } ZEND_HASH_FOREACH_END();
        if (ztimeout) {
            ZEND_ASSERT(Z_TYPE_P(ztimeout) == IS_LONG || Z_TYPE_P(ztimeout) == IS_DOUBLE);
            if (Z_TYPE_P(ztimeout) == IS_LONG) {
                redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(ztimeout));
            } else {
                redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(ztimeout));
            }
        }
    } else {
        if (has_timeout) {
            zend_uchar type = Z_TYPE(z_args[argc - 1]);
            if (type == IS_LONG || type == IS_DOUBLE) {
                ztimeout = &z_args[argc - 1];
            } else {
                php_error_docref(NULL, E_ERROR, "Timeout value must be a long or double");
                efree(z_args);
                return FAILURE;
            }
        }
        tail = has_timeout ? argc-1 : argc;
        for(i = 0; i < tail; i++) {
            zstr = zval_get_string(&z_args[i]);
            key = ZSTR_VAL(zstr);
            key_len = ZSTR_LEN(zstr);

            key_free = redis_key_prefix(redis_sock, &key, &key_len);

            /* Protect against CROSSSLOT errors if we've got a slot */
            if (slot) {
                if ( kslot == -1) {
                    kslot = cluster_hash_key(key, key_len);
                } else if (cluster_hash_key(key,key_len)!=kslot) {
                    php_error_docref(NULL, E_WARNING,
                        "Not all keys hash to the same slot");
                    zend_string_release(zstr);
                    if (key_free) efree(key);
                    efree(z_args);
                    return FAILURE;
                }
            }

            // Append this key
            redis_cmd_append_sstr(&cmdstr, key, key_len);
            zend_string_release(zstr);
            if (key_free) efree(key);
        }

        if (ztimeout) {
            ZEND_ASSERT(Z_TYPE_P(ztimeout) == IS_LONG || Z_TYPE_P(ztimeout) == IS_DOUBLE);
            if (Z_TYPE_P(ztimeout) == IS_DOUBLE) {
                redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(ztimeout));
            } else {
                redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(ztimeout));
            }
        }

        // Cleanup args
        efree(z_args);
    }

    // Push out parameters
    if (slot) *slot = kslot;
    *cmd = cmdstr.c;
    *cmd_len = cmdstr.len;

    return SUCCESS;
}