PHP edit value of multidimensional array item by path? [duplicate] - php

Let say I have an array like:
Array
(
[0] => Array
(
[Data] => Array
(
[id] => 1
[title] => Manager
[name] => John Smith
)
)
[1] => Array
(
[Data] => Array
(
[id] => 1
[title] => Clerk
[name] =>
(
[first] => Jane
[last] => Smith
)
)
)
)
I want to be able to build a function that I can pass a string to that will act as the array index path and return the appropriate array value without using eval(). Is that possible?
function($indexPath, $arrayToAccess)
{
// $indexPath would be something like [0]['Data']['name'] which would return
// "Manager" or it could be [1]['Data']['name']['first'] which would return
// "Jane" but the amount of array indexes that will be in the index path can
// change, so there might be 3 like the first example, or 4 like the second.
return $arrayToAccess[$indexPath] // <- obviously won't work
}

A Bit later, but... hope helps someone:
// $pathStr = "an:string:with:many:keys:as:path";
$paths = explode(":", $pathStr);
$itens = $myArray;
foreach($paths as $ndx){
$itens = $itens[$ndx];
}
Now itens is the part of the array you wanted to.
[]'s
Labs

you might use an array as path (from left to right), then a recursive function:
$indexes = {0, 'Data', 'name'};
function get_value($indexes, $arrayToAccess)
{
if(count($indexes) > 1)
return get_value(array_slice($indexes, 1), $arrayToAccess[$indexes[0]]);
else
return $arrayToAccess[$indexes[0]];
}

This is an old question but it has been referenced as this question comes up frequently.
There are recursive functions but I use a reference:
function array_nested_value($array, $path) {
$temp = &$array;
foreach($path as $key) {
$temp =& $temp[$key];
}
return $temp;
}
$path = array(0, 'Data', 'Name');
$value = array_nested_value($array, $path);

Try the following where $indexPath is formatted like a file path i.e.
'<array_key1>/<array_key2>/<array_key3>/...'.
function($indexPath, $arrayToAccess)
{
$explodedPath = explode('/', $indexPath);
$value =& $arrayToAccess;
foreach ($explodedPath as $key) {
$value =& $value[$key];
}
return $value;
}
e.g. using the data from the question, $indexPath = '1/Data/name/first' would return $value = Jane.

function($indexPath, $arrayToAccess)
{
eval('$return=$arrayToAccess'.$indexPath.';');
return $return;
}

You have to parse indexPath string. Chose some separator (for example "."), read text until "." that would be the first key, then read rest until next, that would be next key. Do that until no more dots.
You ken store key in array. Do foreach loop on this array to get seeked element.

Here is one way to get the job done, if string parsing is the way you want to go.
$data[0]["Data"]["stuff"] = "cake";
$path = "[0][\"Data\"]['stuff']";
function indexPath($path,$array){
preg_match_all("/\[['\"]*([a-z0-9_-]+)['\"]*\]/i",$path,$matches);
if(count($matches[1]) > 0) {
foreach ($matches[1] as $key) {
if (isset($array[$key])) {
$array = $array[$key];
} else {
return false;
}
}
} else {
return false;
}
return $array;
}
print_r(indexPath($path,$data));

A preg_match_all, cycling through the matched results would give you CLOSE to the result you wanted. You need to be careful with all of the strategies listed here for lost information. For instance, you have to devise some way to ensure that 55 stays as type int and isn't parsed as type string.

In addition to AbraCadaver:
function array_nested_value($array, $path) {
foreach($path as $key) {
$array = $array[$key];
}
return $array;
}
$path = array(0, 'Data', 'Name');
$value = array_nested_value($array, $path);
Possible use
function get_array_value($array=array(), $path=array()){
foreach($path as $key) {
if(isset($array[$key])){
$array=$array[$key];
}
else{
$array=NULL;
break;
}
}
return $array;
}
function custom_isset($array=array(), $path=array()){
$isset=true;
if(is_array($array)&&is_null(get_array_value($array, $path))){
$isset=false;
}
return $isset;
}
function is($array=array(), $path=array()){
$is=false;
if(is_array($array)){
$array_value=get_array_value($array, $path);
if(is_bool($array_value)){
$is=$array_value;
}
}
return $is;
}
Example
$status=[];
$status['updated']=true;
if(is($status,array('updated'))){
//do something...
}
Resources
https://gist.github.com/rafasashi/0d3ebadf08b8c2976a9d

If you already know the exact array element that you are pulling out why write a function to do it? What's wrong with just
$array[0]['data']['title']

Related

How to concatenate index into a array variable [duplicate]

