4

Is there a way with Laravel Collections to flatten an array with key "namespace". Something like:

$a = collect([
    'id' => 1,
    'data' => [
        'a' => 2,
        'b' => 3
    ]
]);

$a = $a->flattenWithKeysNamespace(); // <-- this does not exists

// Should returns: 
// ['a' => 1, 'data.b' => 2, 'data.c' => 3]; // <-- I would like this.

I know I can do this in raw PHP, or with some assembly of Collection functions, but sometimes I miss something in Laravel Collection documentation. So is there a simple way with Collection functions to do this?

rap-2-h
  • 30,204
  • 37
  • 167
  • 263
  • 1
    I think you are right is that there is no "Laravel way" to do this. Answers like [this](http://stackoverflow.com/a/9546235/1123303) show a way to do so in PHP if you are willing to convert your `Collection` to an array, but since you mention raw PHP I assume you already found this kind of solution. I think your best bet in doing it using `Collection` methods is by writing a similar function to the one I linked, but use functions like `flatMap()` and recursively call your function when your element is also a collection. – Radical May 10 '17 at 12:28
  • Thanks! You could post your comment as an answer and I will accept it! – rap-2-h May 11 '17 at 09:06

2 Answers2

1

I think you are right is that there is no "Laravel way" to do this. Answers like this show a way to do so in PHP if you are willing to convert your Collection to an array, but since you mention raw PHP I assume you already found this kind of solution.

I think your best bet in doing it using Collection methods is by writing a similar function to the one I linked, but use functions like flatMap() and recursively call your function when your element is also a collection.

Community
  • 1
  • 1
Radical
  • 1,003
  • 1
  • 9
  • 25
1

If you don't care about the level of depth that gets converted, I think the simplest option for you is just the array_dot helper function. If you want more granular control of how deep the recursion should go, and whether or not to have dot-delimited array keys, I've written a collection macro that can do that. Typically collect($array)->collapse() maintains string keys, but non-incremental numeric ones still getting lost, even if type-forced to a string. And I had a recent need to maintain them.

Put this in your AppServiceProvider::boot() method:

    /**
     * Flatten an array while keeping it's keys, even non-incremental numeric ones, in tact.
     *
     * Unless $dotNotification is set to true, if nested keys are the same as any
     * parent ones, the nested ones will supersede them.
     *
     * @param int $depth How many levels deep to flatten the array
     * @param bool $dotNotation Maintain all parent keys in dot notation
     */
    Collection::macro('flattenKeepKeys', function ($depth = 1, $dotNotation = false) {
        if ($depth) {
            $newArray = [];
            foreach ($this->items as $parentKey => $value) {
                if (is_array($value)) {
                    $valueKeys = array_keys($value);
                    foreach ($valueKeys as $key) {
                        $subValue = $value[$key];
                        $newKey = $key;
                        if ($dotNotation) {
                            $newKey = "$parentKey.$key";
                            if ($dotNotation !== true) {
                                $newKey = "$dotNotation.$newKey";
                            }

                            if (is_array($value[$key])) {
                                $subValue = collect($value[$key])->flattenKeepKeys($depth - 1, $newKey)->toArray();
                            }
                        }
                        $newArray[$newKey] = $subValue;
                    }
                } else {
                    $newArray[$parentKey] = $value;
                }
            }

            $this->items = collect($newArray)->flattenKeepKeys(--$depth, $dotNotation)->toArray();
        }

        return collect($this->items);
    });

Then you can call collect($a)->flattenKeepKeys(1, true); and get back what you're expecting.

kmuenkel
  • 2,659
  • 1
  • 19
  • 20