How to access and manipulate multi-dimensional array by key names / path?

前端 未结 10 2209
北恋
北恋 2020-11-21 07:35

I\'ve to implement a setter in PHP, that allows me to specify the key, or sub key, of an array (the target), passing the name as a dot-separated-keys value.

相关标签:
10条回答
  • 2020-11-21 07:46

    This function does the same as the accepted answer, plus is adds a third parameter by reference that is set to true/false if the key is present

    function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
      $ref = &$array;
      foreach ($parents as $parent) {
        if (is_array($ref) && array_key_exists($parent, $ref)) {
          $ref = &$ref[$parent];
        }
        else {
          $key_exists = FALSE;
          $null = NULL;
          return $null;
        }
      }
      $key_exists = TRUE;
      return $ref;
    }
    
    0 讨论(0)
  • 2020-11-21 07:47

    This is an approach using a static class. The benefits of this style is that your configuration will be globally accessible in your application.

    It works by taking in a key path for example "database.mysql.username" and splitting the string into each of the key parts and moving a pointer to create a nested array.

    The benefits of this approach is you can give a partial key and get back arrays of configuration values, you're not limited to just the end values. It also makes "default values" trivial to implement.

    If you would like to have multiple configuration stores, just remove the static keywords and use it as an object instead.

    Live Example

    class Config
    {
        private static $configStore = [];
        // This determines what separates the path
        // Examples: "." = 'example.path.value' or "/" = 'example/path/value'
        private static $separator = '.';
    
        public static function set($key, $value)
        {
            $keys = explode(self::$separator, $key);
    
            // Start at the root of the configuration array
            $pointer = &self::$configStore;
    
            foreach ($keys as $keySet) {
                // Check to see if a key exists, if it doesn't, set that key as an empty array
                if (!isset($pointer[$keySet])) {
                    $pointer[$keySet] = [];
                }
    
                // Set the pointer to the current key
                $pointer = &$pointer[$keySet];
            }
    
            // Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
            $pointer = $value;
        }
    
        public static function get($key, $defaultValue = null)
        {
            $keys = explode(self::$separator, $key);
    
            // Start at the root of the configuration array
            $pointer = &self::$configStore;
    
            foreach ($keys as $keySet) {
                // If we don't have a key as a part of the path, we should return the default value (null)
                if (!isset($pointer[$keySet])) {
                    return $defaultValue;
                }
                $pointer = &$pointer[$keySet];
            }
    
            // Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
            return $pointer;
        }
    }
    
    // Examples of how to use
    Config::set('database.mysql.username', 'exampleUsername');
    Config::set('database.mysql.password', 'examplePassword');
    Config::set('database.mysql.database', 'exampleDatabase');
    Config::set('database.mysql.host', 'exampleHost');
    
    // Get back all the database configuration keys
    var_dump(Config::get('database.mysql'));
    
    // Get back a particular key from the database configuration
    var_dump(Config::get('database.mysql.host'));
    
    // Get back a particular key from the database configuration with a default if it doesn't exist
    var_dump(Config::get('database.mysql.port', 3306));
    
    0 讨论(0)
  • 2020-11-21 07:48

    I have solution for you not in the pure PHP, but using ouzo goodies concretely Arrays::getNestedValue method:

    $arr = array('a' => 1,
        'b' => array(
            'y' => 2,
            'x' => array('z' => 5, 'w' => 'abc')
        ),
        'c' => null);
    
    $key = 'b.x.z';
    $path = explode('.', $key);
    
    print_r(Arrays::getNestedValue($arr, $path));
    

    Similarly if you need to set nested value you can use Arrays::setNestedValue method.

    $arr = array('a' => 1,
        'b' => array(
            'y' => 2,
            'x' => array('z' => 5, 'w' => 'abc')
        ),
        'c' => null);
    
    Arrays::setNestedValue($arr, array('d', 'e', 'f'), 'value');
    print_r($arr);
    
    0 讨论(0)
  • 2020-11-21 07:48

    If the keys of the array are unique, you can solve the problem in a few lines of code using array_walk_recursive:

        $arr = array('a' => 1,
            'b' => array(
                'y' => 2,
                'x' => array('z' => 5, 'w' => 'abc')
            ),
            'c' => null);
    
        function changeVal(&$v, $key, $mydata) {
            if($key == $mydata[0]) {
                $v = $mydata[1];
            }
        }
    
        $key = 'z';
        $value = '56';
        array_walk_recursive($arr, 'changeVal', array($key, $value));
    
        print_r($arr);
    
    0 讨论(0)
  • 2020-11-21 07:48

    Yet another solution for getter, using plain array_reduce method

    @AbraCadaver's solution is nice, but not complete:

    • missing optional separator parameter & split if required
    • it raises an error in case of trying to obtain a key from a scalar value like 'one.two' from ['one' => 2]

    My solution is:

    function get ($array, $path, $separator = '.') {
        if (is_string($path)) {
            $path = explode($separator, $path);
        }
    
        return array_reduce(
            $path,
            function ($carry, $item) {
                return $carry[$item] ?? null;
            },
            $array
        );
    }
    

    it requires PHP 7 due to ?? operator, but this can be changed for older versions pretty easy ...

    0 讨论(0)
  • 2020-11-21 07:51

    Assuming $path is already an array via explode (or add to the function), then you can use references. You need to add in some error checking in case of invalid $path etc. (think isset):

    $key = 'b.x.z';
    $path = explode('.', $key);
    

    Getter

    function get($path, $array) {
        //$path = explode('.', $path); //if needed
        $temp =& $array;
    
        foreach($path as $key) {
            $temp =& $temp[$key];
        }
        return $temp;
    }
    
    $value = get($path, $arr); //returns NULL if the path doesn't exist
    

    Setter / Creator

    This combination will set a value in an existing array or create the array if you pass one that has not yet been defined. Make sure to define $array to be passed by reference &$array:

    function set($path, &$array=array(), $value=null) {
        //$path = explode('.', $path); //if needed
        $temp =& $array;
    
        foreach($path as $key) {
            $temp =& $temp[$key];
        }
        $temp = $value;
    }
    
    set($path, $arr);
    //or
    set($path, $arr, 'some value');
    

    Unsetter

    This will unset the final key in the path:

    function unsetter($path, &$array) {
        //$path = explode('.', $path); //if needed
        $temp =& $array;
    
        foreach($path as $key) {
            if(!is_array($temp[$key])) {
                unset($temp[$key]);
            } else {
                $temp =& $temp[$key];
            }
        }
    }
    unsetter($path, $arr);
    

    *The original answer had some limited functions that I will leave in case they are of use to someone:

    Setter

    Make sure to define $array to be passed by reference &$array:

    function set(&$array, $path, $value) {
        //$path = explode('.', $path); //if needed
        $temp =& $array;
    
        foreach($path as $key) {
            $temp =& $temp[$key];
        }
        $temp = $value;
    }
    
    set($arr, $path, 'some value');
    

    Or if you want to return the updated array (because I'm bored):

    function set($array, $path, $value) {
        //$path = explode('.', $path); //if needed
        $temp =& $array;
    
        foreach($path as $key) {
            $temp =& $temp[$key];
        }
        $temp = $value;
    
        return $array;
    }
    
    $arr = set($arr, $path, 'some value');
    

    Creator

    If you wan't to create the array and optionally set the value:

    function create($path, $value=null) {
        //$path = explode('.', $path); //if needed
        foreach(array_reverse($path) as $key) {
            $value = array($key => $value);
        }
        return $value;
    }    
    
    $arr = create($path);    
    //or
    $arr = create($path, 'some value');
    

    For Fun

    Constructs and evaluates something like $array['b']['x']['z'] given a string b.x.z:

    function get($array, $path) {
        //$path = explode('.', $path); //if needed
        $path = "['" . implode("']['", $path) . "']";
        eval("\$result = \$array{$path};");
    
        return $result;
    }
    

    Sets something like $array['b']['x']['z'] = 'some value';:

    function set(&$array, $path, $value) {
        //$path = explode('.', $path); //if needed
        $path = "['" . implode("']['", $path) . "']";
        eval("\$array{$path} = $value;");
    }
    

    Unsets something like $array['b']['x']['z']:

    function unsetter(&$array, $path) {
        //$path = explode('.', $path); //if needed
        $path = "['" . implode("']['", $path) . "']";
        eval("unset(\$array{$path});");
    }
    
    0 讨论(0)
提交回复
热议问题