I'm in need of a function that will parse a querystring into a multidimensional array.
The querystring could look somewhat like this:
?fields=incidents:(id,shortdescription,action,request);people:(unid,name)&format=json
The array returned should like like this:
array(
'fields' => array(
'incidents' => array(
'id',
'shortdescription',
'action',
'request'
),
'people' => array(
'unid',
'name'
),
),
'format' => 'json'
);
I want the separators to be dynamic, so they would reside in an array like this:
$separators = array(';', ':', ',');
The order of the seperators in this array will determine the order in which the querystring is parsed.
Someone have anything like this handy???
Regards,
Mark
To get you started:
$final = array();
$keys = explode("&", $_SERVER['QUERY_STRING']);
foreach ($keys as $key => $value) {
$final[$key] = $value;
$child = explode(";", $value);
foreach ($child as $key2 => $value2) {
$final[$key][$key2] = array();
$subchild = explode(":", $value2);
foreach ($subchild as $key3 => $value3) {
$subsubchild = explode(",", $value3);
$final[$key][$key2][$key3] = $subsubchild;
}
}
}
I didn't test this, but hopefully get the idea of where I'm going...
This could be updated to create a function that accepts a delimiter to make it truly recursive so that rather than having a loop inside a loop inside a loop you could call this function...
Myself, I came up with this:
protected static function split( $value, $sep = ',' )
{
if ( str_contains( $value, $sep ) )
$value = explode( $sep, $value );
return $value;
}
protected static function multi_split( $value )
{
$arr = array();
// Do we have multiple models?
$parts = static::split( $value, '+' );
if ( count( $parts ) > 1 ) {
foreach( $parts as $part ) {
$models = static::split( $part, ':' );
if ( count( $models ) > 1 ) {
$fields = static::split( trim($models[1], '()'), ',' );
$arr[$models[0]] = $fields;
}
}
return $arr;
}
// Do we have a single model?
$parts = static::split( $value, ':' );
if ( count( $parts ) > 1 ) {
$fields = static::split( trim($parts[1], '()'), ',' );
$arr[$parts[0]] = $fields;
return $arr;
}
// Do we have a comma-separated param?
$parts = static::split( $value, ',' );
if ( count( $parts ) > 1 )
return $parts;
// Do we have a string param?
return $value;
}
There is a lot of repetition that I would like to get rid off. Also, it is not very dynamic yet!
Related
I have an array that looks like this:
$array = array (
[level_1] => array (
[level_2] => array (
[level_3] => something
)
),
[level_12] => array (
[level_2] => somethingelse
),
[level_13] => array (
[level_22] => array (
[level_3] => something
)
),
);
The keys or values aren't always unique but the branches are.
And I have a string that looks like this:
$string = 'level_1-level_2-level_3';
Those are the keys for a branch.
And I need to somehow get the value from the array based on that string?
Like this:
$string_array = explode('-', $string);
$array[$string_array[0]][$string_array[1]][$string_array[2]] // something
But since the depth can be different this is not a viable solution...
Try this simple example, no need for a recursive function:
function get_item( $path, $array )
{
$paths = explode( '-', $path );
$result = $array;
foreach ( $paths as $path) {
isset( $result[$path] ) ? $result = $result[$path] : $result = false;
}
return $result;
}
$path = 'level_1-level_2-level_3';
echo get_item( $path, $array );
Try this:
$array = array (
'level_1' => array (
'level_2' => array (
'level_3' => 'something'
)
),
'level_12' => array (
'level_2' => 'somethingelse'
),
'level_13' => array (
'level_22' => array (
'level_3' => 'something'
)
),
);
$string = 'level_1-level_2-level_3';
$keys = explode('-', $string);
echo getItemIterative($keys, $array);
echo "\n";
echo getItemRecursive($keys, $array);
function getItemIterative($keys, $array)
{
$value = null;
foreach ($keys as $key) {
if ($value == null) {
$value = $array[$key];
}
if (is_array($value) && array_key_exists($key, $value)) {
$value = $value[$key];
}
}
return $value;
}
function getItemRecursive($keys, $array)
{
$key = array_shift($keys);
$value = $array[$key];
if (empty($keys)) {
return $value;
} else {
return getItemRecursive($keys, $value);
}
}
Make a $result variable which initially points to the root of the array, and loop through the levels of your $string_array 'til $result points at the leaf you were looking for.
// stuff you already have:
$array = array(...); // your big array
$string = 'level_1-level_2-level_3';
$string_array = explode('-', $string);
// new stuff:
$result = $array;
foreach ($string_array as $level) {
$result = $result[$level];
}
echo $result; // 'something'
Working example: Ideone
Searched for so long but didn't get any feasible answer.
A) Input:
$array = array(
'order_source' => array('google','facebook'),
'order_medium' => 'google-text'
);
Which looks like:
Array
(
[order_source] => Array
(
[0] => google
[1] => facebook
)
[order_medium] => google-text
)
B) Required output:
order_source=google&order_source=facebook&order_medium=google-text
C) What I've tried (http://3v4l.org/b3OYo):
$arr = array('order_source' => array('google','facebook'), 'order_medium' => 'google-text');
function bqs($array, $qs='')
{
foreach($array as $par => $val)
{
if(is_array($val))
{
bqs($val, $qs);
}
else
{
$qs .= $par.'='.$val.'&';
}
}
return $qs;
}
echo $qss = bqs($arr);
D) What I'm getting:
order_medium=google-text&
Note: It should also work for any single dimensional array like http_build_query() works.
I hope that this is what you are looking for, it works with single to n-dimensinal arrays.
$walk = function( $item, $key, $parent_key = '' ) use ( &$output, &$walk ) {
is_array( $item )
? array_walk( $item, $walk, $key )
: $output[] = http_build_query( array( $parent_key ?: $key => $item ) );
};
array_walk( $array, $walk );
echo implode( '&', $output ); // order_source=google&order_source=facebook&order_medium=google-text
You don't really need to do anything special here.
$array = array(
'order_source' => array('google', 'facebook'),
'order_medium' => 'google-text'
);
$qss = http_build_query($array);
On the other side:
var_dump($_GET);
Result:
array(2) {
["order_source"]=>
array(2) {
[0]=>
string(6) "google"
[1]=>
string(8) "facebook"
}
["order_medium"]=>
string(11) "google-text"
}
This really is the best way to send arrays as GET variables.
If you absolutely must have the output as you've defined, this will do it:
function bqs($array, $qs = false) {
$parts = array();
if ($qs) {
$parts[] = $qs;
}
foreach ($array as $key => $value) {
if (is_array($value)) {
foreach ($value as $value2) {
$parts[] = http_build_query(array($key => $value2));
}
} else {
$parts[] = http_build_query(array($key => $value));
}
}
return join('&', $parts);
}
Although as you found in the comments if you are trying to pass this as $_GET you will have override problems, the solution to your problem to get desired results using recursive functions would be:
function bqs($array, $qs='',$index = false)
{
foreach($array as $par => $val)
{
if($index)
$par = $index;
if(is_array($val))
{
$qs = bqs($val, $qs,$par);
}
else
{
$qs .= $par.'='.$val.'&';
}
}
return $qs;
}
where i am concatinating the $qs string if it's an array and passing the index as a reference along with the value if it's an array()
fixed
After supplying the $index you do not need to concatenate again. See here: http://3v4l.org/QHF5G
Well, is pretty common to get an array like this in relations M-N in mysql:
[0]
'book_id' => 1
'title' => 'title'
'author_id' => 1
'author_name' => 'name1'
[1]
'book_id' => 1
'title' => 'title'
'author_id' => 2
'author_name' => 'name2'
So, there is an elegant/easy way to convert this kind or arrays in something like this?
'book_id' => 1
'title' => 'title'
'authors' => array( 0 => array( 'author_id' => 1, 'author_name' => 'name1' ),
1 => array( 'author_id' => 2, 'author_name' => 'name2' ) )
I don't find any script or combinations of functions that made this... and is pretty common, maybe I have not searched correctly.. don't know how to call the problem...
Any ideas or experiences?
Thanks :)
PS: I don't want to use GROUP BY + GROUP CONCACT in MySQL, I found that pretty ugly solution...
EDIT:
I'm working in something generic, not only to solve this specific problem..
I would do the JOIN in SQL and the post-process the results into a nested array in PHP code.
$bookkeys = array_flip(array("book_id", "title"));
$authorkeys = array_flip(array("author_id", "author_name"));
$stmt = $dbh->query("...");
$books = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (!array_key_exists($row["book_id"], $books)) {
$books[ $row["book_id"] ] = array_intersect_key($row, $bookkeys);
}
$books[ $row["book_id"] ]["authors"][] = array_intersect_key($row, $authorkeys);
}
If you want the final array to be an ordinal array instead of keyed by book_id, you can convert it:
$books = array_values($books);
A generic solution for multiple sub-keys:
$rows = collapse_rows($rows, 'book_id', array(
'author_id' => array('author_name'),
));
function collapse_rows($rows, $key, $subkeys){
# make a map of all fields we don't perform a simple copy on
$skip_fields = array();
foreach ($subkeys as $k => $v){
$skip_fields[$k] = 1;
foreach ($v as $v2) $skip_fields[$v2] = 1;
}
# now build our output
$out = array();
foreach ($rows as $row){
$row2 =& $out[$row[$key]];
# simple fields first
foreach ($row as $k => $v){
if (!$skip_fields[$k])){
$row2[$k] = $v;
}
}
# now subkeys
foreach ($subkeys as $k => $v){
$sub_row = array($k => $row[$k]);
foreach ($v as $v2) $sub_row[$v2] = $row[$v2];
$row2[$k][$sub_row[$k]] = $sub_row;
}
}
return $out;
}
This lets you pass the primary key and a hash of sub keys and fields to aggregate by.
Well, I have a "solution" based in Bill Karwin suggestion:
$key = 'authors';
$output = array();
if ( is_array( $elements ) )
{
$all_keys = array_keys( $elements[0] );
$conflicted_keys = call_user_func_array( 'array_diff_assoc', $elements );
$conflicted_keys = array_keys( $conflicted_keys );
$good_keys = array_diff( $all_keys, $conflicted_keys );
$conflicted_keys_fliped = array_flip( $conflicted_keys );
$good_keys_fliped = array_flip( $good_keys );
foreach ( $elements as $row )
{
if ( !array_key_exists( $row[$good_keys[0]], $output ) ) {
$output[ $row[$good_keys[0]] ] = array_intersect_key($row, $good_keys_fliped);
}
$output[ $row[$good_keys[0]] ][ $key ][] = array_intersect_key( $row, $conflicted_keys_fliped );
}
}
$output = array_values($output);
var_dump($output);
Don't know if is the most eficient/correct.. On other hand, this will not work if there are more than one type of conflict... patches will be good received :)
thanks to all!
Something simple like this would work:
$out = array();
foreach ($rows as $row){
$out[$row['book_id']]['book_id'] = $row['book_id'];
$out[$row['book_id']]['title'] = $row['title'];
$out[$row['book_id']]['authors'][$row['author_id']] = array(
'author_id' => $row['author_id'],
'author_name' => $row['author_name'],
);
}
Rows are keyed by book ID and then author ID so we only have one output row per book and then per author inside that row.
Well, this is the funcion that finally I will use, it is based completly in the idea of Cal but trying to use native functions as said Bill Karvin:
protected function collapse_rows( $rows, $key, $subkeys ) {
if ( is_array( $rows ) )
{
// make a map of all fields we don't perform a simple copy on
$skip_fields = array();
foreach( $subkeys as $sub ) {
$skip_fields = array_merge( $skip_fields, array_reverse( $sub ) );
}
$skip_fields = array_flip( $skip_fields );
// now build our output
$out = array();
foreach ( $rows as $row ) {
// simple fields first
if ( !array_key_exists( $row[$key], $out ) ) {
$out[ $row[$key] ] = array_diff_key( $row, $skip_fields );
}
// now subkeys
foreach ( $subkeys as $k => $v ) {
$value = array_intersect_key( $row, array_flip( $subkeys[$k] ) );
if ( !empty( $value ) ) {
$out[ $row[$key] ][ $k ][] = $value;
}
}
}
return array_values( $out );
}
return $rows;
}
Once done, I don't think it has a too much good performance... I wonder how other people solve this kind of problems...
Whatever, thanks to both!
The code below dynamically concatenates keys to an existing array $options_pool. So the final form should be: $options_pool[ $base_key ][ $first_key ][ $second_key ]... This is so I can dynamically assign a value to the elements of the array $options_pool which is multidimensional.
foreach( $this->post_vars as $name => $value ) {
//Look for $name key in array $options_pool if it exists.
//Use multi_array_key_exists() to handle the task
//It should return something like "fruit:mango:apple_mango"
//Then dynamically call $options_pool based on the data. Like so: $options_pool[ 'fruit' ][ 'mango' ][ 'apple_mango' ] = $value;
$match_key = multi_array_key_exists( $name, $options_pool );
$option_keys = explode( ':', $match_key );
$option_keys_length = count( $option_keys );
$option_name_array = array();
if( 0 < $option_keys_length ) {
for( $c = $option_keys_length; $c > 0; $c-- ) {
$sub_keys = '$options_pool';
for( $c_sub = 0; $c_sub < $c ; $c_sub++ ) {
$sub_keys .= '[ $option_keys[ '. $c_sub . ' ] ]';
}
$option_name_array[] = $sub_keys;
}
foreach( $option_name_array as $f_var_name ) {
//the following line should equal to: $options_pool[ 'fruit' ][ 'mango' ][ 'apple_mango' ] = $value;
$f_var_name = $value;
}
}
}
//The $options_pool array
$options_pool = array( 'car' => '', 'animals' => '', 'fruit' => array( 'mango' => array( 'apple_mango' => '' ));
I think the logic is correct except that this portion of the code:
foreach( $option_name_array as $f_var_name ) {
$f_var_name = $value; //particularly this line
}
doesn't work? I've tested printing the value of $f_var_name and the result is correct but it doesn't really call the array?
That is incorrect, you are right.
The variable name will always be $options_pool.
You can pass the keys as explode(':', $name) and later assign them.
By the way, your code at
foreach( $option_keys as $option_keys_value ) {
$option_key_names[] = $option_keys_value;
}
Do you realize that it just copies $option_keys as $option_key_names just as if you had written $option_key_names = $option_keys; (in this code) ?
Maybe with a stack you could do this iteratively but with recursion it is just more natural, as you see here
function getVariableToWrite(&$reference, $nested_opts, $write) {
if(empty($nested_ops)) // Base case
return $reference = write;
if(isset($reference[array_shift($nested_ops)]))
return getVariableToWrite($reference, $nested_ops, $write);
throw new Exception("The key does not exist..");
}
And then just
foreach( $this->post_vars as $name => $value ) {
// Your work over here until...
$match_key = "fruit:mango:apple_mango";
$option_keys = explode( ':', $match_key );
getVariableToWrite($options_pool, $option_keys, $value);
}
Will this do the work?
In your foreach, you need to pass the value by reference, so you can edit it.
foreach( $option_name_array as &$f_var_name ){
$f_var_name = $value;
}
Try this...
foreach( $option_name_array as $key => $f_var_name ) {
$option_name_array[$key] = $value; //particularly this line
}
I have a multidimensional array with strings as keys. I want to perform a function (to manipulate the strings) on those keys and then write to a new array (i.e. leave the original array unchanged).
Example:
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678);
"bar_old" => array("car_old" => 4321, "tar_old" => 8765);
);
Becomes:
$newArr = array(
"foo_new" => array("moo_new" => 1234, "woo_new" => 5678);
"bar_new" => array("car_new" => 4321, "tar_new" => 8765);
);
This is just an example, the actual array has more levels/dimensions. Oh and my function doesn't replace "_old" with "_new", again, just an example.
I hope I made some sense, thanks in advance!
Edit: I added a function for printing out the changed array. You may include the code on a website and it will show the result. New edited code:
// array initialisation
oldArr = array();
$subArr1 = array();
$subArr2 = array();
$subArr1["moo_old"]=1234;
$subArr1["woo_old"]=5678;
$subArr2["car_old"]=4321;
$subArr2["tar_old"]=8765;
$oldArr["foo_old"]=$subArr1;
$oldArr["bar_old"]=$subArr2;
$oldArr; // make a copy of the array
// function which replaces recursivly the keys of the array
function renameArrayKeys( $oldArr ) {
$copyArr = $oldArr;
if( is_array( $oldArr) && count( $oldArr ) ) {
foreach ( $oldArr as $k => $v ) {
unset($copyArr[$k]); // removes old entries
$newKey = str_replace( '_old', '_new', $k );
if( is_array( $v ) ) {
$copyArr[ $newKey ] = renameArrayKeys( $v );
}
else {
$copyArr[ $newKey ] = $v;
}
}
return $copyArr;
}
}
// prints out the keys and values of the changed array
function printout($arr ){
foreach ($arr as $k => $val ) {
echo $k."=>".$val." | ";
if( is_array( $val ) ) {
printout( $val );
}
}
}
// calls the above functions
$changedArr = renameArrayKeys($oldArr);
printout($changedArr);
I'm probably slightly late, but recursion is the way forward with this!
$replace_from = "_old"; //can also be array i.e. array("foo_old", "bar_old")
$replace_to = "_new"; //can also be an array i.e. array("foo_new", "bar_new")
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678),
"bar_old" => array("car_old" => 4321, "tar_old" => 8765),
);
function replace($arr){
global $replace_from, $replace_to;
$newArr = array();
foreach($arr as $key => $value){
$newArr[str_replace($replace_from,$replace_to,$key)] = (is_array($value)) ? replace($value) : $value;
}
return $newArr;
}
print_r (replace($oldArr));
Something like this:
function renameKeys( $arr )
{
if( is_array( $arr ) && count( $arr ) ) {
foreach ( $arr as $k => $v ) {
$nk = str_replace( '_old', '_new', $k );
if( is_array( $v ) ) {
$v = renameKeys( $v );
}
$arr[ $nk ] = $v;
unset( $arr[$k] );
}
}
return $arr;
}
$oldArr = array(
"foo_old" => array("moo_old" => 1234, "woo_old" => 5678) ,
"bar_old" => array("car_old" => 4321, "tar_old" => 8765)
);
$nArr = renameKeys( $oldArr );
print_r( $nArr );
Closure version. This doesn't mess up the namespace.
<?php
$from = '_old';
$to = '_new';
$old_arr = array(
'foo_old' => array('moo_old' => 1234, 'woo_old' => 5678),
'bar_old' => array('car_old' => 4321, 'tar_old' => 8765),
);
$func = function ($arr) use (&$func, $from, $to) {
$new_arr = array();
foreach($arr as $k => $v){
$new_arr[str_replace($from, $to, $k)] = is_array($v) ? $func($v) : $v;
}
return $new_arr;
};
print_r($func($old_arr));