PHP Nested Arrays: Imploding all the tree keys of each leaf results in a multidimensional array instead of a 1D associative one

Viewed 103

From a nested array, I want to generate the 1D associative array which contains, for each leaf, its ascending keys concatenation.

Summary

  1. Expected results example

    1.1. Input

    1.2. Output

  2. Actual results example

    1.1. Input

    1.2. Output

  3. Question

  4. Minimal, Testable Executable Sources

    4.1. Explanations

    4.2. Sources & Execution


Expected results example

Input

The following nested array:

[
   'key1' => 'foo',
   'key2' => [
         'key3' => [
               0 => ['key4' => 'bar' ],
               1 => ['key4' => 'azerty']
         ]
   ]    
]

Output

The following 1D associative array (glue character for the concatenation of the keys: _):

[
   'key1' => 'foo', 
   'key2_key3_0_key4' => 'bar',
   'key2_key3_1_key4' => 'azerty'
]

Actual results example

Input

[
   'etat' => 'bar',
   'proposition_en_cours' => [
         'fichiers' => [
               0 => ['url_fichier' => 'foo' ],
               1 => ['url_fichier' => 'bar']
         ]
   ]    
]

Output

Array
(
    [] => bar
    [proposition_en_cours] => Array
        (
            [fichiers] => Array
                (
                    [0] => Array
                        (
                            [url_fichier] => foo
                        )

                    [1] => Array
                        (
                            [url_fichier] => bar
                        )

                )

        )

    [proposition_en_cours_fichiers] => Array
        (
            [0] => Array
                (
                    [url_fichier] => foo
                )

            [1] => Array
                (
                    [url_fichier] => bar
                )

        )

    [proposition_en_cours_fichiers_0] => foo
    [proposition_en_cours_fichiers_0_1] => bar
)

Question

As you can see, the array I get differs in all points from the expected one. I can't figure out why.

Minimal, Testable Executable Sources

Explanations

I initialize an array that must contain all the ascending keys for each leaf: $key_in_db_format = [];.

I iterate on the input array. For each element (leaf or subarray), I pop $key_in_db_format if, and only if, the current depth equals the last depth. If it's an array (i.e.: not a leaf): I add the key to $key_in_db_format. I set a value (the leaf) at the key that is the concatenation of the ascending keys.

Sources & Execution

  1. First, define this array in an empty PHP script of your choice:

    $values = [
    
        'etat' => 'bar',
    
        'proposition_en_cours' => [
            'fichiers' => [
                0 => [ 'url_fichier' => 'foo' ],
                1 => [ 'url_fichier' => 'bar' ]
             ]
        ]
    
    ];
    
  2. Then, copy/paste the following code and you will be able to execute it:

     $values_to_insert_in_meta_table = [];
    
     $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($values), \RecursiveIteratorIterator::SELF_FIRST);
     $last_depth = 0;
     $key_in_db_format = [];
     foreach ($iterator as $value_key_field => $value_value_field) {
         if($iterator->getDepth() == $last_depth) {
             array_pop($key_in_db_format);
         }
    
         if(is_array($value_value_field)) {
             array_push($key_in_db_format, $value_key_field);
         } else {
             $values_to_insert_in_meta_table[implode('_', $key_in_db_format)] = $value_value_field;
         }
    
    
         $last_depth = $iterator->getDepth();
     }
    
     echo '<pre>';
     print_r($values_to_insert_in_meta_table);
    
2 Answers

I think I've found a solution!!! :-)

$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($values), \RecursiveIteratorIterator::SELF_FIRST);
        $key_in_db_format = [];
        $current_counter = 0;
        foreach($iterator as $value_key_field => $value_value_field) {
            if(is_array($value_value_field)) {  
                $current_counter = 0;
                array_push($key_in_db_format, $value_key_field);                
            }

            if(!is_array($value_value_field)) {
                $key_to_insert_in_db = !empty($key_in_db_format) ? implode('_', $key_in_db_format) . '_' . $value_key_field : $value_key_field ;
                $values_to_insert_in_meta_table[$key_to_insert_in_db] = $value_value_field;

                if($current_counter == count($iterator->getSubIterator())) {
                    array_pop($key_in_db_format);
                }

                $current_counter++;
            }
        }
        echo '<br /> <pre>';

        print_r($values_to_insert_in_meta_table);
        exit;

The idea is:

  1. We add to the array of ascendent keys the key if, and only if, the current element is not a leaf.

  2. If the current element is a leaf, then we define the key equalled to the imploded ascendent keys PLUS (concatenation) the current element's key. Moreover we pop the array of ascendent keys if there are not following siblings elements.

Maybe I missed something, but as far as I understand, I would do something like that:

<?php

function flatten(array $array, ?string $prefix = null): array {
    $prefix = $prefix === null ? '' : "{$prefix}_";
    $output = [];

    foreach ($array as $key => $value) {
        $key = $prefix . $key;

        if (is_array($value)) {
            $output = array_merge($output, flatten($value, $key));
        } else {
            $output[$key] = $value;
        }
    }

    return $output;
}

var_export(flatten([
    'key1' => 'foo',
    'key2' => [
        'key3' => [
            0 => ['key4' => 'bar' ],
            1 => ['key4' => 'azerty']
        ]
    ]
]));

Output:

array (
  'key1' => 'foo',
  'key2_key3_0_key4' => 'bar',
  'key2_key3_1_key4' => 'azerty',
)
Related