Parse string containing dots in php

I would parse the following string:

$str = 'ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1';                 
parse_str($str,$f);

I wish that $f be parsed into:

array(
    'ProceduresCustomer.tipi_id' => '10',
    'ProceduresCustomer.id' => '1'
)

Actually, the parse_str returns

array(
        'ProceduresCustomer_tipi_id' => '10',
        'ProceduresCustomer_id' => '1'
    )

Beside writing my own function, does anybody know if there is a php function for that?


From the PHP Manual:

Dots and spaces in variable names are converted to underscores. For example <input name="a.b" /> becomes $_REQUEST["a_b"].

So, it is not possible. parse_str() will convert all periods to underscores. If you really can't avoid using periods in your query variable names, you will have to write custom function to achieve this.

The following function (taken from this answer) converts the names of each key-value pair in the query string to their corresponding hexadecimal form and then does a parse_str() on it. Then, they're reverted back to their original form. This way, the periods aren't touched:

function parse_qs($data)
{
    $data = preg_replace_callback('/(?:^|(?<=&))[^=[]+/', function($match) {
        return bin2hex(urldecode($match[0]));
    }, $data);

    parse_str($data, $values);

    return array_combine(array_map('hex2bin', array_keys($values)), $values);
}

Example usage:

$data = parse_qs($_SERVER['QUERY_STRING']);

Quick 'n' dirty.

$str = "ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1";    

function my_func($str){
    $expl = explode("&", $str);
    foreach($expl as $r){
        $tmp = explode("=", $r);
        $out[$tmp[0]] = $tmp[1];
    }
    return $out;
}

var_dump(my_func($str));

array(2) {
    ["ProceduresCustomer.tipi_id"]=> string(2) "10"
    ["ProceduresCustomer.id"]=>string(1) "1"
}

This quick-made function attempts to properly parse the query string and returns an array.

The second (optional) parameter $break_dots tells the parser to create a sub-array when encountering a dot (this goes beyond the question, but I included it anyway).

/**
 * parse_name -- Parses a string and returns an array of the key path
 * if the string is malformed, only return the original string as a key
 *
 * $str The string to parse
 * $break_dot Whether or not to break on dots (default: false)
 *
 * Examples :
 *   + parse_name("var[hello][world]") = array("var", "hello", "world")
 *   + parse_name("var[hello[world]]") = array("var[hello[world]]") // Malformed
 *   + parse_name("var.hello.world", true) = array("var", "hello", "world")
 *   + parse_name("var.hello.world") = array("var.hello.world")
 *   + parse_name("var[hello][world") = array("var[hello][world") // Malformed
 */
function parse_name ($str, $break_dot = false) {
    // Output array
    $out = array();
    // Name buffer
    $buf = '';
    // Array counter
    $acount = 0;
    // Whether or not was a closing bracket, in order to avoid empty indexes
    $lastbroke = false;

    // Loop on chars
    foreach (str_split($str) as $c) {
        switch ($c) {
            // Encountering '[' flushes the buffer to $out and increments the
            // array counter
            case '[':
                if ($acount == 0) {
                    if (!$lastbroke) $out[] = $buf;
                    $buf = "";
                    $acount++;
                    $lastbroke = false;
                // In this case, the name is malformed. Return it as-is
                } else return array($str);
                break;

            // Encountering ']' flushes rge buffer to $out and decrements the
            // array counter
            case ']':
                if ($acount == 1) {
                    if (!$lastbroke) $out[] = $buf;
                    $buf = '';
                    $acount--;
                    $lastbroke = true;
                // In this case, the name is malformed. Return it as-is
                } else return array($str);
                break;

            // If $break_dot is set to true, flush the buffer to $out.
            // Otherwise, treat it as a normal char.
            case '.':
                if ($break_dot) {
                    if (!$lastbroke) $out[] = $buf;
                    $buf = '';
                    $lastbroke = false;
                    break;
                }

            // Add every other char to the buffer
            default:
                $buf .= $c;
                $lastbroke = false;
        }
    }

    // If the counter isn't back to 0 then the string is malformed. Return it as-is
    if ($acount > 0) return array($str);

    // Otherwise, flush the buffer to $out and return it.
    if (!$lastbroke) $out[] = $buf;
    return $out;
}

/**
 * decode_qstr -- Take a query string and decode it to an array
 *
 * $str The query string
 * $break_dot Whether or not to break field names on dots (default: false)
 */
function decode_qstr ($str, $break_dots = false) {
    $out = array();

    // '&' is the field separator 
    $a = explode('&', $str);

    // For each field=value pair:
    foreach ($a as $param) {
        // Break on the first equal sign.
        $param = explode('=', $param, 2);

        // Parse the field name
        $key = parse_name($param[0], $break_dots);

        // This piece of code creates the array structure according to th
        // decomposition given by parse_name()
        $array = &$out; // Reference to the last object. Starts to $out
        $append = false; // If an empty key is given, treat it like $array[] = 'value'

        foreach ($key as $k) {
            // If the current ref isn't an array, make it one
            if (!is_array($array)) $array = array();
            // If the current key is empty, break the loop and append to current ref
            if (empty($k)) {
                $append = true;
                break;
            }
            // If the key isn't set, set it :)
            if (!isset($array[$k])) $array[$k] = NULL;

            // In order to walk down the array, we need to first save the ref in
            // $array to $tmp
            $tmp = &$array;
            // Deletes the ref from $array
            unset($array);
            // Create a new ref to the next item
            $array =& $tmp[$k];
            // Delete the save
            unset($tmp);
        }

        // If instructed to append, do that
        if ($append) $array[] = $param[1];
        // Otherwise, just set the value
        else $array = $param[1];

        // Destroy the ref for good
        unset($array);
    }

    // Return the result
    return $out;
}

I tried to correctly handle multi-level keys. The code is a bit hacky, but it should work. I tried to comment the code, comment if you have any question.

Test case:

var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1"));
// array(2) {
//   ["ProceduresCustomer.tipi_id"]=>
//   string(2) "10"
//   ["ProceduresCustomer.id"]=>
//   string(1) "1"
// }


var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1", true));
// array(1) {
//   ["ProceduresCustomer"]=>
//   array(2) {
//     ["tipi_id"]=>
//     string(2) "10"
//     ["id"]=>
//     string(1) "1"
//   }
// }