Let say I have an array like:
Array
(
[0] => Array
(
[Data] => Array
(
[id] => 1
[title] => Manager
[name] => John Smith
)
)
[1] => Array
(
[Data] => Array
(
[id] => 1
[title] => Clerk
[name] =>
(
[first] => Jane
[last] => Smith
)
)
)
)
I want to be able to build a function that I can pass a string to that will act as the array index path and return the appropriate array value without using eval(). Is that possible?
function($indexPath, $arrayToAccess)
{
// $indexPath would be something like [0]['Data']['name'] which would return
// "Manager" or it could be [1]['Data']['name']['first'] which would return
// "Jane" but the amount of array indexes that will be in the index path can
// change, so there might be 3 like the first example, or 4 like the second.
return $arrayToAccess[$indexPath] // <- obviously won't work
}
A Bit later, but... hope helps someone:
// $pathStr = "an:string:with:many:keys:as:path";
$paths = explode(":", $pathStr);
$itens = $myArray;
foreach($paths as $ndx){
$itens = $itens[$ndx];
}
Now itens is the part of the array you wanted to.
[]'s
Labs
you might use an array as path (from left to right), then a recursive function:
$indexes = {0, 'Data', 'name'};
function get_value($indexes, $arrayToAccess)
{
if(count($indexes) > 1)
return get_value(array_slice($indexes, 1), $arrayToAccess[$indexes[0]]);
else
return $arrayToAccess[$indexes[0]];
}
This is an old question but it has been referenced as this question comes up frequently.
There are recursive functions but I use a reference:
function array_nested_value($array, $path) {
$temp = &$array;
foreach($path as $key) {
$temp =& $temp[$key];
}
return $temp;
}
$path = array(0, 'Data', 'Name');
$value = array_nested_value($array, $path);
Try the following where $indexPath is formatted like a file path i.e.
'<array_key1>/<array_key2>/<array_key3>/...'.
function($indexPath, $arrayToAccess)
{
$explodedPath = explode('/', $indexPath);
$value =& $arrayToAccess;
foreach ($explodedPath as $key) {
$value =& $value[$key];
}
return $value;
}
e.g. using the data from the question, $indexPath = '1/Data/name/first' would return $value = Jane.
function($indexPath, $arrayToAccess)
{
eval('$return=$arrayToAccess'.$indexPath.';');
return $return;
}
You have to parse indexPath string. Chose some separator (for example "."), read text until "." that would be the first key, then read rest until next, that would be next key. Do that until no more dots.
You ken store key in array. Do foreach loop on this array to get seeked element.
Here is one way to get the job done, if string parsing is the way you want to go.
$data[0]["Data"]["stuff"] = "cake";
$path = "[0][\"Data\"]['stuff']";
function indexPath($path,$array){
preg_match_all("/\[['\"]*([a-z0-9_-]+)['\"]*\]/i",$path,$matches);
if(count($matches[1]) > 0) {
foreach ($matches[1] as $key) {
if (isset($array[$key])) {
$array = $array[$key];
} else {
return false;
}
}
} else {
return false;
}
return $array;
}
print_r(indexPath($path,$data));
A preg_match_all, cycling through the matched results would give you CLOSE to the result you wanted. You need to be careful with all of the strategies listed here for lost information. For instance, you have to devise some way to ensure that 55 stays as type int and isn't parsed as type string.
In addition to AbraCadaver:
function array_nested_value($array, $path) {
foreach($path as $key) {
$array = $array[$key];
}
return $array;
}
$path = array(0, 'Data', 'Name');
$value = array_nested_value($array, $path);
Possible use
function get_array_value($array=array(), $path=array()){
foreach($path as $key) {
if(isset($array[$key])){
$array=$array[$key];
}
else{
$array=NULL;
break;
}
}
return $array;
}
function custom_isset($array=array(), $path=array()){
$isset=true;
if(is_array($array)&&is_null(get_array_value($array, $path))){
$isset=false;
}
return $isset;
}
function is($array=array(), $path=array()){
$is=false;
if(is_array($array)){
$array_value=get_array_value($array, $path);
if(is_bool($array_value)){
$is=$array_value;
}
}
return $is;
}
Example
$status=[];
$status['updated']=true;
if(is($status,array('updated'))){
//do something...
}
Resources
https://gist.github.com/rafasashi/0d3ebadf08b8c2976a9d
If you already know the exact array element that you are pulling out why write a function to do it? What's wrong with just
$array[0]['data']['title']

Is it possible to read an associative array value from layers deeper than the first one by passing its coordinate in a string? [duplicate]

