Dynamic formation of variable name in PHP? - php

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
}

Related

Turn string into multi-dimensional array equivalent

I'm trying to parse a string from a URL and convert it into a PHP array, but so far I haven't been able to get it to work correctly.
The string contains comma separated values which are then grouped by periods, for example:
course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text
The Array equivalent to this string would be:
PHP:
$array = [
'course' => [
'id',
'title',
'author',
'duration',
'trailer' => [
'video_key',
'duration'
],
'lessons' => [
'id',
'title'
]
],
'track' => [
'id',
'title',
'caption' => [
'srt',
'vtt',
'text'
]
]
];
My current solution is this:
PHP:
$string = 'course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text';
$parameters = explode( ',', $string );
$array = array();
$inset = array();
foreach ( $parameters as $parameter ) {
$segments = explode( '.', $parameter );
if ( ! array_key_exists( $segments[0], $inset ) ) {
$array[ $segments[0] ] = array();
$inset[ $segments[0] ] = true;
}
$array[ $segments[0] ] = array_merge( $array[ $segments[0] ], addSegment( $segments, 1 ) );
}
function addSegment( $segments, $counter ) {
$results = array();
if ( end( $segments ) == $segments[ $counter ] ) {
return array( $segments[ $counter ] => null );
}
$results[ $segments[ $counter ] ] = addSegment( $segments, $counter + 1 );
return $results;
}
This somewhat works, however; it fails with simpler string like this one
course,track,lessons
I think this calls for recursion but I'm not good at writing this so I'm hoping someone has already done this and can help me.
Thank you.
You can achieve this without recursion - just double loop and use of &:
$arr = explode(",", "course.id,course.title,course.author,course.duration,course.trailer.video_key,course.trailer.duration,course.lessons.id,course.lessons.title,track.id,track.title,track.caption.srt,track.caption.vtt,track.caption.text");
foreach($arr as $e) {
$e = explode(".", $e);
$obj = &$res; //set the current place root
while(count($e) > 1) { // last element will be add as value
$key = array_shift($e);
if (!isset($obj[$key]))
$obj[$key] = [];
$obj = &$obj[$key]; //reset the current place
}
$obj[] = array_shift($e); // add the last as value to current place
}
$res will contain your desire output

Recursive querystring parser

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!

How can I convert this M-N mysql array in this other array in PHP? (examples inside)

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!

PHP: rename multidimensional array's keys

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));

most efficient method of turning multiple 1D arrays into columns of a 2D array

As I was writing a for loop earlier today, I thought that there must be a neater way of doing this... so I figured I'd ask. I looked briefly for a duplicate question but didn't see anything obvious.
The Problem:
Given N arrays of length M, turn them into a M-row by N-column 2D array
Example:
$id = [1,5,2,8,6]
$name = [a,b,c,d,e]
$result = [[1,a],
[5,b],
[2,c],
[8,d],
[6,e]]
My Solution:
Pretty straight forward and probably not optimal, but it does work:
<?php
// $row is returned from a DB query
// $row['<var>'] is a comma separated string of values
$categories = array();
$ids = explode(",", $row['ids']);
$names = explode(",", $row['names']);
$titles = explode(",", $row['titles']);
for($i = 0; $i < count($ids); $i++) {
$categories[] = array("id" => $ids[$i],
"name" => $names[$i],
"title" => $titles[$i]);
}
?>
note: I didn't put the name => value bit in the spec, but it'd be awesome if there was some way to keep that as well.
Maybe this? Not sure if it's more efficient but it's definitely cleaner.
/*
Using the below data:
$row['ids'] = '1,2,3';
$row['names'] = 'a,b,c';
$row['titles'] = 'title1,title2,title3';
*/
$categories = array_map(NULL,
explode(',', $row['ids']),
explode(',', $row['names']),
explode(',', $row['titles'])
);
// If you must retain keys then use instead:
$withKeys = array();
foreach ($row as $k => $v) {
$v = explode(',', $v);
foreach ($v as $k2 => $v2) {
$withKeys[$k2][$k] = $v[$k2];
}
}
print_r($categories);
print_r($withKeys);
/*
$categories:
array
0 =>
array
0 => int 1
1 => string 'a' (length=1)
2 => string 'title1' (length=6)
...
$withKeys:
array
0 =>
array
'ids' => int 1
'names' => string 'a' (length=1)
'titles' => string 'title1' (length=6)
...
*/
Just did a quick simple benchmark for the 4 results on this page and got the following:
// Using the following data set:
$row = array(
'ids' => '1,2,3,4,5',
'names' => 'abc,def,ghi,jkl,mno',
'titles' => 'pqrs,tuvw,xyzA,BCDE,FGHI'
);
/*
For 10,000 iterations,
Merge, for:
0.52803611755371
Merge, func:
0.94854116439819
Merge, array_map:
0.30260396003723
Merge, foreach:
0.40261697769165
*/
Yup, array_combine()
$result = array_combine( $id, $name );
EDIT
Here's how I'd handle your data transformation
function convertRow( $row )
{
$length = null;
$keys = array();
foreach ( $row as $key => $value )
{
$row[$key] = explode( ',', $value );
if ( !is_null( $length ) && $length != count( $row[$key] ) )
{
throw new Exception( 'Lengths don not match' );
}
$length = count( $row[$key] );
// Cheap way to produce a singular - might break on some words
$keys[$key] = preg_replace( "/s$/", '', $key );
}
$output = array();
for ( $i = 0; $i < $length; $i++ )
{
foreach ( $keys as $key => $singular )
{
$output[$i][$singular] = $row[$key][$i];
}
}
return $output;
}
And a test
$row = convertRow( $row );
echo '<pre>';
print_r( $row );

Categories