简体   繁体   中英

PHP arrays: modify a value when the structure of the array is unknown

Question

How do you modify a value in a PHP array if you do not know the structure of the array in advance?

Example

In the following array, I have to change 'fave_color' to 'blue' in the part of the array where 'system' == 'knoppix' ... the problem is I do not know what the structure of the array will be at runtime, so I cannot simply do :

$myarray['settings']['user_prefs']['otherhost']['fave_color'] = 'blue';

This will not work, because the nesting is unknown for the fave_color at runtime.

Moreover, the fave_color key I am interested in is dependent on system array key .

I have to find the one with the value 'knoppix' and then change the corresponding 'fave_color' value, making sure not to change any other fave_color value in the array.

'settings' => array(
  'user_prefs'=>array(
    'localhost'=>array(
      'fave_color'=>'orange',
      'system'    =>'unbuntu',
    ),
    'otherhost'=>array(
      'fave_color'=>'yellow',
      'system'    =>'knoppix',
    ),        
  ),
),

You can use array_walk_recursive and check if key is equal to 'knoppix'.

function findKnoppix(&$value, $key)
{
    if($value == 'knoppix') $value = 'NEW VALUE';
}

array_walk_recursive($myArray, 'findKnoppix');

I know this question is a couple months old, but I ran into the same problem and came up with a solution I really liked and figured someone on SO had to have asked about it before. First, a couple of thoughts:

1) Can you use XPath or anything other than native PHP arrays, even if you have to convert back and forth? Measure the performance hit, of course, but try it out -- using a query style like you described in your related question might save you a headache without tremendous overhead.

2) Can you change the array's structure? If system is an important key, is there a good reason it's nested at that depth? The array might not be as compact if you restructured it as "settings by system type", but it could be much easier to traverse.

Finally, here's my solution to the problem I had, which I think you can adapt to yours. If you need to add more complex logic, such as checking for a requisite sibling, I would add an optional third parameter for a callback function.

/**
 * Sets key/value pairs at any depth on an array.
 * @param $attrs an array of key/value pairs to be set/added
 * @param $data the array you want to affect
 */
function setAttributes($attrs, &$data)
{
    foreach ($attrs as $name => $value) {
        if (strpos($name, '.') === false) {
            // If the array doesn't contain a special separator character,
            // just set the key/value pair. If $value is an array,
            // you will set nested key/value pairs just fine.
            $data[$name] = $value;
        } else {
            // In this case we're trying to target a specific nested key
            // without overwriting any other siblings/ancestors. The period
            // is my separator character -- you can change it to any unique
            // string that is invalid for a key in your system.
            $keys = explode('.', $name);
            // Set the root of the tree.
            $opt_tree =& $data;
            // Start traversing the tree using the specified keys.
            while ($key = array_shift($keys)) {
                // If there are more keys after the current one...
                if ($keys) {
                    if (!isset($opt_tree[$key]) || !is_array($opt_tree[$key])) {
                        // Create this node if it doesn't already exist.
                        $opt_tree[$key] = array();
                    }
                    // Here's the fun bit -- redefine the "root" of the tree
                    // (assignment by reference) then process the next key.
                    $opt_tree =& $opt_tree[$key];
                } else {
                    // This is the last key to check, so assign the value.
                    $opt_tree[$key] = $value;
                }
            }
        }
    }
}

Sample usage:

$x = array();
setAttributes(array('foo' => 'bar', 'baz' => array('quux', 42)), $x);
print_r($x); // $x has the same structure as the first argument
setAttributes(array('jif.snee' => 'hello world'), $x);
print_r($x); // $x now has a jif key whose value is snee => hello world
    // if jif.snee already existed, this call would have overwritten
    // that value without modifying any other values

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM