I have a string containing an array key position which I am trying to use this position to access the array ($arr).
Example of string ($str) which has a string value of svg.1.linearGradient.0.#style
This would be the equivalent of ['svg'][1]['linearGradient'][0]['#style']
How can I use the string to access/retrieve data from $arr using the above position?
For example, lets say i wanted to unset the array key unset($arr['svg'][1]['linearGradient'][0]['#style']) - how can i achieve this programmatically?
You can use the mechanism of passing the value by reference:
$result = &$arr;
$path = explode('.', $str);
for($i = 0; $i < count($path); $i++) {
$key = $path[$i];
if (is_array($result) && array_key_exists($key, $result)) {
if ($i == count($path) - 1) {
unset($result[$key]); // deleting an element with the last key
} else {
$result = &$result[$key];
}
} else {
break; // not found
}
}
unset($result); // resetting value by reference
print_r($arr);
fiddle
One method could be to split the string on ., then iterate through the "keys" to traverse your $arr object. (Please excuse my poor php, it's been a while...)
Example:
$arr = (object)[
"svg" => (array)[
(object)[],
(object)[
"linearGradient" => [
(object)[
"#style" => "testing",
],
],
],
],
];
$str = "svg.1.linearGradient.0.#style";
$keys = explode('.', $str);
$val = $arr;
foreach($keys as $key) {
$val = is_object($val)
? $val->$key
: $val[$key];
}
echo $val;
https://3v4l.org/h8YPv
Unsetting a key given the path:
$arr = (object)[
"svg" => (array)[
(object)[],
(object)[
"linearGradient" => [
(object)[
"#style" => "testing",
],
],
],
],
];
$str = "svg.1.linearGradient.0.#style";
$keys = explode('.', $str);
$exp = "\$arr";
$val = $arr;
foreach($keys as $index => $key) {
$exp .= is_object($val)
? "->{'" . $key . "'}"
: "[" . $key . "]";
$val = is_object($val) ? $val->$key : $val[$key];
}
eval("unset($exp);");
https://3v4l.org/Fdo6B
Docs
explode()
eval()
Related
I have a string as:
$string = "My name is {name}. I live in {detail.country} and age is {detail.age}";
and I have an array like that and it will be always in that format.
$array = array(
'name' => 'Jon',
'detail' => array(
'country' => 'India',
'age' => '25'
)
);
and the expected output should be like :
My name is Jon. I live in India and age is 25
So far I tried with the following method:
$string = str_replace(array('{name}','{detail.country}','{detail.age}'), array($array['name'],$array['detail']['country'],$array['detail']['age']));
But the thing is we can not use the plain text of string variable. It should be dynamic on the basis of the array keys.
You can use preg_replace_callback() for a dynamic replacement:
$string = preg_replace_callback('/{([\w.]+)}/', function($matches) use ($array) {
$keys = explode('.', $matches[1]);
$replacement = '';
if (sizeof($keys) === 1) {
$replacement = $array[$keys[0]];
} else {
$replacement = $array;
foreach ($keys as $key) {
$replacement = $replacement[$key];
}
}
return $replacement;
}, $string);
It also exists preg_replace() but the above one allows matches processing.
You can use a foreach to achieve that :
foreach($array as $key=>$value)
{
if(is_array($value))
{
foreach($value as $key2=>$value2)
{
$string = str_replace("{".$key.".".$key2."}",$value2,$string);
}
}else{
$string = str_replace("{".$key."}",$value,$string);
}
}
print_r($string);
The above will only work with a depth of 2 in your array, you'll have to use recurcivity if you want something more dynamic than that.
Here's a recursive array handler: http://phpfiddle.org/main/code/e7ze-p2ap
<?php
function replaceArray($oldArray, $newArray = [], $theKey = null) {
foreach($oldArray as $key => $value) {
if(is_array($value)) {
$newArray = array_merge($newArray, replaceArray($value, $newArray, $key));
} else {
if(!is_null($theKey)) $key = $theKey . "." . $key;
$newArray["{" . $key . "}"] = $value;
}
}
return $newArray;
}
$array = [
'name' => 'Jon',
'detail' => [
'country' => 'India',
'age' => '25'
]
];
$string = "My name is {name}. I live in {detail.country} and age is {detail.age}";
$array = replaceArray($array);
echo str_replace(array_keys($array), array_values($array), $string);
?>
echo "my name is ".$array['name']." .I live in ".$array['detail']['countery']." and my age is ".$array['detail']['age'];
The following PHP function finds the node in the multi-dimensional array by matching its key:
<?php
function & findByKey($array, $key) {
foreach ($array as $k => $v) {
if(strcasecmp($k, $key) === 0) {
return $array[$k];
} elseif(is_array($v) && ($find = findByKey($array[$k], $key))) {
return $find;
}
}
return null;
}
$array = [
'key1' => [
'key2' => [
'key3' => [
'key5' => 1
],
'key4' => '2'
]
]
];
$result = findByKey($array, 'key3');
I want the function to return a reference to the node so that if I change $result then the original $array also changes (like Javascript objects).
<?php
array_splice($result, 0, 0, '2');
//Changes $array also since the `$result` is a reference to:
$array['key1']['key2']['key3']
How can I do this?
You need to do two things:
1) specify your $array parameter as a reference:
function & findByKey(&$array, $key) {
2) assign the variable $result by reference using the &:
$result = &findByKey($array, 'key3');
Since you are calling your function recursively, you need to also assign $find by reference as well.
altogether:
function & findByKey(&$array, $key) {
foreach ($array as $k => $v) {
if(strcasecmp($k, $key) === 0) {
return $array[$k];
} elseif(is_array($v) && ($find = &findByKey($array[$k], $key))) {
return $find;
}
}
return null;
}
$result = &findByKey($array, 'key3');
$result = 'changed';
print_r($array);
This seems simple, but has perplexed me. I need to get $env to look like $desired result.
I tried using explode and foreach loops in a multitude of ways but keep getting stuck.
$env = [
["mysql_user"=>"user var"],
["mysql_pass"=>"password var"],
["rabbit_list_one"=>"listone var"],
["rabbit_list_two"=>"listtwo var"],
["system_var_main_deep"=>"deep this"],
["system_var_main_that"=>"deep that"]
];
$desiredResult = [
"mysql" => [
"user" => "user var",
"pass" => "password var"
],
"rabbit" => [
"list" => [
"one" => "listone var",
"two" => "listtwo var"
]
],
"system" => [
"var" => [
"main" => [
"deep" => "deep this",
"that" => "deep that"
]
]
]
];
Double-check the formatting on $env, because you're showing arrays inside of the $env array rather than just key/value pairs. Assuming your input is correct, and there are actually inner arrays, this should work:
$out = [];
foreach ($env as $piece) {
foreach ($piece as $key => $value) {
$key_full = explode('_', $key);
$key_last = array_pop($key_full);
$pointer = &$out;
foreach ($key_full as $key_level) {
if (!isset($pointer[$key_level])) {
$pointer[$key_level] = [];
}
$pointer = &$pointer[$key_level];
}
$pointer[$key_last] = $value;
}
}
Based on Netrilix's answer, this was the final solution. This handled the situation where FOO_BAR_BAX and FOO_BAR were both set. We opted to always take the array over the string. Thank you all for all your help!
public function build() {
$config = $_ENV;
$out = [];
foreach ($config as $key => $value) {
$key_full = explode('_', $key);
$key_last = strtolower(array_pop($key_full));
$pointer = &$out;
foreach ($key_full as $key_level) {
$key_level = strtolower($key_level);
if (!isset($pointer[$key_level])) {
$pointer[$key_level] = [];
}
$pointer = &$pointer[$key_level];
}
$pointer = !is_array($pointer) ? [] : $pointer;
if (!isset($pointer[$key_last])) {
$pointer[$key_last] = $value;
}
}
return $out;
}
In a normal array you can select this way
$key='example';
echo $array[$key];
How about in a multidimensional?
$keys='example[secondDimension][thirdDimension]';
echo $array[$keys];
What's the proper way to go about this?
i think this solution is good.
note that you have to wrap all keys with "[" and "]".
$array = array(
'example' => array(
'secondDimension' => array(
'thirdDimension' => 'Hello from 3rd dimension',
)
),
);
function array_get_value_from_plain_keys($array, $keys)
{
$result;
$keys = str_replace(array('[', ']'), array("['", "']"), $keys); // wrapping with "'" (single qoutes)
eval('$result = $array' . $keys . ';');
return $result;
}
$keys = '[example][secondDimension][thirdDimension]'; // wrap 1st key with "[" and "]"
echo array_get_value_from_plain_keys($array, $keys);
Learn more about eval() function
if you also want to check if the value is defined or not then you can use this function
function array_check_is_value_set_from_plain_keys($array, $keys)
{
$result;
$keys = str_replace(array('[', ']'), array("['", "']"), $keys); // wrapping with "'" (single qoutes)
eval('$result = isset($array' . $keys . ');');
return $result;
}
Giving a better name to that function will be appreciated ^^
Here is a solution without using eval:
$array = [
'example' => [
'secondDimension' => [
'thirdDimension' => 'Hello from 3rd dimension',
],
],
];
$keys = '[example][secondDimension][thirdDimension]';
function get_array_value( $array, $keys ) {
$keys = explode( '][', trim( $keys, '[]' ) );
return get_recursive_array_value( $array, $keys );
}
function get_recursive_array_value( $array, $keys ) {
if ( ! isset( $array[ $keys[0] ] ) ) {
return null;
};
$res = $array[ $keys[0] ];
if ( count( $keys ) > 1 ) {
array_shift( $keys );
return get_recursive_array_value( $res, $keys );
} else {
return $res;
}
}
echo get_array_value( $array, $keys );
Want you to use the $b array to follow the nested keys of the $a array and get the value in $c ?
<?php
$a = [ 'key_1' => [ 'key_2' => [ 'key_3' => 'value', ], ], ] ;
$b = ['key_1', 'key_2', 'key_3', ] ;
if ($b)
{
$c = $a ; // $a is not copied (copy-on-write)
foreach($b as $k)
if (isset($c[$k]))
$c = $c[$k] ;
else
{
unset($c);
break;
}
var_dump($c);
}
/*
output :
string(5) "value"
*/
or do you want to generate the $b array for an arbitrary formed string and get $c as a reference ?
<?php
$a = [ 'key_1' => [ 'key_2' => [ 'key_3' => 'value', ], ], ] ;
$b = '[key_1][key_2][key_3]';
if ($b !== '')
{
$b = explode('][', trim($b, '[]'));
$c = &$a ;
foreach($b as $k)
if (isset($c[$k]))
$c = &$c[$k] ;
else
{
unset($c);
break;
}
}
var_dump($c);
$c = 'new val' ;
unset($c);
var_dump($a['key_1']['key_2']['key_3']);
/*
output :
string(5) "value"
string(7) "new val"
*/
You have to use a separate variable for each dimension of the array. A common pattern you see with multidimensional arrays where you need to do something with the 2nd dimension is something like this:
$pets = [
'dog' => ['Jack', 'Fido', 'Woofie'],
'cat' => ['Muggles', 'Snowball', 'Kitty'],
];
// Loop through keys in first dimension
foreach ($pets as $type => $names) {
foreach ($names as $index => $name) {
// And do something with second dimension using the variable
// you've gained access to in the foreach
$pets[$type][$index] = strtoupper($name);
}
}
I am trying to created nested array from flat based on its keys.
Also format of keys in original array can be changed if it will simplify task.
From :
$arr = [
'player.name' => 'Joe',
'player.lastName' => 'Snow',
'team.name' => 'Stars',
'team.picture.name' => 'Joe Snow Profile',
'team.picture.file' => 'xxx.jpg'
];
To:
$arr = [
'player' => [
'name' => 'Joe'
, 'lastName' => 'Snow'
]
,'team' => [
'name'=> 'Stars'
,'picture' => [
'name' => 'Joe Snow Profile'
, 'file' =>'xxx.jpg'
]
],
];
Here is my take on it.
It should be able to handle arbitrary depth
function unflatten($arr) {
$result = array();
foreach($arr as $key => $value) {
$keys = explode(".", $key); //potentially other separator
$lastKey = array_pop($keys);
$node = &$result;
foreach($keys as $k) {
if (!array_key_exists($k, $node))
$node[$k] = array();
$node = &$node[$k];
}
$node[$lastKey] = $value;
}
return $result;
}
Combination of iteration and recursion. Could be simplified to just iterative.
$array = [
'player.name' => 'Joe',
'player.lastName' => 'Snow',
'team.name' => 'Stars',
'team.picture.name' => 'Joe Snow Profile',
'team.picture.file' => 'xxx.jpg'
];
$newArray = array ();
foreach($array as $key=> $value) {
$temp = array ();
$keys = array_reverse (explode('.', $key));
$temp[$keys[0]] = $value;
for ($i = 1; $i < count($keys); $i++) {
$temp[$keys[$i]] = $temp;
unset ($temp [$keys [$i -1]]);
}
$newArray = array_merge_recursive($newArray,$temp);
}
var_dump($newArray );
I received this question as a test, this is my answer:
<?php
function buildArray(&$newArray, $keys, $value)
{
if (sizeof($keys) == 0) {
return $value;
} else {
$key = array_shift($keys);
if (isset($newArray[$key])) {
$value = buildArray($newArray[$key], $keys, $value);
if (is_array($value)) {
$newArray[$key] += $value;
} else {
$newArray[$key] = $value;
}
$arr = $newArray;
} else {
$arr[$key] = buildArray($newArray, $keys, $value);
}
}
return $arr;
}
$arr = [
'player.name' => 'Joe',
'player.lastName' => 'Snow',
'team.name' => 'Stars',
'team.picture.name' => 'Joe Snow Profile',
'team.picture.file' => 'xxx.jpg',
];
$newArray = [];
foreach ($arr as $key => $value) {
$explodedKey = explode(".", $key);
$temp = buildArray($newArray, $explodedKey, $value);
$newArray = array_merge($temp, $newArray);
}
print_r($newArray);
?>
You could do it like this
$newArr = [];
for ($arr as $key => $val) {
$tmp = explode ('.', $key);
if (!array_key_exists ($tmp [0], $newArray){
$newArray [$tmp [0]] = [];
}
$newArray [$tmp [0]][$tmp [1]] = $val;
}
edit:
Damn didn't saw the third level in team.
Not very generic but should work for third level ;)
$newArr = [];
for ($arr as $key => $val) {
$tmp = explode ('.', $key);
if (!array_key_exists ($tmp [0], $newArray){
$newArray [$tmp [0]] = [];
}
if (count($tmp) > 2){
if (!array_key_exists ($tmp [1], $newArray[$tmp [0]]){
$newArray [$tmp [0]][$tmp[1]] = [];
}
$newArray [$tmp [0]][$tmp [1]][$tmp [2]] = $val;
} else {
$newArray [$tmp [0]][$tmp [1]] = $val;
}
}
I think you can use something like this, for converting 2d array to nested tree. But You'll have to play with parent_id
https://github.com/denis-cto/flat-array-to-nested-tree