I have a multidimensional array, here is a small excerpt:
Array (
[Albums] => Array (
[A Great Big World - Is There Anybody Out There] => Array(...),
[ATB - Contact] => Array(...),
)
[Pop] => Array (...)
)
And I have a dynamic path:
/albums/a_great_big_world_-_is_there_anybody_out_there
What would be the best way to retrieve the value of (in this example) $arr["albums"]["A Great Big World - Is There Anybody Out There"]?
Please note that it should be dynamic, since the nesting can go deeper than the 2 levels in this example.
EDIT
Here is the function I use to create a simple string for the URL:
function formatURL($url) {
return preg_replace('/__+/', '_', preg_replace('/[^a-z0-9_\s-]/', "", strtolower(str_replace(" ", "_", $url))));
}
$array = array(...);
$path = '/albums/a_great_big_world_-_is_there_anybody_out_there';
$value = $array;
foreach (explode('/', trim($path, '/')) as $key) {
if (isset($value[$key]) && is_array($value[$key])) {
$value = $value[$key];
} else {
throw new Exception("Path $path is invalid");
}
}
echo $value;

String to multidimensional array path

I have a multidimensional array, here is a small excerpt:
Array (
[Albums] => Array (
[A Great Big World - Is There Anybody Out There] => Array(...),
[ATB - Contact] => Array(...),
)
[Pop] => Array (...)
)
And I have a dynamic path:
/albums/a_great_big_world_-_is_there_anybody_out_there
What would be the best way to retrieve the value of (in this example) $arr["albums"]["A Great Big World - Is There Anybody Out There"]?
Please note that it should be dynamic, since the nesting can go deeper than the 2 levels in this example.
EDIT
Here is the function I use to create a simple string for the URL:
function formatURL($url) {
return preg_replace('/__+/', '_', preg_replace('/[^a-z0-9_\s-]/', "", strtolower(str_replace(" ", "_", $url))));
}
$array = array(...);
$path = '/albums/a_great_big_world_-_is_there_anybody_out_there';
$value = $array;
foreach (explode('/', trim($path, '/')) as $key) {
if (isset($value[$key]) && is_array($value[$key])) {
$value = $value[$key];
} else {
throw new Exception("Path $path is invalid");
}
}
echo $value;

How to dynamically set array keys in php

I have some logic that is being used to sort data but depending on the user input the data is grouped differently. Right now I have five different functions that contain the same logic but different groupings. Is there a way to combine these functions and dynamically set a value that will group properly. Within the function these assignments are happening
For example, sometimes I store the calculations simply by:
$calcs[$meter['UnitType']['name']] = ...
but other times need a more specific grouping:
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] =...
As you can see sometimes it is stored in a multidiminesional array and other times not. I have been trying to use eval() but without success (not sure that is the correct approach). Storing the data in a temporary variable does not really save much because there are many nested loops and if statements so the array would have to be repeated in multiple places.
EDIT
I hope the following example explains my problem better. It is obviously a dumbed down version:
if(){
$calcs[$meter['UnitType']['name']] = $data;
} else {
while () {
$calcs[$meter['UnitType']['name']] = $data;
}
}
Now the same logic can be used but for storing it in different keys:
if(){
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
Is there a way to abstract out the keys in the $calc[] array so that I can have one function instead of having multiple functions with different array keys?
You can use this if you want to get&set array values dynamically.
function getVal($data,$chain){
$level = $data;
for($i=0;$i<count($chain);$i++){
if(isset($level[$chain[$i]]))
$level = $level[$chain[$i]];
else
return null; // key does not exist, return null
}
return $level;
}
function setVal(&$data,$chain,$value){
$level = &$data;
for($i=0;$i<count($chain);$i++){
$level = &$level[$chain[$i]]; // set reference (&) in order to change the value of the object
}
$level = $value;
}
How it works:
Calling getVal($data,array('foo','bar','2017-08')) will return the equivalent of $data['foo']['bar']['2017-08'].
Calling setVal($data,array('foo','bar','2017-08'),'hello') will set value as if you called
$data['foo']['bar']['2017-08'] = 'hello'. non-existent keys will be created automatically by php magic.
This can be useful if you want to build the structure of the array dynamically.
Here's a function I wrote for setting deeply nested members on arrays or objects:
function dict_set($var, $path, $val) {
if(empty($var))
$var = is_array($var) ? array() : new stdClass();
$parts = explode('.', $path);
$ptr =& $var;
if(is_array($parts))
foreach($parts as $part) {
if('[]' == $part) {
if(is_array($ptr))
$ptr =& $ptr[];
} elseif(is_array($ptr)) {
if(!isset($ptr[$part]))
$ptr[$part] = array();
$ptr =& $ptr[$part];
} elseif(is_object($ptr)) {
if(!isset($ptr->$part))
$ptr->$part = array();
$ptr =& $ptr->$part;
}
}
$ptr = $val;
return $var;
}
Using your example data:
$array = [];
$array = dict_set($array, 'resource1.unit1.2017-10', 'value1');
$array = dict_set($array, 'resource1.unit2.2017-11', 'value2');
$array = dict_set($array, 'resource2.unit1.2017-10', 'value3');
print_r($array);
Results in output like:
Array
(
[resource1] => Array
(
[unit1] => Array
(
[2017-10] => value1
)
[unit2] => Array
(
[2017-11] => value2
)
)
[resource2] => Array
(
[unit1] => Array
(
[2017-10] => value3
)
)
)
The second argument to dict_set() is a $path string in dot-notation. You can build this using dynamic keys with period delimiters between the parts. The function works with arrays and objects.
It can also append incremental members to deeply nested array by using [] as an element of the $path. For instance: parent.child.child.[]
Would it not be easier to do the following
$calcs = array(
$meter['Resource']['name'] => array(
$meter['UnitType']['name'] => 'Some Value',
$meter['UnitType']['name2'] => 'Some Value Again'
),
);
or you can use Objects
$calcs = new stdClass();
$calcs->{$meter['UnitType']['name']} = 'Some Value';
but I would advice you build your structure in arrays and then do!
$calcs = (object)$calcs_array;
or you can loop your first array into a new array!
$new = array();
$d = date('Y-m',$start);
foreach($meter as $key => $value)
{
$new[$key]['name'][$d] = array();
}
Give it ago and see how the array structure comes out.
Try to use a switch case.
<?php
$userinput = $calcs[$meter['UnitType']['name']] = $data;;
switch ($userinput) {
case "useriput1":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
case "userinput2":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
...
default:
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
?>
I agree with the comment on the OP by #Jake N that perhaps using objects is a better approach. Nonetheless, if you want to use arrays, you can check for the existence of keys in a conditional, like so:
if(
array_key_exists('Resource', $meter)
) {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
$calcs[$meter['UnitType']['name']] = $data;
}
On the other hand, if you want to use objects, you can create a MeterReading object type, and then add MeterReading instances as array elements to your $calcs array, like so:
// Object defintion
class MeterReading {
private $data;
private $resource;
private $startDate;
private $unitType;
public function __construct(Array $meter, $start, $data) {
$this->unitType = $meter['UnitType']['name'];
$this->resource = $meter['Resource']['name'];
$this->startDate = date('Y-m',$start);
}
public function data() {
return $this->data;
}
public function resource() {
return $this->resource;
}
public function startDate() {
return $this->startDate;
}
public function unitType() {
return $this->unitType;
}
}
// Example population
$calcs[] = new MeterReading($meter, $start, $data);
// Example usage
foreach($calcs as $calc) {
if($calc->resource()) {
echo 'Resource: ' . $calc->resource() . '<br>';
}
echo 'Unit Type: ' . $calc->unitType() . '<br>';
echo 'Start Date: ' . $calc->startDate() . '<br>';
echo 'Data: ' . $calc->data() . '<br>';
}
Obviously you can take this further, such as checking the existence of array keys in the object constructor, giving the object property resource a default value if not provided, and so on, but this is a start to an OO approach.
You can use this library to get or set value in multidimensional array using array of keys:
Arr::getNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
]);
to get value or:
Arr::handleNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
], $data);
to set $data as value.

access php array children through parameters?

I have a unique case where I have an array like so:
$a = array('a' => array('b' => array('c' => 'woohoo!')));
I want to access values of the array in a manner like this:
some_function($a, array('a')) which would return the array for position a
some_function($a, array('a', 'b', 'c')) which would return the word 'woohoo'
So basically, it drills down in the array using the passed in variables in the second param and checks for the existence of that key in the result. Any ideas on some native php functions that can help do this? I'm assuming it'll need to make use of recursion. Any thoughts would be really appreciated.
Thanks.
This is untested but you shouldn't need recursion to handle this case:
function getValueByKey($array, $key) {
foreach ($key as $val) {
if (!empty($array[$val])) {
$array = $array[$val];
} else return false;
}
return $array;
}
You could try with RecursiveArrayIterator
Here is an example on how to use it.
Here’s a recursive implementation:
function some_function($array, $path) {
if (!count($path)) {
return;
}
$key = array_shift($path);
if (!array_key_exists($key, $array)) {
return;
}
if (count($path) > 1) {
return some_function($array[$key], $path);
} else {
return $array[$key];
}
}
And an iterative implementation:
function some_function($array, $path) {
if (!count($path)) {
return;
}
$tmp = &$array;
foreach ($path as $key) {
if (!array_key_exists($key, $tmp)) {
return;
}
$tmp = &$tmp[$key];
}
return $tmp;
}
These functions will return null if the path is not valid.
$a['a'] returns the array at position a.
$a['a']['b']['c'] returns woohoo.
Won't this do?

Categories